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 = ( -
(ref = r)} class={style.lintError}> -
-
🔥 {error.message.split('\n')[0]}
-
- ); - render(errorLine, this.scratch); - this.errors = [this.editor.addLineWidget(error.loc.line - 1, ref)]; - }); - }, 1000); - } + useEffect(() => { + if (props.slug || !editor.current) routeHasChanged.current = true; + }, [props.slug]); - componentDidMount() { - let { spaces, value, tabSize } = this.props; + useEffect(() => { + if (routeHasChanged.current === false) return; + routeHasChanged.current = false; - this.editor = codemirror(this.base, { - value: String(value || ''), - mode: 'jsx', - theme: 'one-dark', - lineNumbers: true, - indentWithTabs: !spaces, - tabSize: tabSize || 2, - indentUnit: spaces ? Math.round(spaces) || 2 : false, - showCursorWhenSelecting: true, - extraKeys: { - 'Cmd-/': 'toggleComment' - } - }); + if (editor.current) { + editor.current.dispatch({ + changes: { from: 0, to: editor.current.state.doc.length, insert: props.value } + }); + return; + } - this.editor.on('change', () => { - if (this.events === false) return; + const theme = EditorView.theme({}, { dark: true }); - this.value = this.editor.getValue(); - let { onInput } = this.props; - if (onInput) onInput(this.value); + const state = EditorState.create({ + doc: props.value, + extensions: [ + lineNumbers(), + highlightActiveLine(), + highlightActiveLineGutter(), + history(), + indentUnit.of('\t'), + closeBrackets(), + bracketMatching(), + autocompletion(), + javascript({ jsx: true }), + keymap.of([indentWithTab, ...defaultKeymap, ...historyKeymap]), + [theme, syntaxHighlighting(highlightStyle, { fallback: true })], + EditorView.updateListener.of(update => { + // Ignores changes from swapping out the editor code programmatically + if (isViewUpdateFromUserInput(update)) { + props.onInput(update.state.doc.toString()); + } + }) + ] }); - } - componentWillReceiveProps({ value, error }) { - let current = this.hasOwnProperty('value') ? this.value : this.props.value; - if (value !== current) { - let e = this.events; - this.events = false; - this.value = value; - this.editor.setValue(value); - this.showError(null); - setTimeout(() => this.editor.refresh(), 1); - this.events = e; - } + editor.current = new EditorView({ + state, + parent: editorParent.current + }); + }, [props.value]); - if (error !== this.props.error) { - this.showError(error); + useEffect(() => ( + () => { + if (editor.current) editor.current.destroy(); } - } - - shouldComponentUpdate() { - return false; - } + ), []); - componentWillUnmount() { - let wrapper = this.editor && this.editor.getWrapperElement(); - if (wrapper) this.base.removeChild(wrapper); - this.editor = null; - } + return
; +} - render({ value, onInput, children, ...props }) { - return
; +/** @param {import('@codemirror/view').ViewUpdate} viewUpdate */ +function isViewUpdateFromUserInput(viewUpdate) { + if (viewUpdate.docChanged) { + for (const transaction of viewUpdate.transactions) { + if (transaction.annotation(Transaction.userEvent)) return true; + } } + return false; } diff --git a/src/components/controllers/repl/index.jsx b/src/components/controllers/repl/index.jsx index 72109fda1..bc561cf95 100644 --- a/src/components/controllers/repl/index.jsx +++ b/src/components/controllers/repl/index.jsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from 'preact/hooks'; +import { useState, useEffect } from 'preact/hooks'; import { useLocation, useRoute } from 'preact-iso'; import { Splitter } from '../../splitter'; import { textToBase64 } from './query-encode.js'; -import { EXAMPLES, fetchExample } from './examples'; import { ErrorOverlay } from './error-overlay'; +import { EXAMPLES, fetchExample } from './examples'; import { useStoredValue } from '../../../lib/localstorage'; import { useResource } from '../../../lib/use-resource'; import { parseStackTrace } from './errors'; @@ -23,12 +23,11 @@ export function Repl({ code }) { const [error, setError] = useState(null); const [copied, setCopied] = useState(false); - // TODO: CodeMirror v5 cannot load in Node, and loading only the runner - // causes some bad jumping/pop-in. For the moment, this is the best option + // TODO: Needs some work for prerendering to not cause pop-in if (typeof window === 'undefined') return null; /** - * @type {{ Runner: import('../repl/runner').default, CodeEditor: import('../../code-editor').default }} + * @type {{ Runner: import('./runner').default, CodeEditor: import('../../code-editor').default }} */ const { Runner, CodeEditor } = useResource(() => Promise.all([ import('../../code-editor'), @@ -144,6 +143,7 @@ export function Repl({ code }) { class={style.code} value={editorCode} error={error} + slug={query.example} onInput={onEditorInput} /> diff --git a/src/components/controllers/tutorial/index.jsx b/src/components/controllers/tutorial/index.jsx index 1b9ffe09a..3254853b7 100644 --- a/src/components/controllers/tutorial/index.jsx +++ b/src/components/controllers/tutorial/index.jsx @@ -59,8 +59,7 @@ export function Tutorial({ html, meta }) { const hasCode = meta.code !== false; const showCode = showCodeOverride && hasCode; - // TODO: CodeMirror v5 cannot load in Node, and loading only the runner - // causes some bad jumping/pop-in. For the moment, this is the best option + // TODO: Needs some work for prerendering to not cause pop-in if (typeof window === 'undefined') return null; /** @@ -184,15 +183,16 @@ export function Tutorial({ html, meta }) { } > -
- -
- +
+ +
+ } >