feat(editor): add query editing utilities
This commit is contained in:
1 parent
d689d78ab0
commit
782615586c
15 files changed
+989
-59
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import type { EditorState, TransactionSpec } from "@codemirror/state";
|
||||
import { SearchQuery } from "@codemirror/search";
|
||||
|
||||
export interface EditorSearchQueryOptions {
|
||||
@@ -16,3 +17,35 @@ export function createEditorSearchQuery(options: EditorSearchQueryOptions): Sear
|
||||
literal: !options.useRegex,
|
||||
});
|
||||
}
|
||||
|
||||
export interface EditorSearchMatch {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
export function collectEditorSearchMatches(query: SearchQuery, state: EditorState, from: number, to: number, limit = Number.POSITIVE_INFINITY): EditorSearchMatch[] {
|
||||
const matches: EditorSearchMatch[] = [];
|
||||
const cursor = query.getCursor(state);
|
||||
|
||||
for (let result = cursor.next(); !result.done; result = cursor.next()) {
|
||||
if (result.value.from >= from && result.value.to <= to) {
|
||||
matches.push({ from: result.value.from, to: result.value.to });
|
||||
if (matches.length >= limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
export function replaceEditorSearchMatches(view: { dispatch(spec: TransactionSpec): void }, matches: readonly EditorSearchMatch[], replacement: (match: EditorSearchMatch) => string): boolean {
|
||||
if (matches.length === 0) return false;
|
||||
|
||||
view.dispatch({
|
||||
changes: matches.map((match) => ({
|
||||
from: match.from,
|
||||
to: match.to,
|
||||
insert: replacement(match),
|
||||
})),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { ChangeSpec, EditorState, SelectionRange, Text, TransactionSpec } from "@codemirror/state";
|
||||
|
||||
export interface DispatchableEditorView {
|
||||
state: EditorState;
|
||||
dispatch(spec: TransactionSpec): void;
|
||||
}
|
||||
|
||||
export function replaceSelectedEditorText(view: DispatchableEditorView, insert: string): boolean {
|
||||
if (view.state.readOnly) return false;
|
||||
|
||||
const selection = view.state.selection.main;
|
||||
if (selection.empty) return false;
|
||||
|
||||
view.dispatch({
|
||||
changes: { from: selection.from, to: selection.to, insert },
|
||||
selection: { anchor: selection.from + insert.length },
|
||||
scrollIntoView: true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export function blankLineDeletionChanges(doc: Text, selection: SelectionRange): ChangeSpec[] {
|
||||
if (doc.length === 0) return [];
|
||||
|
||||
const hasSelection = !selection.empty;
|
||||
const firstLine = hasSelection ? doc.lineAt(selection.from).number : 1;
|
||||
const lastSelectedPosition = hasSelection ? Math.max(selection.from, selection.to - 1) : doc.length;
|
||||
const lastLine = hasSelection ? doc.lineAt(lastSelectedPosition).number : doc.lines;
|
||||
const changes: ChangeSpec[] = [];
|
||||
|
||||
let lineNumber = firstLine;
|
||||
while (lineNumber <= lastLine) {
|
||||
const line = doc.line(lineNumber);
|
||||
if (line.text.trim().length > 0) {
|
||||
lineNumber += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstBlankLine = line;
|
||||
let lastBlankLine = line;
|
||||
while (lineNumber < lastLine) {
|
||||
const nextLine = doc.line(lineNumber + 1);
|
||||
if (nextLine.text.trim().length > 0) break;
|
||||
lineNumber += 1;
|
||||
lastBlankLine = nextLine;
|
||||
}
|
||||
|
||||
// Prefer the following separator so non-empty lines retain their existing newline.
|
||||
if (lastBlankLine.to < doc.length) {
|
||||
changes.push({ from: firstBlankLine.from, to: lastBlankLine.to + 1 });
|
||||
} else if (firstBlankLine.from > 0) {
|
||||
changes.push({ from: firstBlankLine.from - 1, to: lastBlankLine.to });
|
||||
} else {
|
||||
changes.push({ from: 0, to: doc.length });
|
||||
}
|
||||
lineNumber += 1;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
Reference in new issue
Block a user