Cannot dynamically select code
Hi,
I'm implementing a debugger for which I need to select a portion of the code dynamically: essentialy whenever the user steps over the code I need to select the sentence to be executed. Here is the editor rendering code:
...
<CodeMirror
ref={this.editorRef}
width="100%"
height="100%"
extensions={[
smalltalk,
EditorView.lineWrapping,
lintGutter(),
linter(this.annotations),
Prec.highest(keymap.of([...this.extraKeys()])),
]}
theme={this.theme()}
value={source}
onChange={this.sourceChanged}
readOnly={evaluating || progress}
basicSetup={{
lineNumbers: lineNumbers,
closeBrackets: true,
bracketMatching: true,
highlightActiveLine: false,
drawSelection: true,
}}
/>
...
where source is part of the state.
The component is provided with a 'selectedRange' prop, and it works fine except for the first time. Since there is no option to set the selection in the same way as the value, I have to manage it somewhere else: I did it in componentDidUpdate thinking (naively perhaps) that I needed to have the value updated in order to apply the new selection. Concretely, I have this function:
selectRange(range) {
try {
this.editorRef.current.editorView.dispatch({ selection: range });
} catch (error) {
console.log(error);
}
}
and I call it from componentDidUpdate. The point is when selectRange is called, the value of the editor hasn't change yet... it changes a bit after, so the range can fall ouside of the (previous) value. For example, lets suppose that the current code is:
someCode
self someOtherCode
And the the user changes to a frame (remember that it is a debugger, with a call stack with different frames/methods) where the new code is:
someOtherCode
self blah.
self blahBlah.
self blahBlahBlah.
and the selection in the new code should be [anchor: 54, head: 75]. The render call will set the value to the new code but then when componentDidUpdate is called, it will try to apply the selection to the current value (....viewState.state.doc), which is still the previous code, and then the selection transaction will fail. Any suggestion? Any other way to manage the selection in a "controlled" way?
I also have this problem. currently resolved to calling dispatch 2x and with a delay
const onChange = (e: any, editorContent: string) => {
for (let i = 0; i < 2; i++) {
setTimeout(() => {
if (!codeMirrorRef.current) return
// ...calculate ranges
codeMirrorRef.current.view?.dispatch({
selection: EditorSelection.single(charactersBeforeCount, charactersBeforeCount + charactersInsideCount),
scrollIntoView: true,
})
}, 200 * i)
}
}
@tonisives @guillermoamaral https://codesandbox.io/s/react-codemirror-example-codemirror-6-https-github-com-uiwjs-react-codemirror-issues-314-w64xo4
- https://github.com/uiwjs/react-codemirror/issues/314#issuecomment-1557816378
import React, { useRef, useState } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { javascript } from "@codemirror/lang-javascript";
export default function App() {
const $edit = useRef();
const [val, setVal] = useState("console.log('hello world!');");
const onChange = React.useCallback((value, viewUpdate) => {
console.log("value:", value);
}, []);
const onRefChange = () => {
$edit.current.view.dispatch({
changes: { from: 0, to: 12, insert: "test" + new Date() }
});
};
return (
<div>
<button onClick={() => setVal("Time: " + new Date())}>
Change Value
</button>
<button onClick={onRefChange}>Ref Change Value</button>
<CodeMirror
value={val}
ref={$edit}
height="200px"
theme="dark"
extensions={[javascript({ jsx: true })]}
onChange={onChange}
/>
</div>
);
}
Hi,
Thanks for the response @jaywcjlove but I don't see how this can solve the problem. In my case, the component has 2 properties source and selectedRage. When they are updated, a rendering is triggered and the value gets updated (from the source prop), but the selection does not. My approach was to update it after the rendering (in componentDidUpdate), also dispatching a selection transaction, but that has the mentioned drawback: the editor's value remains the previous source for a while, and the new selection range (sometimes) surpasses the boundaries of such previous value (not the new one).
I tried what @tonisives did and it works but I'm not happy either.
Thanks in advance!
@guillermoamaral You can use $edit.current.view.dispatch to dynamically select text.
https://codemirror.net/docs/ref/#state.Transaction.selection