silverbullet/web/cm_plugins/smart_quotes.ts

117 lines
3.2 KiB
TypeScript
Raw Permalink Normal View History

2024-07-30 23:33:33 +08:00
import type { KeyBinding } from "@codemirror/view";
import { syntaxTree } from "@codemirror/language";
2024-06-23 11:33:16 +08:00
import { EditorSelection } from "@codemirror/state";
2024-07-30 23:33:33 +08:00
import type { Client } from "../client.ts";
2022-04-25 00:06:34 +08:00
const straightQuoteContexts = [
"CommentBlock",
2023-11-12 17:44:45 +08:00
"CodeBlock",
"FencedCode",
"InlineCode",
"FrontMatterCode",
2023-07-26 23:12:56 +08:00
"Attribute",
"CommandLink",
"TemplateDirective",
"LuaDirective",
];
// TODO: Add support for selection (put quotes around or create blockquote block?)
function keyBindingForQuote(
2024-06-23 11:33:16 +08:00
originalQuote: string,
left: string,
right: string,
): KeyBinding {
return {
2022-11-09 16:35:26 +08:00
any: (target, event): boolean => {
// Moving this check here rather than using the regular "key" property because
// for some reason the "ä" key is not recognized as a quote key by CodeMirror.
2024-06-23 11:33:16 +08:00
if (event.key !== originalQuote) {
2022-11-09 16:35:26 +08:00
return false;
}
const cursorPos = target.state.selection.main.from;
const chBefore = target.state.sliceDoc(cursorPos - 1, cursorPos);
2022-04-25 00:06:34 +08:00
// Figure out the context, if in some sort of code/comment fragment don't be smart
let node = syntaxTree(target.state).resolveInner(cursorPos);
while (node) {
if (straightQuoteContexts.includes(node.type.name)) {
return false;
}
if (node.parent) {
node = node.parent;
} else {
break;
}
}
// Ok, still here, let's use a smart quote
2024-06-23 11:33:16 +08:00
const changes = target.state.changeByRange((range) => {
if (!range.empty) {
return {
changes: [
{ insert: left, from: range.from },
{ insert: right, from: range.to },
],
range: EditorSelection.range(
range.anchor + left.length,
range.head + left.length,
),
};
} else {
const quote = (/\W/.exec(chBefore) && !/[!\?,\.\-=“]/.exec(chBefore))
? left
: right;
return {
changes: {
insert: quote,
from: cursorPos,
},
range: EditorSelection.cursor(
range.anchor + quote.length,
),
};
}
});
2024-06-23 11:33:16 +08:00
target.dispatch(changes);
return true;
},
};
}
2024-07-20 01:08:49 +08:00
export function createSmartQuoteKeyBindings(client: Client): KeyBinding[] {
// Also check the deprecated useSmartQuotes, default is true so either can disable
if (
client.config?.useSmartQuotes === false ||
client.config?.smartQuotes?.enabled === false
) {
2024-07-20 01:08:49 +08:00
return [];
}
let doubleLeft = "“";
let doubleRight = "”";
let singleLeft = "";
let singleRight = "";
const config = client.config?.smartQuotes;
if (config) {
if (typeof config.double?.left === "string") {
doubleLeft = config.double!.left;
}
if (typeof config.double?.right === "string") {
doubleRight = config.double!.right;
}
if (typeof config.single?.left === "string") {
singleLeft = config.single!.left;
}
if (typeof config.single?.right === "string") {
singleRight = config.single!.right;
}
}
2024-07-20 01:08:49 +08:00
return [
keyBindingForQuote('"', doubleLeft, doubleRight),
keyBindingForQuote("'", singleLeft, singleRight),
2024-07-20 01:08:49 +08:00
];
}