refactor(codemirror): split custom tab functionality into more methods

This commit is contained in:
Elian Doran 2025-05-31 12:11:57 +03:00
parent 9e3909a5f7
commit 56b7965c9a
No known key found for this signature in database

View File

@ -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;
}