Open felixroos opened 1 year ago
React Example.
import CodeMirror from "@uiw/react-codemirror";
import { useState, useEffect, useCallback } from "react";
import { StateField, StateEffect } from "@codemirror/state";
import { EditorView, Decoration } from "@codemirror/view";
export const setHighlights = StateEffect.define();
const highlightField = StateField.define({
create() {
return Decoration.none;
},
update(highlights, tr) {
for (let e of tr.effects) {
const char = e.value;
if (e.is(setHighlights) && tr.state.doc.length) {
const mark = Decoration.mark({
attributes: { style: `outline: 2px solid blue` }
}).range(char, char + 1);
highlights = Decoration.set([mark]);
}
}
return highlights;
},
provide: (f) => EditorView.decorations.from(f)
});
function useHighlighting(view) {
useEffect(() => {
let frame;
if (view) {
console.log("start highlighting");
frame = requestAnimationFrame(updateHighlights);
function updateHighlights() {
try {
const char = Math.round(Math.random() * view.state.doc.length - 1);
view.dispatch({ effects: setHighlights.of(char) }); // highlight all still active + new active haps
frame = requestAnimationFrame(updateHighlights);
} catch (err) {
// even though we catch errors here,
// the range error still crashes
console.log("err", err);
}
}
}
return () => {
frame && cancelAnimationFrame(frame);
};
}, [view]);
}
const extensions = [highlightField];
export default function App() {
const [view, setView] = useState();
const [value, setValue] = useState(
"delete me with ctrl+a delete to get an error"
);
const handleOnChange = useCallback((v) => {
setValue(v);
}, []);
const handleOnCreateEditor = useCallback((v) => {
setView(v);
}, []);
console.log("value", value);
useHighlighting(view);
return (
<div>
<CodeMirror
value={value}
onCreateEditor={handleOnCreateEditor}
onChange={handleOnChange}
extensions={extensions}
/>
</div>
);
}
import {basicSetup, EditorView} from "codemirror"
import {javascript} from "@codemirror/lang-javascript"
import { StateField, StateEffect } from "@codemirror/state";
import { Decoration } from "@codemirror/view";
const setHighlights = StateEffect.define();
const highlightField = StateField.define({
create() {
return Decoration.none;
},
update(highlights, tr) {
for (let e of tr.effects) {
const char = e.value;
if (e.is(setHighlights) && tr.state.doc.length) {
const mark = Decoration.mark({
attributes: { style: `outline: 2px solid blue` }
}).range(char, char + 1);
highlights = Decoration.set([mark]);
}
}
return highlights;
},
provide: (f) => EditorView.decorations.from(f)
});
var view = new EditorView({
doc: "console.log('hello')\n",
extensions: [basicSetup, javascript(), highlightField],
parent: document.body
})
var frame = requestAnimationFrame(updateHighlights);
function updateHighlights() {
try {
const char = Math.round(Math.random() * view.state.doc.length - 1);
view.dispatch({ effects: setHighlights.of(char) }); // highlight all still active + new active haps
frame = requestAnimationFrame(updateHighlights);
} catch (err) {
// even though we catch errors here,
// the range error still crashes
console.log("err", err);
}
}
in some cases, the view state seems to be out of sync. I am updating Decorations in an animation frame, and it can happen that the view dispatches from an old state, which can create errors. these errors also don't seem to be catchable with all I've tried.
I've made a minimal repro here:
https://codesandbox.io/s/react-codemirror-range-error-forked-pcpwtl?file=/src/App.js
I also wrote a post in the codemirror forum: https://discuss.codemirror.net/t/uncatchable-out-of-range-errors-when-document-changes/4864
This was all some time ago and I already found a workaround but today I ran into a similar problem again.
Not sure what to do about this..