CodinGame / monaco-editor-react

16 stars 2 forks source link

Cursor is moved to the beginning of the file #36

Closed tyagirajat200 closed 8 months ago

tyagirajat200 commented 10 months ago

While I'm typing rapidly within the editor, the cursor has a tendency to unexpectedly leap to the start of the file.

Here's the code snippet I'm utilizing:

function MonacoEditor(props: MonacoProps): ReactElement {
    const classNames = useClassNames();
    const [value, setValue] = useState('');

    return <div className={classNames("code-editor", [editorClassName])}>
        <Editor value={value} onChange={setValue} />
    </div>
}

export default MonacoEditor;

The problem doesn't arise when I don't provide the 'value' attribute. I suspect the issue stems from the fact that the updated value is being consistently passed to the editor upon each change.

CGNonofr commented 10 months ago

That's surprising. The lib only uses an effect to update the model when the prop change, I have no idea how it can happen and I'm unable to reproduce.

The component is not so complicated, are you able to investigate a little bit? (debugging the effect)

tyagirajat200 commented 10 months ago

I tried to debug the issue and found that the fixedCode !== model.getValue() condition returns true only when I'm typing rapidly. In such cases, it sets the value again. This might be happening because we are using a debounce function in the onChange event handler.

useEffect(() => {
    if (fixedCode != null) {
        const model = modelRef.current;
        const editor = editorRef.current;
        if (fixedCode !== model.getValue()) {
            preventTriggerChangeEventRef.current = true;
            console.debug('Replacing whole editor content');
            editor.pushUndoStop();
            model.setValue(fixedCode);
            editor.pushUndoStop();
            preventTriggerChangeEventRef.current = false;
        }
    }
}, [fixedCode]);

useEffect(() => {
    if (onChange != null) {
        // Sometimes, monaco calls the didChangeModelContent multiple times in a row
        // (For instance, after an autocomplete with some additionalTextEdits - used by the typescript language server)
        // The onChange prop is very probably used to store the current code in a state somewhere
        // Updating that state 2 times in a row will trigger 2 asynchronous renders
        // The first render will use the state after the first model content change, while the monaco model is already up to date with the last change
        // It will then produce a desync and this component will overwrite the editor content to sync it back
        // Doing so produces at least 2 issues: the cursor is moved to the beginning of the file by monaco and the undo stack is lost
        // So a solution is to debounce the onChange callback so it's called only once in that case
        const debouncedOnChange = debounce(onChange, 0);
        const editor = editorRef.current;
        const didChangeModelContentDisposable = editor.onDidChangeModelContent(event => {
            if (!preventTriggerChangeEventRef.current) {
                debouncedOnChange(editor.getValue(), event);
            }
        });
        return () => {
            didChangeModelContentDisposable.dispose();
        };
    }
    return undefined;
}, [onChange]);
CGNonofr commented 10 months ago

That's funny, that code is there exactly to prevent that issue (:

I'm surprised it can have this impact as it's just a setTimeout(0) and I don't know how you can type THAT fast to be able to enter 2 chars.

I'm unable to reproduce though, can you provide some hints? What browser are you using?

cj-rajat commented 9 months ago

I was using latest version of chrome. This issue was resolved by removing the setTimeout.

cj-rajat commented 9 months ago

I have one more issue if I create multiple models beforehand then on setting the model in the monaco editor below error is coming - main.js:3283 Unexpected error TypeError: Cannot read properties of undefined (reading 'match') at MonarchModernTokensCollector.emit (monarchLexer.js:239:1) at MonarchTokenizer._myTokenize (monarchLexer.js:714:1) at MonarchTokenizer._tokenize (monarchLexer.js:394:1) at MonarchTokenizer.tokenizeEncoded (monarchLexer.js:386:1) at safeTokenize (textModelTokens.js:415:37) at TextModelTokenization._updateTokensUntilLine (textModelTokens.js:338:23) at TextModelTokenization._tokenizeOneInvalidLine (textModelTokens.js:324:14) at TextModelTokenization._backgroundTokenizeForAtLeast1ms (textModelTokens.js:228:46) at execute (textModelTokens.js:201:18) at TextModelTokenization._backgroundTokenizeWithDeadline (textModelTokens.js:212:9) With this syntax highlighting is not working.

cj-rajat commented 8 months ago

I imported monaco directly from "monaco-editor" in one of the files. This is fixed.