diff --git a/packages/codemirror/src/extensions/custom_tab.ts b/packages/codemirror/src/extensions/custom_tab.ts index 3987827dd..e0f39690a 100644 --- a/packages/codemirror/src/extensions/custom_tab.ts +++ b/packages/codemirror/src/extensions/custom_tab.ts @@ -1,5 +1,5 @@ import { indentLess, indentMore } from "@codemirror/commands"; -import { EditorSelection, EditorState, SelectionRange, type ChangeSpec } from "@codemirror/state"; +import { EditorSelection, EditorState, SelectionRange, type Transaction, type ChangeSpec } from "@codemirror/state"; import type { KeyBinding } from "@codemirror/view"; /** @@ -19,8 +19,6 @@ const smartIndentWithTab: KeyBinding[] = [ } const { selection } = state; - const changes: ChangeSpec[] = []; - const newSelections: SelectionRange[] = []; // Step 1: Handle non-empty selections → replace with tab if (selection.ranges.some(range => !range.empty)) { @@ -40,55 +38,69 @@ const smartIndentWithTab: KeyBinding[] = [ // Multiple lines are selected, indent each line. return indentMore({ state, dispatch }); } else { - // Single line selection, replace with tab. - for (let range of selection.ranges) { - changes.push({ from: range.from, to: range.to, insert: "\t" }); - newSelections.push(EditorSelection.cursor(range.from + 1)); - } - - dispatch( - state.update({ - changes, - selection: EditorSelection.create(newSelections), - scrollIntoView: true, - userEvent: "input" - }) - ); + return handleSingleLineSelection(state, dispatch); } - - return true; } // Step 2: Handle empty selections - for (let range of selection.ranges) { - const line = state.doc.lineAt(range.head); - const beforeCursor = state.doc.sliceString(line.from, range.head); - - if (/^\s*$/.test(beforeCursor)) { - // Only whitespace before cursor → indent line - return indentMore({ state, dispatch }); - } else { - // Insert tab character at cursor - changes.push({ from: range.head, to: range.head, insert: "\t" }); - newSelections.push(EditorSelection.cursor(range.head + 1)); - } - } - - if (changes.length) { - dispatch( - state.update({ - changes, - selection: EditorSelection.create(newSelections), - scrollIntoView: true, - userEvent: "input" - }) - ); - return true; - } - - return false; + return handleEmptySelections(state, dispatch); }, shift: indentLess }, ] export default smartIndentWithTab; + +function handleSingleLineSelection(state: EditorState, dispatch: (transaction: Transaction) => void) { + const changes: ChangeSpec[] = []; + const newSelections: SelectionRange[] = []; + + // Single line selection, replace with tab. + for (let range of state.selection.ranges) { + changes.push({ from: range.from, to: range.to, insert: "\t" }); + newSelections.push(EditorSelection.cursor(range.from + 1)); + } + + dispatch( + state.update({ + changes, + selection: EditorSelection.create(newSelections), + scrollIntoView: true, + userEvent: "input" + }) + ); + + return true; +} + +function handleEmptySelections(state: EditorState, dispatch: (transaction: Transaction) => void) { + const changes: ChangeSpec[] = []; + const newSelections: SelectionRange[] = []; + + for (let range of state.selection.ranges) { + const line = state.doc.lineAt(range.head); + const beforeCursor = state.doc.sliceString(line.from, range.head); + + if (/^\s*$/.test(beforeCursor)) { + // Only whitespace before cursor → indent line + return indentMore({ state, dispatch }); + } else { + // Insert tab character at cursor + changes.push({ from: range.head, to: range.head, insert: "\t" }); + newSelections.push(EditorSelection.cursor(range.head + 1)); + } + } + + if (changes.length) { + dispatch( + state.update({ + changes, + selection: EditorSelection.create(newSelections), + scrollIntoView: true, + userEvent: "input" + }) + ); + return true; + } + + return false; +}