diff --git a/src/cm/baseExtensions.ts b/src/cm/baseExtensions.ts index bd10524604..a0db81d4c4 100644 --- a/src/cm/baseExtensions.ts +++ b/src/cm/baseExtensions.ts @@ -1,6 +1,7 @@ import { acceptCompletion, closeBrackets, + closeBracketsKeymap, completionKeymap, } from "@codemirror/autocomplete"; import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; @@ -75,7 +76,12 @@ export default function createBaseExtensions( Prec.highest(keymap.of([{ key: "Tab", run: acceptCompletion }])), ); extensions.push( - keymap.of([...completionKeymap, ...defaultKeymap, ...historyKeymap]), + keymap.of([ + ...(autoCloseBrackets ? closeBracketsKeymap : []), + ...completionKeymap, + ...defaultKeymap, + ...historyKeymap, + ]), ); extensions.push( // This prevents tooltips from being going out of the editor area diff --git a/src/test/editor.tests.js b/src/test/editor.tests.js index c0a3fd216b..6e0bbbb61f 100644 --- a/src/test/editor.tests.js +++ b/src/test/editor.tests.js @@ -7,7 +7,7 @@ import { } from "@codemirror/language"; import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; import { EditorSelection, EditorState } from "@codemirror/state"; -import { EditorView } from "@codemirror/view"; +import { EditorView, runScopeHandlers } from "@codemirror/view"; import createBaseExtensions from "cm/baseExtensions"; import indentGuides from "cm/indentGuides"; import { @@ -33,7 +33,7 @@ import { TestRunner } from "./tester"; export async function runCodeMirrorTests(writeOutput) { const runner = new TestRunner("CodeMirror 6 Editor Tests"); - function createEditor(doc = "", extensions = []) { + function createEditor(doc = "", extensions = [], baseExtensionOptions = {}) { const container = document.createElement("div"); container.style.width = "500px"; container.style.height = "300px"; @@ -42,18 +42,31 @@ export async function runCodeMirrorTests(writeOutput) { const state = EditorState.create({ doc, - extensions: [...createBaseExtensions(), ...extensions], + extensions: [ + ...createBaseExtensions(baseExtensionOptions), + ...extensions, + ], }); const view = new EditorView({ state, parent: container }); return { view, container }; } - async function withEditor(test, fn, initialDoc = "", extensions = []) { + async function withEditor( + test, + fn, + initialDoc = "", + extensions = [], + baseExtensionOptions = {}, + ) { let view, container; try { - ({ view, container } = createEditor(initialDoc, extensions)); + ({ view, container } = createEditor( + initialDoc, + extensions, + baseExtensionOptions, + )); test.assert(view != null, "EditorView instance should be created"); await new Promise((resolve) => setTimeout(resolve, 100)); await fn(view); @@ -139,6 +152,50 @@ export async function runCodeMirrorTests(writeOutput) { container.remove(); }); + runner.test("Backspace deletes an auto-closed bracket pair", async (test) => { + await withEditor( + test, + async (view) => { + view.dispatch({ selection: { anchor: 1 } }); + const handled = runScopeHandlers( + view, + new KeyboardEvent("keydown", { key: "Backspace" }), + "editor", + ); + + test.assert( + handled, + "Backspace should be handled between a bracket pair", + ); + test.assertEqual(view.state.doc.toString(), ""); + }, + "()", + ); + }); + + runner.test( + "Backspace behaves normally when auto-close is disabled", + async (test) => { + await withEditor( + test, + async (view) => { + view.dispatch({ selection: { anchor: 1 } }); + const handled = runScopeHandlers( + view, + new KeyboardEvent("keydown", { key: "Backspace" }), + "editor", + ); + + test.assert(handled, "Backspace should retain its default behavior"); + test.assertEqual(view.state.doc.toString(), ")"); + }, + "()", + [], + { autoCloseBrackets: false }, + ); + }, + ); + runner.test("State access", async (test) => { await withEditor(test, async (view) => { const state = view.state;