2024-07-30 23:33:33 +08:00
|
|
|
|
import type { KeyBinding } from "@codemirror/view";
|
2024-03-16 22:29:24 +08:00
|
|
|
|
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
|
|
|
|
|
2022-10-17 21:48:21 +08:00
|
|
|
|
const straightQuoteContexts = [
|
|
|
|
|
"CommentBlock",
|
2023-11-12 17:44:45 +08:00
|
|
|
|
"CodeBlock",
|
2022-10-17 21:48:21 +08:00
|
|
|
|
"FencedCode",
|
|
|
|
|
"InlineCode",
|
|
|
|
|
"FrontMatterCode",
|
2023-07-26 23:12:56 +08:00
|
|
|
|
"Attribute",
|
2024-01-02 21:47:02 +08:00
|
|
|
|
"CommandLink",
|
2024-01-05 03:08:12 +08:00
|
|
|
|
"TemplateDirective",
|
2024-10-05 21:37:36 +08:00
|
|
|
|
"LuaDirective",
|
2022-10-17 21:48:21 +08:00
|
|
|
|
];
|
2022-03-20 16:56:28 +08:00
|
|
|
|
|
|
|
|
|
// TODO: Add support for selection (put quotes around or create blockquote block?)
|
|
|
|
|
function keyBindingForQuote(
|
2024-06-23 11:33:16 +08:00
|
|
|
|
originalQuote: string,
|
2022-03-20 16:56:28 +08:00
|
|
|
|
left: string,
|
2022-10-10 20:50:21 +08:00
|
|
|
|
right: string,
|
2022-03-20 16:56:28 +08:00
|
|
|
|
): 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;
|
|
|
|
|
}
|
2022-10-17 21:48:21 +08:00
|
|
|
|
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,
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-03-20 16:56:28 +08:00
|
|
|
|
});
|
2024-06-23 11:33:16 +08:00
|
|
|
|
target.dispatch(changes);
|
|
|
|
|
|
2022-03-20 16:56:28 +08:00
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 01:08:49 +08:00
|
|
|
|
export function createSmartQuoteKeyBindings(client: Client): KeyBinding[] {
|
2024-10-18 04:34:47 +08:00
|
|
|
|
// 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 [];
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-18 04:34:47 +08:00
|
|
|
|
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 [
|
2024-10-18 04:34:47 +08:00
|
|
|
|
keyBindingForQuote('"', doubleLeft, doubleRight),
|
|
|
|
|
keyBindingForQuote("'", singleLeft, singleRight),
|
2024-07-20 01:08:49 +08:00
|
|
|
|
];
|
|
|
|
|
}
|