From 4ac8c9df12c6997d9ae076bd3672149393c9757f Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Thu, 4 Jul 2024 02:56:38 -0500 Subject: [PATCH] chore: Bump codemirror (#1148) * chore: Bump codemirror * revert: Bring back comment highlight color * refactor: Better editor mount/unmount setup * refactor: Fix editor mount conditional * chore: Temp, playing with mounting of editor * refactor: Swap effect for input handler * refactor: Finalize mount/unmount/update * revert: Provide errors to editor, though they're unusable --- package-lock.json | 249 +++++++++++++++++- package.json | 9 +- src/components/code-editor/code-mirror.css | 96 ++----- src/components/code-editor/index.jsx | 168 ++++++------ src/components/controllers/repl/index.jsx | 10 +- src/components/controllers/tutorial/index.jsx | 22 +- 6 files changed, 381 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index e707736bc..27022649d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,13 +7,20 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@codemirror/commands": "^6.6.0", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/language": "^6.10.2", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.1", "@docsearch/react": "^3.6.0", "@preact/preset-vite": "^2.8.2", "@preact/signals": "^1.1.3", "@preact/signals-core": "^1.2.3", "@rollup/browser": "^3.18.0", "@rollup/plugin-replace": "^5.0.5", - "codemirror": "^5.50.2", + "codemirror": "^6.0.1", "comlink": "^4.4.1", "decko": "^1.2.0", "htm": "^3.1.1", @@ -754,6 +761,133 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz", + "integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz", + "integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz", + "integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", + "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", + "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", + "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.0.tgz", + "integrity": "sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.28.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz", + "integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@docsearch/css": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", @@ -1253,6 +1387,74 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lezer/common": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.8.tgz", + "integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz", + "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", + "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.4.17", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.17.tgz", + "integrity": "sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz", + "integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@mdn/browser-compat-data": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-2.0.7.tgz", @@ -2493,9 +2695,30 @@ } }, "node_modules/codemirror": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.59.0.tgz", - "integrity": "sha512-UGzSkCacY9z0rSpQ3wnTWRN2nvRE6foDXnJltWW8pazInR/R+3gXHrao4IFQMv/bSBvFBxt8/HPpkpKAS54x5Q==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/codemirror/node_modules/@codemirror/search": { + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz", + "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } }, "node_modules/color-convert": { "version": "1.9.3", @@ -2590,6 +2813,12 @@ "dev": true, "hasInstallScript": true }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6294,6 +6523,12 @@ "node": ">=8" } }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -6840,6 +7075,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 2fab0cf2a..5d34da0df 100644 --- a/package.json +++ b/package.json @@ -67,13 +67,20 @@ "vite-plugin-static-copy": "1.0.2" }, "dependencies": { + "@codemirror/commands": "^6.6.0", + "@codemirror/lang-html": "^6.4.9", + "@codemirror/lang-javascript": "^6.2.2", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/language": "^6.10.2", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.28.1", "@docsearch/react": "^3.6.0", "@preact/preset-vite": "^2.8.2", "@preact/signals": "^1.1.3", "@preact/signals-core": "^1.2.3", "@rollup/browser": "^3.18.0", "@rollup/plugin-replace": "^5.0.5", - "codemirror": "^5.50.2", + "codemirror": "^6.0.1", "comlink": "^4.4.1", "decko": "^1.2.0", "htm": "^3.1.1", diff --git a/src/components/code-editor/code-mirror.css b/src/components/code-editor/code-mirror.css index 88d37ab87..bf4f40066 100644 --- a/src/components/code-editor/code-mirror.css +++ b/src/components/code-editor/code-mirror.css @@ -1,4 +1,4 @@ -.CodeMirror { +.cm-editor { position: absolute; left: 0; top: 0; @@ -28,113 +28,65 @@ pre, code, - .CodeMirror-linenumber { + .cm-lineNumbers { font-family: 'source-code-pro', Menlo, Consolas, Monaco, 'Andale Mono', 'Courier New', monospace; - line-height: 1.4; } - .CodeMirror-selected { + .cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, + .cm-selectionBackground, + .cm-content ::selection { background: #3e4451; } - .CodeMirror-line::selection, - .CodeMirror-line > span::selection, - .CodeMirror-line > span > span::selection { - background: rgba(73, 72, 62, 0.99); - } - - .CodeMirror-gutters { + .cm-gutters { background: #282c34; border-right: 1px solid #282c34; } - .CodeMirror-guttermarker { - color: white; - } - - .CodeMirror-guttermarker-subtle { - color: #d0d0d0; - } - - .CodeMirror-linenumber { + .cm-gutterElement { color: #8b93a3; } - .CodeMirror-cursor { - border-left: 1px solid #528bff; - } - - .cm-comment { - color: var(--color-code-comment); - } - - .cm-atom { - color: #64b6c3; - } - - .cm-number { - color: var(--color-code-symbol); - } - - .cm-property { - color: var(--color-code-function); - } - .cm-attribute { - color: var(--color-code-symbol); + .cm-content { + caret-color: #528bff; } .cm-keyword { color: var(--color-code-keyword); } - .cm-string, - .cm-string-2 { - color: var(--color-code-string); - } - - .cm-variable { - color: var(--color-code-symbol); - } - - .cm-variable-2 { - color: #c678dd; - } - - .cm-variable-3 { - color: #64b6c3; - } - - .cm-def { + .cm-function, + .cm-literal { color: var(--color-code-function); } - .cm-bracket, - .cm-bracket.cm-tag { - color: #abb2bf; + .cm-tag, + .cm-attribute { + color: var(--color-code-tag); } - .cm-tag { - color: var(--color-code-tag); + .cm-string { + color: var(--color-code-string); } - .cm-header { - color: #abb2bf; + .cm-operator { + color: var(--color-code-operator); } - .cm-link { - color: #98c379; + .cm-comment { + color: var(--color-code-comment); } - .cm-error { + .cm-invalid { border-bottom: 2px solid #c94b55; } - .CodeMirror-activeline-background { - background: #2c323b; + .cm-activeLine { + background: #1f232a; } - .CodeMirror-matchingbracket { + .cm-matchingBracket { text-decoration: underline; color: #abb2bf !important; } diff --git a/src/components/code-editor/index.jsx b/src/components/code-editor/index.jsx index 1cfd54df6..6d519fd48 100644 --- a/src/components/code-editor/index.jsx +++ b/src/components/code-editor/index.jsx @@ -1,99 +1,107 @@ -import { Component, render } from 'preact'; -import codemirror from 'codemirror'; +import { useRef, useEffect } from 'preact/hooks'; +import { EditorView } from 'codemirror'; +import { lineNumbers, keymap, highlightActiveLineGutter, highlightActiveLine } from '@codemirror/view'; +import { EditorState, Transaction } from '@codemirror/state'; +import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'; +import { javascript } from '@codemirror/lang-javascript'; +import { syntaxHighlighting, HighlightStyle, indentUnit, bracketMatching } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; +import { closeBrackets, autocompletion } from '@codemirror/autocomplete'; import cx from '../../lib/cx'; -import 'codemirror/mode/jsx/jsx'; -import 'codemirror/addon/comment/comment'; -import 'codemirror/lib/codemirror.css'; + import style from './style.module.css'; import './code-mirror.css'; -export default class CodeEditor extends Component { - scratch = document.createElement('div'); +// Custom theme that better matches our Prism config, though +// the lexer is somewhat limited so it still deviates +const highlightStyle = HighlightStyle.define([ + { tag: tags.keyword, class: 'cm-keyword' }, + { tag: [tags.definition(tags.function(tags.name)), tags.function(tags.name), tags.propertyName], class: 'cm-function' }, + { tag: tags.literal, class: 'cm-literal' }, + { tag: tags.tagName, class: 'cm-tag' }, + { tag: tags.attributeName, class: 'cm-attribute' }, + { tag: tags.string, class: 'cm-string' }, + { tag: [tags.operator], class: 'cm-operator' }, + { tag: tags.comment, class: 'cm-comment' }, + { tag: tags.invalid, class: 'cm-invalid' } +]); - showError(error) { - clearTimeout(this.showErrorTimer); - if (this.errors) { - this.editor.operation(() => { - this.errors.forEach(e => e.clear()); - }); - this.errors.length = 0; - } +/** + * @param {object} props + * @param {string} props.value + * @param {(value: string) => void} props.onInput + * @param {any} props.error - Unused at this time + * @param {string} props.slug + * @param {string} [props.class] + */ +export default function CodeEditor(props) { + const editorParent = useRef(null); + /** @type {{ current: EditorView | null }} */ + const editor = useRef(null); - if (!error || !error.loc) return; + const routeHasChanged = useRef(false); - this.showErrorTimer = setTimeout(() => { - this.editor.operation(() => { - let { left } = this.editor.cursorCoords( - { line: error.loc.line - 1, ch: error.loc.column - 1 }, - 'local' - ); - let ref; - const errorLine = ( -
▲-