Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot dynamically select code #509

Open
guillermoamaral opened this issue May 25, 2023 · 4 comments
Open

Cannot dynamically select code #509

guillermoamaral opened this issue May 25, 2023 · 4 comments

Comments

@guillermoamaral
Copy link

guillermoamaral commented May 25, 2023

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?

@tonisives
Copy link

tonisives commented May 26, 2023

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)
    }
  }

@jaywcjlove
Copy link
Member

@tonisives @guillermoamaral https://codesandbox.io/s/react-codemirror-example-codemirror-6-https-github-com-uiwjs-react-codemirror-issues-314-w64xo4

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>
  );
}

@guillermoamaral
Copy link
Author

guillermoamaral commented May 26, 2023

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!

@jaywcjlove
Copy link
Member

@guillermoamaral You can use $edit.current.view.dispatch to dynamically select text.

https://codemirror.net/docs/ref/#state.Transaction.selection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants