diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index e29ae6d1..2094416e 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -1,11 +1,12 @@ -import { EditorState } from '@codemirror/state'; -import { EditorView, keymap, Decoration, lineNumbers, highlightActiveLineGutter } from '@codemirror/view'; import { defaultKeymap } from '@codemirror/commands'; -import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'; import { javascript } from '@codemirror/lang-javascript'; -import { StateField, StateEffect } from '@codemirror/state'; +import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'; +import { EditorState } from '@codemirror/state'; +import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view'; +import { Drawer, repl } from '@strudel.cycles/core'; +import { flashField, flash } from './flash.mjs'; +import { highlightExtension, highlightMiniLocations } from './highlight.mjs'; import { oneDark } from './themes/one-dark'; -import { repl, Drawer } from '@strudel.cycles/core'; // https://codemirror.net/docs/guide/ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) { @@ -15,7 +16,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the theme, javascript(), lineNumbers(), - highlightField, + highlightExtension, highlightActiveLineGutter(), syntaxHighlighting(defaultHighlightStyle), keymap.of(defaultKeymap), @@ -40,93 +41,6 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the }); } -// -// highlighting -// - -export const setHighlights = StateEffect.define(); -export const highlightField = StateField.define({ - create() { - return Decoration.none; - }, - update(highlights, tr) { - try { - for (let e of tr.effects) { - if (e.is(setHighlights)) { - const { haps } = e.value; - const marks = - haps - .map((hap) => - (hap.context.locations || []).map(({ start, end }) => { - // const color = hap.context.color || e.value.color || '#FFCA28'; - let from = tr.newDoc.line(start.line).from + start.column; - let to = tr.newDoc.line(end.line).from + end.column; - const l = tr.newDoc.length; - if (from > l || to > l) { - return; // dont mark outside of range, as it will throw an error - } - const mark = Decoration.mark({ - attributes: { style: `outline: 2px solid #FFCA28;` }, - }); - return mark.range(from, to); - }), - ) - .flat() - .filter(Boolean) || []; - highlights = Decoration.set(marks, true); - } - } - return highlights; - } catch (err) { - // console.warn('highlighting error', err); - return Decoration.set([]); - } - }, - provide: (f) => EditorView.decorations.from(f), -}); - -// helper to simply trigger highlighting for given haps -export function highlightHaps(view, haps) { - view.dispatch({ effects: setHighlights.of({ haps }) }); -} - -// -// flash -// - -export const setFlash = StateEffect.define(); -const flashField = StateField.define({ - create() { - return Decoration.none; - }, - update(flash, tr) { - try { - for (let e of tr.effects) { - if (e.is(setFlash)) { - if (e.value && tr.newDoc.length > 0) { - const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } }); - flash = Decoration.set([mark.range(0, tr.newDoc.length)]); - } else { - flash = Decoration.set([]); - } - } - } - return flash; - } catch (err) { - console.warn('flash error', err); - return flash; - } - }, - provide: (f) => EditorView.decorations.from(f), -}); - -export const flash = (view, ms = 200) => { - view.dispatch({ effects: setFlash.of(true) }); - setTimeout(() => { - view.dispatch({ effects: setFlash.of(false) }); - }, ms); -}; - export class StrudelMirror { constructor(options) { const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options; @@ -134,7 +48,7 @@ export class StrudelMirror { this.drawer = new Drawer((haps, time) => { const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped); - this.highlight(currentFrame); + this.highlight(currentFrame, time); onDraw?.(haps, time, currentFrame); }, drawTime); @@ -193,7 +107,7 @@ export class StrudelMirror { flash(ms) { flash(this.editor, ms); } - highlight(haps) { - highlightHaps(this.editor, haps); + highlight(haps, time) { + highlightMiniLocations(this.editor.view, time, haps); } } diff --git a/packages/codemirror/flash.mjs b/packages/codemirror/flash.mjs new file mode 100644 index 00000000..9bc5c593 --- /dev/null +++ b/packages/codemirror/flash.mjs @@ -0,0 +1,35 @@ +import { StateEffect, StateField } from '@codemirror/state'; +import { Decoration, EditorView } from '@codemirror/view'; + +export const setFlash = StateEffect.define(); +export const flashField = StateField.define({ + create() { + return Decoration.none; + }, + update(flash, tr) { + try { + for (let e of tr.effects) { + if (e.is(setFlash)) { + if (e.value && tr.newDoc.length > 0) { + const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } }); + flash = Decoration.set([mark.range(0, tr.newDoc.length)]); + } else { + flash = Decoration.set([]); + } + } + } + return flash; + } catch (err) { + console.warn('flash error', err); + return flash; + } + }, + provide: (f) => EditorView.decorations.from(f), +}); + +export const flash = (view, ms = 200) => { + view.dispatch({ effects: setFlash.of(true) }); + setTimeout(() => { + view.dispatch({ effects: setFlash.of(false) }); + }, ms); +}; diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs new file mode 100644 index 00000000..317c5fdf --- /dev/null +++ b/packages/codemirror/highlight.mjs @@ -0,0 +1,126 @@ +import { RangeSetBuilder, StateEffect, StateField } from '@codemirror/state'; +import { Decoration, EditorView } from '@codemirror/view'; + +export const setMiniLocations = StateEffect.define(); +export const showMiniLocations = StateEffect.define(); +export const updateMiniLocations = (view, locations) => { + view.dispatch({ effects: setMiniLocations.of(locations) }); +}; +export const highlightMiniLocations = (view, atTime, haps) => { + view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }); +}; + +const miniLocations = StateField.define({ + create() { + return Decoration.none; + }, + update(locations, tr) { + if (tr.docChanged) { + locations = locations.map(tr.changes); + } + + for (let e of tr.effects) { + if (e.is(setMiniLocations)) { + // this is called on eval, with the mini locations obtained from the transpiler + // codemirror will automatically remap the marks when the document is edited + // create a mark for each mini location, adding the range to the spec to find it later + const marks = e.value + .filter(([from]) => from < tr.newDoc.length) + .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) + .map( + (range) => + Decoration.mark({ + id: range.join(':'), + // this green is only to verify that the decoration moves when the document is edited + // it will be removed later, so the mark is not visible by default + attributes: { style: `background-color: #00CA2880` }, + }).range(...range), // -> Decoration + ); + + locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet + } + } + + return locations; + }, +}); + +const visibleMiniLocations = StateField.define({ + create() { + return { atTime: 0, haps: new Map() }; + }, + update(visible, tr) { + for (let e of tr.effects) { + if (e.is(showMiniLocations)) { + // this is called every frame to show the locations that are currently active + // we can NOT create new marks because the context.locations haven't changed since eval time + // this is why we need to find a way to update the existing decorations, showing the ones that have an active range + const haps = new Map(); + for (let hap of e.value.haps) { + for (let { start, end } of hap.context.locations) { + let id = `${start}:${end}`; + if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { + haps.set(id, hap); + } + } + } + + visible = { atTime: e.value.atTime, haps }; + } + } + + return visible; + }, +}); + +// // Derive the set of decorations from the miniLocations and visibleLocations +const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => { + const iterator = state.field(miniLocations).iter(); + const { haps } = state.field(visibleMiniLocations); + const builder = new RangeSetBuilder(); + + while (iterator.value) { + const { + from, + to, + value: { + spec: { id }, + }, + } = iterator; + + if (haps.has(id)) { + const hap = haps.get(id); + const color = hap.context.color ?? 'var(--foreground)'; + // Get explicit channels for color values + /* + const swatch = document.createElement('div'); + swatch.style.color = color; + document.body.appendChild(swatch); + let channels = getComputedStyle(swatch) + .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) + .slice(1) + .map((c) => parseFloat(c || 1)); + document.body.removeChild(swatch); + + // Get percentage of event + const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; + channels[3] *= percent; + */ + + builder.add( + from, + to, + Decoration.mark({ + // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, + attributes: { style: `outline: solid 2px ${color}` }, + }), + ); + } + + iterator.next(); + } + + return builder.finish(); +}); + +export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; diff --git a/packages/codemirror/index.mjs b/packages/codemirror/index.mjs new file mode 100644 index 00000000..bf7ce971 --- /dev/null +++ b/packages/codemirror/index.mjs @@ -0,0 +1,3 @@ +export * from './codemirror.mjs'; +export * from './highlight.mjs'; +export * from './flash.mjs'; diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index ecbd408f..0e32fef6 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -2,7 +2,7 @@ "name": "@strudel/codemirror", "version": "0.8.4", "description": "Codemirror Extensions for Strudel", - "main": "codemirror.mjs", + "main": "index.mjs", "publishConfig": { "main": "dist/index.js", "module": "dist/index.mjs" diff --git a/packages/codemirror/vite.config.js b/packages/codemirror/vite.config.js index 8562915c..0fc63a6b 100644 --- a/packages/codemirror/vite.config.js +++ b/packages/codemirror/vite.config.js @@ -7,7 +7,7 @@ export default defineConfig({ plugins: [], build: { lib: { - entry: resolve(__dirname, 'codemirror.mjs'), + entry: resolve(__dirname, 'index.mjs'), formats: ['es', 'cjs'], fileName: (ext) => ({ es: 'index.mjs', cjs: 'index.js' }[ext]), }, diff --git a/packages/react/examples/nano-repl/src/App.jsx b/packages/react/examples/nano-repl/src/App.jsx index 70bf2af1..086923ac 100644 --- a/packages/react/examples/nano-repl/src/App.jsx +++ b/packages/react/examples/nano-repl/src/App.jsx @@ -82,9 +82,10 @@ function App() { code, defaultOutput: webaudioOutput, getTime, + afterEval: ({ meta }) => setMiniLocations(meta.miniLocations), }); - useHighlighting({ + const { setMiniLocations } = useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), diff --git a/packages/react/package.json b/packages/react/package.json index bded6c9e..51916049 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -42,6 +42,7 @@ "@strudel.cycles/core": "workspace:*", "@strudel.cycles/transpiler": "workspace:*", "@strudel.cycles/webaudio": "workspace:*", + "@strudel/codemirror": "workspace:*", "@uiw/codemirror-themes": "^4.19.16", "@uiw/react-codemirror": "^4.19.16", "react-hook-inview": "^4.5.0" diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index 2041456f..0f1b2274 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -1,221 +1,24 @@ -import React, { useMemo } from 'react'; -import _CodeMirror from '@uiw/react-codemirror'; -import { EditorView, Decoration } from '@codemirror/view'; -import { StateField, StateEffect, RangeSetBuilder } from '@codemirror/state'; -import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; -import strudelTheme from '../themes/strudel-theme'; -import './style.css'; -import { useCallback } from 'react'; import { autocompletion } from '@codemirror/autocomplete'; -import { strudelAutocomplete } from './Autocomplete'; -import { vim } from '@replit/codemirror-vim'; +import { javascript, javascriptLanguage } from '@codemirror/lang-javascript'; +import { EditorView } from '@codemirror/view'; import { emacs } from '@replit/codemirror-emacs'; +import { vim } from '@replit/codemirror-vim'; +import _CodeMirror from '@uiw/react-codemirror'; +import React, { useCallback, useMemo } from 'react'; +import strudelTheme from '../themes/strudel-theme'; +import { strudelAutocomplete } from './Autocomplete'; +import { + highlightExtension, + flashField, + flash, + highlightMiniLocations, + updateMiniLocations, +} from '@strudel/codemirror'; +import './style.css'; -export const setFlash = StateEffect.define(); -const flashField = StateField.define({ - create() { - return Decoration.none; - }, - update(flash, tr) { - try { - for (let e of tr.effects) { - if (e.is(setFlash)) { - if (e.value && tr.newDoc?.length?.length > 0) { - const mark = Decoration.mark({ attributes: { style: `background-color: #FFCA2880` } }); - flash = Decoration.set([mark.range(0, tr.newDoc.length)]); - } else { - flash = Decoration.set([]); - } - } - } - return flash; - } catch (err) { - console.warn('flash error', err); - return flash; - } - }, - provide: (f) => EditorView.decorations.from(f), -}); +export { flash, highlightMiniLocations, updateMiniLocations }; -export const flash = (view) => { - view.dispatch({ effects: setFlash.of(true) }); - setTimeout(() => { - view.dispatch({ effects: setFlash.of(false) }); - }, 200); -}; - -export const setMiniLocations = StateEffect.define(); -export const showMiniLocations = StateEffect.define(); -export const updateMiniLocations = (view, locations) => { - view.dispatch({ effects: setMiniLocations.of(locations) }); -}; -export const highlightMiniLocations = (view, atTime, haps) => { - view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) }); -}; - -const miniLocations = StateField.define({ - create() { - return Decoration.none; - }, - update(locations, tr) { - if (tr.docChanged) { - locations = locations.map(tr.changes); - } - - for (let e of tr.effects) { - if (e.is(setMiniLocations)) { - // this is called on eval, with the mini locations obtained from the transpiler - // codemirror will automatically remap the marks when the document is edited - // create a mark for each mini location, adding the range to the spec to find it later - const marks = e.value - .filter(([from]) => from < tr.newDoc.length) - .map(([from, to]) => [from, Math.min(to, tr.newDoc.length)]) - .map( - (range) => - Decoration.mark({ - id: range.join(':'), - // this green is only to verify that the decoration moves when the document is edited - // it will be removed later, so the mark is not visible by default - attributes: { style: `background-color: #00CA2880` }, - }).range(...range), // -> Decoration - ); - - locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet - } - } - - return locations; - }, -}); - -const visibleMiniLocations = StateField.define({ - create() { - return { atTime: 0, haps: new Map() }; - }, - update(visible, tr) { - for (let e of tr.effects) { - if (e.is(showMiniLocations)) { - // this is called every frame to show the locations that are currently active - // we can NOT create new marks because the context.locations haven't changed since eval time - // this is why we need to find a way to update the existing decorations, showing the ones that have an active range - const haps = new Map(); - for (let hap of e.value.haps) { - for (let { start, end } of hap.context.locations) { - let id = `${start}:${end}`; - if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) { - haps.set(id, hap); - } - } - } - - visible = { atTime: e.value.atTime, haps }; - } - } - - return visible; - }, -}); - -// // Derive the set of decorations from the miniLocations and visibleLocations -const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => { - const iterator = state.field(miniLocations).iter(); - const { atTime, haps } = state.field(visibleMiniLocations); - const builder = new RangeSetBuilder(); - - while (!!iterator.value) { - const { - from, - to, - value: { - spec: { id }, - }, - } = iterator; - - if (haps.has(id)) { - const hap = haps.get(id); - const color = hap.context.color ?? 'var(--foreground)'; - - // Get explicit channels for color values - /* - const swatch = document.createElement('div'); - swatch.style.color = color; - document.body.appendChild(swatch); - let channels = getComputedStyle(swatch) - .color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/) - .slice(1) - .map((c) => parseFloat(c || 1)); - document.body.removeChild(swatch); - - // Get percentage of event - const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration; - channels[3] *= percent; - */ - - builder.add( - from, - to, - Decoration.mark({ - // attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` }, - attributes: { style: `outline: solid 2px ${color}` }, - }), - ); - } - - iterator.next(); - } - - return builder.finish(); -}); - -const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]; - -export const setHighlights = StateEffect.define(); -const highlightField = StateField.define({ - create() { - return Decoration.none; - }, - update(highlights, tr) { - highlights = highlights.map(tr.changes); - try { - for (let e of tr.effects) { - if (e.is(setHighlights)) { - const { haps } = e.value; - const marks = - haps - .map((hap) => - (hap.context.locations || []).map(({ start: from, end: to }) => { - const color = hap.context.color || e.value.color; - console.log(color); - /* let from = tr.newDoc.line(start.line).from + start.column; - let to = tr.newDoc.line(end.line).from + end.column; */ - const l = tr.newDoc.length; - if (from > l || to > l) { - return; // dont mark outside of range, as it will throw an error - } - let mark; - if (color) { - mark = Decoration.mark({ attributes: { style: `outline: 2px solid ${color};` } }); - } else { - mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } }); - } - return mark.range(from, to); - }), - ) - .flat() - .filter(Boolean) || []; - highlights = Decoration.set(marks, true); - } - } - return highlights; - } catch (err) { - // console.warn('highlighting error', err); - return Decoration.set([]); - } - }, - provide: (f) => EditorView.decorations.from(f), -}); - -const staticExtensions = [javascript(), highlightField, flashField, highlightExtension]; +const staticExtensions = [javascript(), flashField, highlightExtension]; export default function CodeMirror({ value, @@ -230,8 +33,6 @@ export default function CodeMirror({ isLineWrappingEnabled, fontSize = 18, fontFamily = 'monospace', - options, - editorDidMount, }) { const handleOnChange = useCallback( (value) => { @@ -299,103 +100,3 @@ export default function CodeMirror({ ); } - -let parenMark; -export const markParens = (editor, data) => { - const v = editor.getDoc().getValue(); - const marked = getCurrentParenArea(v, data); - parenMark?.clear(); - parenMark = editor.getDoc().markText(...marked, { css: 'background-color: #00007720' }); // -}; - -// returns { line, ch } from absolute character offset -export function offsetToPosition(offset, code) { - const lines = code.split('\n'); - let line = 0; - let ch = 0; - for (let i = 0; i < offset; i++) { - if (ch === lines[line].length) { - line++; - ch = 0; - } else { - ch++; - } - } - return { line, ch }; -} - -// returns absolute character offset from { line, ch } -export function positionToOffset(position, code) { - const lines = code.split('\n'); - if (position.line > lines.length) { - // throw new Error('positionToOffset: position.line > lines.length'); - return 0; - } - let offset = 0; - for (let i = 0; i < position.line; i++) { - offset += lines[i].length + 1; - } - offset += position.ch; - return offset; -} - -// given code and caret position, the functions returns the indices of the parens we are in -export function getCurrentParenArea(code, caretPosition) { - const caret = positionToOffset(caretPosition, code); - let open, i, begin, end; - // walk left - i = caret; - open = 0; - while (i > 0) { - if (code[i - 1] === '(') { - open--; - } else if (code[i - 1] === ')') { - open++; - } - if (open === -1) { - break; - } - i--; - } - begin = i; - // walk right - i = caret; - open = 0; - while (i < code.length) { - if (code[i] === '(') { - open--; - } else if (code[i] === ')') { - open++; - } - if (open === 1) { - break; - } - i++; - } - end = i; - return [begin, end].map((o) => offsetToPosition(o, code)); -} - -/* -export const markEvent = (editor) => (time, event) => { - const locs = event.context.locations; - if (!locs || !editor) { - return; - } - const col = event.context?.color || '#FFCA28'; - // mark active event - const marks = locs.map(({ start, end }) => - editor.getDoc().markText( - { line: start.line - 1, ch: start.column }, - { line: end.line - 1, ch: end.column }, - //{ css: 'background-color: #FFCA28; color: black' } // background-color is now used by parent marking - { css: 'outline: 1px solid ' + col + '; box-sizing:border-box' }, - //{ css: `background-color: ${col};border-radius:5px` }, - ), - ); - //Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler... - setTimeout(() => { - marks.forEach((mark) => mark.clear()); - // }, '+' + event.duration * 0.5); - }, event.duration * 1000); -}; */ diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index 1a6cce94..12500874 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -71,6 +71,7 @@ export function MiniRepl({ evalOnMount, drawContext, drawTime, + afterEval: ({ meta }) => setMiniLocations(meta.miniLocations), }); const [view, setView] = useState(); @@ -84,7 +85,7 @@ export function MiniRepl({ } return isVisible || wasVisible.current; }, [isVisible, hideOutsideView]); - useHighlighting({ + const { setMiniLocations } = useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), diff --git a/packages/react/src/hooks/useHighlighting.mjs b/packages/react/src/hooks/useHighlighting.mjs index 56136369..7b522464 100644 --- a/packages/react/src/hooks/useHighlighting.mjs +++ b/packages/react/src/hooks/useHighlighting.mjs @@ -1,10 +1,18 @@ -import { useEffect, useRef } from 'react'; -import { highlightMiniLocations } from '../components/CodeMirror6'; +import { useEffect, useRef, useState } from 'react'; +import { highlightMiniLocations, updateMiniLocations } from '../components/CodeMirror6'; const round = (x) => Math.round(x * 1000) / 1000; function useHighlighting({ view, pattern, active, getTime }) { const highlights = useRef([]); const lastEnd = useRef(0); + + const [miniLocations, setMiniLocations] = useState([]); + useEffect(() => { + if (view) { + updateMiniLocations(view, miniLocations); + } + }, [view, miniLocations]); + useEffect(() => { if (view) { if (pattern && active) { @@ -30,11 +38,14 @@ function useHighlighting({ view, pattern, active, getTime }) { cancelAnimationFrame(frame); }; } else { + console.log('not active'); highlights.current = []; highlightMiniLocations(view, 0, highlights.current); } } }, [pattern, active, view]); + + return { setMiniLocations }; } export default useHighlighting; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7017134..88355655 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,6 +292,9 @@ importers: '@strudel.cycles/webaudio': specifier: workspace:* version: link:../webaudio + '@strudel/codemirror': + specifier: workspace:* + version: link:../codemirror '@uiw/codemirror-themes': specifier: ^4.19.16 version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 9f54a51e..878c77ea 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -126,13 +126,6 @@ export function Repl({ embedded = false }) { isLineWrappingEnabled, } = useSettings(); - const [miniLocations, setMiniLocations] = useState([]); - useEffect(() => { - if (view) { - updateMiniLocations(view, miniLocations); - } - }, [view, miniLocations]); - const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = useStrudel({ initialCode: '// LOADING...', @@ -195,7 +188,7 @@ export function Repl({ embedded = false }) { ); // highlighting - useHighlighting({ + const { setMiniLocations } = useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'),