144 lines
3.8 KiB
TypeScript
144 lines
3.8 KiB
TypeScript
import type { EditorView } from "@codemirror/view";
|
|
import { undo, redo } from "@codemirror/commands";
|
|
|
|
export function insertText(view: EditorView | null, text: string) {
|
|
if (!view) return;
|
|
const { from, to } = view.state.selection.main;
|
|
view.dispatch({
|
|
changes: { from, to, insert: text },
|
|
selection: { anchor: from + text.length },
|
|
});
|
|
view.focus();
|
|
}
|
|
|
|
export function wrapSelection(
|
|
view: EditorView | null,
|
|
prefix: string,
|
|
suffix: string,
|
|
placeholder = "",
|
|
) {
|
|
if (!view) return;
|
|
const { state } = view;
|
|
const selection = state.selection.main;
|
|
const selected = state.doc.sliceString(selection.from, selection.to);
|
|
|
|
const innerWrapped =
|
|
selected.length >= prefix.length + suffix.length &&
|
|
selected.startsWith(prefix) &&
|
|
selected.endsWith(suffix);
|
|
|
|
const before = state.doc.sliceString(
|
|
Math.max(0, selection.from - prefix.length),
|
|
selection.from,
|
|
);
|
|
const after = state.doc.sliceString(
|
|
selection.to,
|
|
Math.min(state.doc.length, selection.to + suffix.length),
|
|
);
|
|
const outerWrapped = before === prefix && after === suffix;
|
|
|
|
if (innerWrapped) {
|
|
const inner = selected.slice(prefix.length, selected.length - suffix.length);
|
|
view.dispatch({
|
|
changes: { from: selection.from, to: selection.to, insert: inner },
|
|
selection: { anchor: selection.from, head: selection.from + inner.length },
|
|
});
|
|
} else if (outerWrapped) {
|
|
const from = selection.from - prefix.length;
|
|
const to = selection.to + suffix.length;
|
|
view.dispatch({
|
|
changes: { from, to, insert: selected },
|
|
selection: { anchor: from, head: from + selected.length },
|
|
});
|
|
} else {
|
|
const body = selected || placeholder;
|
|
view.dispatch({
|
|
changes: { from: selection.from, to: selection.to, insert: prefix + body + suffix },
|
|
selection: {
|
|
anchor: selection.from + prefix.length,
|
|
head: selection.from + prefix.length + body.length,
|
|
},
|
|
});
|
|
}
|
|
view.focus();
|
|
}
|
|
|
|
export function prefixLines(
|
|
view: EditorView | null,
|
|
prefix: string,
|
|
placeholder = "",
|
|
) {
|
|
if (!view) return;
|
|
const selection = view.state.selection.main;
|
|
const startLine = view.state.doc.lineAt(selection.from);
|
|
const endLine = view.state.doc.lineAt(selection.to);
|
|
|
|
if (selection.empty && startLine.text.trim() === "") {
|
|
insertText(view, prefix + placeholder);
|
|
return;
|
|
}
|
|
|
|
const changes = [];
|
|
for (let number = startLine.number; number <= endLine.number; number += 1) {
|
|
const line = view.state.doc.line(number);
|
|
if (line.text.startsWith(prefix)) continue;
|
|
changes.push({ from: line.from, insert: prefix });
|
|
}
|
|
|
|
view.dispatch({ changes });
|
|
view.focus();
|
|
}
|
|
|
|
export function undoEdit(view: EditorView | null) {
|
|
if (!view) return;
|
|
undo(view);
|
|
view.focus();
|
|
}
|
|
|
|
export function redoEdit(view: EditorView | null) {
|
|
if (!view) return;
|
|
redo(view);
|
|
view.focus();
|
|
}
|
|
|
|
export function setTypstConfig(
|
|
view: EditorView | null,
|
|
setting: string,
|
|
property: string,
|
|
value: string,
|
|
) {
|
|
if (!view) return;
|
|
|
|
const content = view.state.doc.toString();
|
|
const rule = new RegExp(`^#set\\s+${setting}\\s*\\(([^)]*)\\)`, "m");
|
|
const match = content.match(rule);
|
|
|
|
if (!match || match.index === undefined) {
|
|
view.dispatch({
|
|
changes: { from: 0, insert: `#set ${setting}(${property}: ${value})\n` },
|
|
});
|
|
view.focus();
|
|
return;
|
|
}
|
|
|
|
const existing = match[1];
|
|
const property_rule = new RegExp(
|
|
`${property}\\s*:\\s*(?:\\([^)]*\\)|"[^"]*"|[^,)]+)`,
|
|
);
|
|
|
|
const next = property_rule.test(existing)
|
|
? existing.replace(property_rule, `${property}: ${value}`)
|
|
: existing.trim()
|
|
? `${existing}, ${property}: ${value}`
|
|
: `${property}: ${value}`;
|
|
|
|
view.dispatch({
|
|
changes: {
|
|
from: match.index,
|
|
to: match.index + match[0].length,
|
|
insert: `#set ${setting}(${next})`,
|
|
},
|
|
});
|
|
view.focus();
|
|
}
|