Upgrade codemirror to v6 (#18524)

Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
Konv Suuandfisker authored and GitHub committed 2025-12-30 03:11:04 +08:00
1 parent 1ce53af15b
commit b261f60dcf
12 files changed
+1598 -1224

No files matched your search

+8 -2
View File
@@ -74,13 +74,17 @@
groupName: "GitHub Actions",
matchManagers: ["github-actions"],
},
{
groupName: "Markdown dependencies",
matchPackageNames: ["micromark-*", "mdast-", "remark-", "unified"],
},
{
groupName: "Docusaurus",
matchPackageNames: ["@docusaurus/*"],
},
{
groupName: "Markdown dependencies",
matchPackageNames: ["micromark-*", "mdast-", "remark-", "unified"],
groupName: "CodeMirror",
matchPackageNames: ["@codemirror/*", "cm6-graphql"],
},
],
ignoreDeps: [
@@ -90,6 +94,8 @@
"prettier",
// Old versions are required to test
"node",
// Only pinned version works, https://github.com/prettier/prettier/pull/18524#issuecomment-3694806317
"@codemirror/view",
],
ignorePaths: ["tests/**", "packages/**"],
}
+3
View File
@@ -40,6 +40,7 @@
"Burgener",
"callees",
"camelified",
"catppuccin",
"Chedeau",
"cherow",
"Cheung",
@@ -142,10 +143,12 @@
"kddnewton",
"Kearney",
"Kempson",
"Keymap",
"Khatri",
"Konstantin",
"l’objectif",
"layed",
"lezer",
"libdef",
"lintstaged",
"lintstagedrc",
-118
View File
@@ -26,124 +26,6 @@
<link rel="icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/icon.png" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/codemirror.min.css"
integrity="sha512-uf06llspW44/LZpHzHT6qBOIVODjWtv4MxCricRxkzvopAlSWnTf6hpZTFxuuZcuNE9CBQhqE0Seu1CoRk84nQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldgutter.min.css"
integrity="sha512-YwkMTlTHn8dBnwa47IF+cKsS00HPiiVhQ4DpwT1KF2gUftfFR7aefepabSPLAs6zrMyD89M3w0Ow6mQ5XJEUCw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/codemirror.min.js"
integrity="sha512-6cPYokihlrofMNApz7OXVQNObWjLiKGIBBb7+UB+AuMiRCLKmFKgrwms21sHq3bdFFZWpfHYRJBJvMFMPj1S9g=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/javascript/javascript.min.js"
integrity="sha512-Cbz+kvn+l5pi5HfXsEB/FYgZVKjGIhOgYNBwj4W2IHP2y8r3AdyDCQRnEUqIQ+6aJjygKPTyaNT2eIihaykJlw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/xml/xml.min.js"
integrity="sha512-LarNmzVokUmcA7aUDtqZ6oTS+YXmUKzpGdm8DxC46A6AHu+PQiYCUlwEGWidjVYMo/QXZMFMIadZtrkfApYp/g=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/jsx/jsx.min.js"
integrity="sha512-jlNQWdDsv1W4EG4blPl9HPp+5AXT/uozlC1HWLydcdG9gT8qM2PtWbk5sCZb8eZTX+lT/oHlXKn1SU6LXogFow=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/css/css.js"
integrity="sha512-kbf0gMc1+KPTTDWkjkGwf3h2Nx4djnWBVtcCKJcwLQpQ/TIMAjRBKM9f0ZDgj9kT5WTs1Qzsq+5Si2p7j9whkg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/markdown/markdown.min.js"
integrity="sha512-DmMao0nRIbyDjbaHc8fNd3kxGsZj9PCU6Iu/CeidLQT9Py8nYVA5n0PqXYmvqNdU+lCiTHOM/4E7bM/G8BttJg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/display/placeholder.min.js"
integrity="sha512-acBo6sW2h2GZQ9BqU9v5RyYGPUEr1a9jrukJg825Y0ahxAg/7aqTNPtcalloqnf4DfsRVdcdNmcBNWPD8b8W8Q=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/display/rulers.min.js"
integrity="sha512-sF+AF3zpVOHMt4f4mN1dVS2/Hon6pr/7h1gc7rDHtga0SeD9z0UiL1J0k3umDFEz5qkY06ARcNzi45NkJ9GsYw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/search/searchcursor.min.js"
integrity="sha512-+ZfZDC9gi1y9Xoxi9UUsSp+5k+AcFE0TRNjI0pfaAHQ7VZTaaoEpBZp9q9OvHdSomOze/7s5w27rcsYpT6xU6g=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/edit/matchbrackets.min.js"
integrity="sha512-GSYCbN/le5gNmfAWVEjg1tKnOH7ilK6xCLgA7c48IReoIR2g2vldxTM6kZlN6o3VtWIe6fHu/qhwxIt11J8EBA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/edit/closebrackets.min.js"
integrity="sha512-tsjcYO5hFvViRssxiM7Jhd8601epWOx1He3Hl4yuI5dKKPxr43KxkOhc9GZeeqzlYJm9ABb7UPA9697NiqZZ7Q=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/comment/comment.min.js"
integrity="sha512-UaJ8Lcadz5cc5mkWmdU8cJr0wMn7d8AZX5A24IqVGUd1MZzPJTq9dLLW6I102iljTcdB39YvVcCgBhM0raGAZQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/wrap/hardwrap.min.js"
integrity="sha512-ZPPL1o5pyZOIF/+PL/pRRBdxrYENhEbcNas/6PJcXMM1aBu5wI2yeFzJY6Jv+fJjH8o48HBYomInIYsYvFlXig=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldcode.min.js"
integrity="sha512-Q2qfEJEU257Qlqc4/5g6iKuJNnn5L0xu2D48p8WHe9YC/kLj2UfkdGD01qfxWk+XIcHsZngcA8WuKcizF8MAHA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldgutter.js"
integrity="sha512-tGjjnhNOLvNMtP2S/fpsalcaJofEksE83moo23We+afRBd1kx3JtSSDn9CDmEyKhwk4N7dJqLq3iUWyVOs9ejQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/brace-fold.min.js"
integrity="sha512-5MuaB1PVXvhsYVG0Ozb0bwauN7/D1VU4P8dwo5E/xiB9SXY+VSEhIyxt1ggYk2xaB/RKqKL7rPXpm1o1IlTQDA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/keymap/sublime.min.js"
integrity="sha512-SV3qeFFtzcmGtUQPLM7HLy/7GKJ/x3c2PdiF5GZQnbHzIlI2q7r77y0IgLLbBDeHiNfCSBYDQt898Xp0tcZOeA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"
integrity="sha512-7O5pXpc0oCRrxk8RUfDYFgn0nO1t+jLuIOQdOMRp4APB7uZ4vSjspzp5y6YDtDs4VzUSTbWzBFZ/LKJhnyFOKw=="
+17 -1
View File
@@ -15,12 +15,24 @@
"update-stable-docs": "node ./scripts/update-stable-docs.mjs"
},
"dependencies": {
"@catppuccin/codemirror": "1.0.1",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.1",
"@codemirror/lang-css": "6.3.1",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-markdown": "6.5.0",
"@codemirror/language": "6.12.1",
"@codemirror/lint": "6.9.2",
"@codemirror/state": "6.5.3",
"@codemirror/view": "6.39.4",
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@lezer/highlight": "1.2.3",
"@mdx-js/react": "3.1.1",
"@sandhose/prettier-animated-logo": "1.0.3",
"@types/react": "19.2.7",
"clsx": "2.1.1",
"codemirror-graphql": "2.2.4",
"cm6-graphql": "0.2.1",
"graphql": "16.12.0",
"prism-react-renderer": "2.4.1",
"react": "19.2.1",
@@ -36,9 +48,13 @@
"@vitejs/plugin-vue": "6.0.2",
"@vitejs/plugin-vue-jsx": "5.1.2",
"concurrently": "9.2.1",
"rollup-plugin-visualizer": "6.0.5",
"vite": "7.2.7",
"yaml": "2.8.2"
},
"resolutions": {
"@codemirror/view": "6.39.4"
},
"browserslist": {
"production": [
">0.5%",
+31 -18
View File
@@ -13,8 +13,6 @@ import SidebarOptions from "./sidebar/SidebarOptions.jsx";
import * as urlHash from "./urlHash.js";
import {
buildCliArgs,
convertOffsetToSelection,
convertSelectionToRange,
getAstAutoFold,
getCodemirrorMode,
getDefaults,
@@ -114,16 +112,21 @@ function setup(props) {
const setSelection = (selection) => {
Object.assign(state, { selection });
if (state.trackCursorOffset) {
handleOptionValueChange(
cursorOffsetOption,
convertSelectionToRange(selection, state.content)[0],
);
handleOptionValueChange(cursorOffsetOption, selection?.anchor ?? -1);
}
};
const setSelectionAsRange = () => {
const { selection, content, options } = state;
const [rangeStart, rangeEnd] = convertSelectionToRange(selection, content);
const { selection, options } = state;
if (!selection) {
return;
}
const anchor = selection.anchor ?? 0;
const head = selection.head ?? anchor;
const rangeStart = Math.min(anchor, head);
const rangeEnd = Math.max(anchor, head);
const updatedOptions = { ...options, rangeStart, rangeEnd };
if (rangeStart === rangeEnd) {
delete updatedOptions.rangeStart;
@@ -194,7 +197,7 @@ function setup(props) {
return props.worker
.format(content, {
parser: "__js_expression",
cursorOffset: convertSelectionToRange(selection, content)[0],
cursorOffset: selection?.anchor ?? -1,
})
.then(({ error, formatted, cursorOffset }) => {
if (error) {
@@ -203,24 +206,34 @@ function setup(props) {
Object.assign(state, {
content: formatted,
selection: convertOffsetToSelection(cursorOffset, formatted),
selection:
cursorOffset >= 0
? { anchor: cursorOffset, head: cursorOffset }
: {},
});
});
};
const insertDummyId = () => {
const { content, selection } = state;
if (!selection) {
return;
}
const anchor = selection.anchor ?? 0;
const head = selection.head ?? anchor;
const start = Math.min(anchor, head);
const end = Math.max(anchor, head);
const dummyId = generateDummyId();
const range = convertSelectionToRange(selection, content);
const modifiedContent =
content.slice(0, range[0]) + dummyId + content.slice(range[1]);
content.slice(0, start) + dummyId + content.slice(end);
Object.assign(state, {
content: modifiedContent,
selection: convertOffsetToSelection(
range[0] + dummyId.length,
modifiedContent,
),
selection: {
anchor: start + dummyId.length,
head: start + dummyId.length,
},
});
};
@@ -417,8 +430,8 @@ function setup(props) {
onChange={setContent}
onSelectionChange={setSelection}
extraKeys={{
"Shift-Alt-F": formatInput,
"Ctrl-Q": insertDummyId,
"Shift-Alt-f": formatInput,
"Ctrl-q": insertDummyId,
}}
foldGutter={options.parser === "doc-explorer"}
/>
@@ -0,0 +1,32 @@
import { ref } from "vue";
const getInitialTheme = () => {
const saved = localStorage.getItem("theme");
if (saved) {
return saved;
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
const applyTheme = (value = getInitialTheme()) => {
document.documentElement.dataset.theme = value;
theme.value = value;
};
const toggleTheme = () => {
const value = theme.value === "dark" ? "light" : "dark";
localStorage.setItem("theme", value);
applyTheme(value);
};
const theme = ref(undefined);
applyTheme();
function useTheme() {
return { theme, toggleTheme };
}
export { useTheme };
+3 -31
View File
@@ -1,42 +1,14 @@
import "codemirror-graphql/cm6-legacy/mode.esm.js";
import "./install-service-worker.js";
import { createApp, onMounted, reactive, ref, watch } from "vue";
import { createApp, onMounted, reactive } from "vue";
import { useTheme } from "./composables/use-theme.js";
import Playground from "./Playground.jsx";
import { fixPrettierVersion } from "./utilities.js";
import VersionLink from "./VersionLink.jsx";
import WorkerApi from "./WorkerApi.js";
function getInitialTheme() {
const saved = localStorage.getItem("theme");
if (saved) {
return saved;
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
function applyTheme(theme) {
document.documentElement.dataset.theme = theme;
}
function ThemeToggle() {
const theme = ref(getInitialTheme());
const toggleTheme = () => {
const newTheme = theme.value === "dark" ? "light" : "dark";
theme.value = newTheme;
localStorage.setItem("theme", newTheme);
applyTheme(newTheme);
};
applyTheme(theme.value);
watch(theme, (newTheme) => {
applyTheme(newTheme);
});
const { theme, toggleTheme } = useTheme();
return (
<button
+343 -153
View File
@@ -1,17 +1,125 @@
import { catppuccinLatte, catppuccinMocha } from "@catppuccin/codemirror";
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import {
defaultKeymap,
history,
historyKeymap,
indentWithTab,
} from "@codemirror/commands";
import {
bracketMatching,
foldable,
foldEffect,
foldGutter as foldGutterExt,
foldKeymap,
} from "@codemirror/language";
import {
Compartment,
EditorState,
StateEffect,
StateField,
} from "@codemirror/state";
import {
Decoration,
EditorView,
keymap,
lineNumbers as lineNumbersExt,
} from "@codemirror/view";
import { onMounted, onUnmounted, useTemplateRef, watch } from "vue";
const { CodeMirror } = window;
import { useTheme } from "./composables/use-theme.js";
function setup(props, { emit }) {
const textareaRef = useTemplateRef("textarea");
const editorRef = useTemplateRef("editorRef");
let _codeMirror = null;
let _cached = "";
let _overlay = null;
const _overlay = StateEffect.define();
const overlayField = StateField.define({
create() {
return Decoration.none;
},
update(decorations, tr) {
for (const effect of tr.effects) {
if (effect.is(_overlay)) {
let { start, end } = effect.value;
if (start !== undefined && end !== undefined) {
if (end < start) {
[start, end] = [end, start];
}
const { length } = _codeMirror.state.doc;
start = Math.min(Math.max(start, 0), length);
end = Math.min(Math.max(end, 0), length);
if (start !== end) {
return Decoration.set([overlayMark.range(start, end)]);
}
}
return Decoration.none;
}
}
return decorations.map(tr.changes);
},
provide: (f) => EditorView.decorations.from(f),
});
const overlayMark = Decoration.mark({
class: "cm-overlay-highlight",
});
const languageExt = new Compartment();
const rulerTheme = new Compartment();
const foldGutterCompartment = new Compartment();
const { theme } = useTheme();
const editorTheme = new Compartment();
const onChange = (value) => {
emit("change", value);
};
const componentDidMount = () => {
const createRulerTheme = (printWidth, color) => {
if (!printWidth) {
return EditorView.theme({});
}
return EditorView.theme({
".cm-content::before": {
content: '""',
position: "absolute",
top: "0",
bottom: "0",
left: `${printWidth}ch`,
width: "1px",
backgroundColor: color || "var(--color-gray-300)",
pointerEvents: "none",
zIndex: "1",
marginLeft: "6px",
},
});
};
async function getLanguageExtension(mode) {
switch (mode) {
case "css": {
const { css } = await import("@codemirror/lang-css");
return css();
}
case "graphql": {
const { graphql } = await import("cm6-graphql");
return graphql();
}
case "markdown": {
const { markdown } = await import("@codemirror/lang-markdown");
return markdown();
}
default: {
const { javascript } = await import("@codemirror/lang-javascript");
return javascript({ jsx: true, typescript: true });
}
}
}
const componentDidMount = async () => {
const options = { ...props };
for (const property of [
"ruler",
@@ -29,123 +137,274 @@ function setup(props, { emit }) {
}
}
options.rulers = [makeRuler(props)];
options.gutters = makeGutters(props);
const state = EditorState.create({
doc: props.value || "",
extensions: [
props.lineNumbers ? lineNumbersExt() : undefined,
foldGutterCompartment.of(
props.foldGutter
? foldGutterExt({
openText: "▾",
closedText: "▸",
})
: [],
),
props.autoCloseBrackets ? closeBrackets() : undefined,
props.matchBrackets ? bracketMatching() : undefined,
options.rulers = [makeRuler(props)];
options.gutters = makeGutters(props);
languageExt.of(await getLanguageExtension(props.mode)),
editorTheme.of(
theme.value === "dark" ? catppuccinMocha : catppuccinLatte,
),
overlayField,
history(),
keymap.of([
...defaultKeymap,
...closeBracketsKeymap,
...historyKeymap,
...foldKeymap,
indentWithTab,
{ key: "Ctrl-l", run: () => false },
...(props.extraKeys
? Object.entries(props.extraKeys).map(([key, cmd]) => ({
key,
run: cmd,
}))
: []),
]),
_codeMirror = CodeMirror.fromTextArea(textareaRef.value, options);
_codeMirror.on("change", handleChange);
_codeMirror.on("focus", handleFocus);
_codeMirror.on("beforeSelectionChange", handleSelectionChange);
EditorState.tabSize.of(props.tabSize ?? 4),
EditorState.readOnly.of(props.readOnly ?? false),
window.CodeMirror.keyMap.pcSublime["Ctrl-L"] = false;
window.CodeMirror.keyMap.sublime["Ctrl-L"] = false;
rulerTheme.of(createRulerTheme(props.ruler, props.rulerColor)),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const value = update.state.doc.toString();
if (value !== _cached) {
handleChange(value);
}
}
if (update.selectionSet) {
const selection = update.state.selection.main;
handleSelectionChange(selection);
}
}),
].filter(Boolean),
});
_codeMirror = new EditorView({
state,
parent: editorRef.value,
});
updateValue(props.value || "");
updateSelection();
updateOverlay();
};
const componentWillUnmount = () => {
_codeMirror?.toTextArea();
};
const componentDidUpdate = (_, prevProps) => {
if (!_codeMirror) {
return;
}
if (props.value !== _cached) {
updateValue(props.value);
}
if (
!isEqualSelection(props.selection, prevProps.selection) &&
!isEqualSelection(props.selection, _codeMirror.listSelections()[0])
) {
updateSelection();
}
if (
props.overlayStart !== prevProps.overlayStart ||
props.overlayEnd !== prevProps.overlayEnd
) {
updateOverlay();
}
if (props.mode !== prevProps.mode) {
_codeMirror.setOption("mode", props.mode);
}
if (props.ruler !== prevProps.ruler) {
_codeMirror.setOption("rulers", [makeRuler(props)]);
}
if (props.foldGutter !== prevProps.foldGutter) {
_codeMirror.setOption("gutters", makeGutters(props));
}
_codeMirror?.destroy();
};
const updateValue = (value) => {
if (_cached === value) {
return;
}
_cached = value;
_codeMirror.setValue(value);
_codeMirror.dispatch({
changes: {
from: 0,
to: _codeMirror.state.doc.length,
insert: value,
},
});
if (props.autoFold instanceof RegExp) {
const lines = value.split("\n");
// going backwards to prevent unfolding folds created earlier
for (let i = lines.length - 1; i >= 0; i--) {
if (props.autoFold.test(lines[i])) {
_codeMirror.foldCode(i);
if (!(props.autoFold instanceof RegExp)) {
return;
}
const lines = value.split("\n");
const effects = [];
for (let i = lines.length - 1; i >= 0; i--) {
if (props.autoFold.test(lines[i])) {
const lineNumber = i + 1;
const range = foldable(
_codeMirror.state,
_codeMirror.state.doc.line(lineNumber).from,
_codeMirror.state.doc.line(lineNumber).to,
);
if (range) {
effects.push(foldEffect.of(range));
}
}
}
if (effects.length > 0) {
_codeMirror.dispatch({ effects });
}
};
const updateSelection = () => {
_codeMirror.setSelection(
props.selection?.anchor ?? { line: 0, ch: 0 },
props.selection?.head,
);
if (!props.selection || !_codeMirror) {
return;
}
const { doc } = _codeMirror.state;
const anchor = Math.max(props.selection.anchor ?? 0, 0);
const head = Math.min(props.selection.head ?? anchor, doc.length);
const currentSelection = _codeMirror.state.selection.main;
if (currentSelection.anchor !== anchor || currentSelection.head !== head) {
_codeMirror.dispatch({
selection: { anchor, head },
});
}
};
const updateOverlay = () => {
if (_overlay) {
_codeMirror.removeOverlay(_overlay);
}
if (props.overlayStart !== undefined) {
const [start, end] = getIndexPosition(props.value, [
props.overlayStart,
props.overlayEnd,
]);
_overlay = createOverlay(start, end);
_codeMirror.addOverlay(_overlay);
if (!_codeMirror) {
return;
}
_codeMirror.dispatch({
effects: _overlay.of({
start: props.overlayStart,
end: props.overlayEnd,
}),
});
};
const handleFocus = (/* codeMirror, event */) => {
if (_codeMirror.getValue() === props.codeSample) {
_codeMirror.execCommand("selectAll");
}
const handleChange = (value) => {
_cached = value;
onChange(value);
updateOverlay();
};
const handleChange = (doc, change) => {
if (change.origin !== "setValue") {
_cached = doc.getValue();
onChange(_cached);
updateOverlay();
const handleSelectionChange = (selection) => {
if (!props.onSelectionChange || !selection) {
return;
}
};
const handleSelectionChange = (doc, change) => {
props.onSelectionChange?.(change.ranges[0]);
props.onSelectionChange({
anchor: selection.anchor,
head: selection.head,
});
};
const render = () => (
<div class="editor input">
<textarea ref="textarea" />
<div ref="editorRef" style={{ position: "absolute", inset: 0 }} />
</div>
);
onMounted(componentDidMount);
onUnmounted(componentWillUnmount);
watch(() => props, componentDidUpdate, { deep: true });
watch(
() => props.value,
(value) => {
if (!_codeMirror) {
return;
}
if (value !== _cached) {
updateValue(value);
}
},
);
watch(
() => props.mode,
async (mode) => {
if (!_codeMirror) {
return;
}
_codeMirror.dispatch({
effects: languageExt.reconfigure(await getLanguageExtension(mode)),
});
},
);
watch(
() => props.ruler,
(ruler) => {
if (!_codeMirror) {
return;
}
_codeMirror.dispatch({
effects: rulerTheme.reconfigure(
createRulerTheme(ruler, props.rulerColor),
),
});
},
);
watch(
() => props.rulerColor,
(rulerColor) => {
if (!_codeMirror) {
return;
}
_codeMirror.dispatch({
effects: rulerTheme.reconfigure(
createRulerTheme(props.ruler, rulerColor),
),
});
},
);
watch(
() => props.selection,
() => {
if (!_codeMirror) {
return;
}
updateSelection();
},
{ deep: true },
);
watch(
() => [props.overlayStart, props.overlayEnd],
() => {
if (!_codeMirror) {
return;
}
updateOverlay();
},
);
watch(
() => props.foldGutter,
(foldGutter) => {
if (!_codeMirror) {
return;
}
_codeMirror.dispatch({
effects: foldGutterCompartment.reconfigure(
foldGutter ? foldGutterExt() : [],
),
});
},
);
watch(theme, (newTheme) => {
_codeMirror.dispatch({
effects: editorTheme.reconfigure(
newTheme === "dark" ? catppuccinMocha : catppuccinLatte,
),
});
});
return render;
}
@@ -170,81 +429,12 @@ const CodeMirrorPanel = {
showCursorWhenSelecting: { type: Boolean, default: undefined },
tabSize: { type: Number, default: undefined },
readOnly: { type: Boolean, default: undefined },
extraKeys: { type: Object, default: undefined },
},
emits: ["change"],
setup,
};
function getIndexPosition(text, indexes) {
indexes = [...indexes];
let line = 0;
let count = 0;
let lineStart = 0;
const result = [];
while (indexes.length > 0) {
const index = indexes.shift();
while (count < index && count < text.length) {
if (text[count] === "\n") {
line++;
lineStart = count + 1;
}
count++;
}
result.push({ line, pos: count - lineStart });
}
return result;
}
function createOverlay(start, end) {
return {
token(stream) {
const { line } = stream.lineOracle;
if (line < start.line || line > end.line) {
stream.skipToEnd();
} else if (line === start.line && stream.pos < start.pos) {
stream.pos = start.pos;
} else if (line === end.line) {
if (stream.pos < end.pos) {
stream.pos = end.pos;
return "searching";
}
stream.skipToEnd();
} else {
stream.skipToEnd();
return "searching";
}
},
};
}
function makeRuler(props) {
return { column: props.ruler, color: props.rulerColor };
}
function makeGutters(props) {
return props.foldGutter
? ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
: [];
}
function isEqualSelection(selection1, selection2) {
const anchor1 = selection1?.anchor ?? { line: 0, ch: 0 };
const head1 = selection1?.head ?? anchor1;
const anchor2 = selection2?.anchor ?? { line: 0, ch: 0 };
const head2 = selection2?.head ?? anchor2;
return (
head1.line === head2.line &&
head1.ch === head2.ch &&
anchor1.line === anchor2.line &&
anchor1.ch === anchor2.ch
);
}
export function InputPanel(props) {
return (
<CodeMirrorPanel
+52 -88
View File
@@ -5,6 +5,7 @@
html {
background-color: light-dark(#fafafa, #1b1b1d);
--background-color: light-dark(#fafafa, #1b1b1d);
color: light-dark(#6a6a6a, #e3e3e3);
--color-border: light-dark(#ddd, #606770);
color-scheme: light dark;
@@ -169,94 +170,6 @@ header h1 {
opacity: 0;
}
.CodeMirror {
height: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
line-height: 1.6;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
&:is([data-theme="dark"] *) {
/* Credit: Chris Kempson, Gerard Braad */
background: #000000;
color: #eaeaea;
div.CodeMirror-selected {
background: #424242;
}
.CodeMirror-gutters {
background: #000000;
border-right: 0px;
}
.CodeMirror-guttermarker {
color: #e78c45;
}
.CodeMirror-guttermarker-subtle {
color: #777;
}
.CodeMirror-linenumber {
color: #424242;
}
.CodeMirror-cursor {
border-left: 1px solid #6a6a6a;
}
span.cm-comment {
color: #d27b53;
}
span.cm-atom {
color: #a16a94;
}
span.cm-number {
color: #a16a94;
}
span.cm-property,
span.cm-attribute {
color: #99cc99;
}
span.cm-keyword {
color: #d54e53;
}
span.cm-string {
color: #e7c547;
}
span.cm-variable {
color: #b9ca4a;
}
span.cm-variable-2 {
color: #7aa6da;
}
span.cm-def {
color: #e78c45;
}
span.cm-bracket {
color: #eaeaea;
}
span.cm-tag {
color: #d54e53;
}
span.cm-link {
color: #a16a94;
}
span.cm-error {
background: #d54e53;
color: #6a6a6a;
}
.CodeMirror-activeline-background {
background: #2a2a2a;
}
.CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
}
}
.bottom-bar {
position: relative;
}
@@ -418,3 +331,54 @@ input[type="number"] {
border-bottom: none;
border-top-color: #000;
}
.cm-editor {
height: 100%;
background-color: var(--background-color) !important;
}
.cm-editor .cm-scroller {
font-family:
Consolas,
Monaco,
Andale Mono,
Ubuntu Mono,
monospace;
line-height: 1.6;
}
.cm-editor.cm-focused {
outline: none;
}
.cm-content {
position: relative;
}
.cm-editor .cm-gutters {
border-right: 1px solid var(--color-border);
background: #f7f7f7;
}
.cm-gutter {
padding-inline-start: 5px;
padding-inline-end: 3px;
}
.cm-overlay-highlight {
background-color: rgba(255, 220, 120, 0.45);
}
.cm-foldGutter .cm-gutterElement {
cursor: pointer;
}
html[data-theme="dark"] .cm-overlay-highlight {
background-color: rgba(255, 220, 120, 0.32);
}
html[data-theme="dark"] .cm-gutters {
background-color: #121212;
border-right-color: transparent;
color: #424242;
}
-24
View File
@@ -96,27 +96,3 @@ export function getAstAutoFold(parser) {
return astAutoFold[parser];
}
}
export function convertSelectionToRange({ head, anchor }, content) {
const lines = content.split("\n");
return [head, anchor]
.map(
({ ch, line }) =>
lines.slice(0, line).join("\n").length + ch + (line ? 1 : 0),
)
.sort((a, b) => a - b);
}
export function convertOffsetToSelection(offset, content) {
let line = 0;
let ch = 0;
for (let i = 0; i < offset && i <= content.length; i++) {
if (content[i] === "\n") {
line++;
ch = 0;
} else {
ch++;
}
}
return { anchor: { line, ch } };
}
+8 -2
View File
@@ -1,15 +1,21 @@
import vitePluginVue from "@vitejs/plugin-vue";
import vitePluginVueJsx from "@vitejs/plugin-vue-jsx";
import { visualizer as rollupPluginVisualizer } from "rollup-plugin-visualizer";
import { defineConfig } from "vite";
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const IS_CI = Boolean(process.env.CI);
// https://vite.dev/config/
export default defineConfig({
base: IS_PRODUCTION ? "/playground/" : undefined,
publicDir: IS_PRODUCTION ? undefined : "./static/",
plugins: [vitePluginVue(), vitePluginVueJsx()],
plugins: [
vitePluginVue(),
vitePluginVueJsx(),
IS_CI || !IS_PRODUCTION
? undefined
: rollupPluginVisualizer({ filename: "./static/playground/report.html" }),
].filter(Boolean),
build: {
outDir: "./static/playground/",
minify: IS_CI,
+1101 -787
View File
File diff suppressed because it is too large. Load diff