materiahq / ngx-monaco-editor

Monaco Editor Library for Angular v6 and above
MIT License
159 stars 35 forks source link

Move cursor to a specific position #29

Closed sanjay51 closed 3 years ago

sanjay51 commented 4 years ago

I've a multi-user ngx-monaco-editor (with firebase real-time sync, similar to firepad.io), i.e. multiple users can simultaneously make changes, and the changes will be propagated to every other user. This is how my ngx-monaco-editor is setup:

    <ngx-monaco-editor #editor [options]="editorOptions" (onInit)="onEditorInit($event)"
                       (ngModelChange)="this.model.candidateDocument.save()" [(ngModel)]="getData().candidateData">

Now as soon as ngModel change event is emitted, monaco-editor content is updated. Great!!

The problem I face is with cursor. As soon as the editor content is updated, the cursor moves to 1st line, position 0. Complete reset.

I want to avoid it. Is there a way to,

(i) Keep the cursor at the same place when the ngx-monaco-editor content is updated dynamically? OR (ii) If not possible, my only option will be to manually keep track of the cursor, and update its position on every change. However, for that, I'll need access to (a) current curser position, and (b) a way to set cursor position dynamically. I wonder if that's possible.

Could you please help me understand how to achieve this? Feel free to ask for any clarification. Thanks a lot.

jmarc-roy commented 4 years ago

Hi @sanjay51,

I looked a bit on possible solution for you. For your use case, I recommand you to retrieve the editor instance (https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) as shown in the readme:

// Template
<ngx-monaco-editor [options]="editorOptions" [(ngModel)]="code" (init)="editorInit($event)"></ngx-monaco-editor>
// TS
import { Component } from '@angular/core';
import { MonacoStandaloneCodeEditor } from '@materia-ui/ngx-monaco-editor';
...
export class AppComponent {
  editorOptions = {theme: 'vs-dark', language: 'javascript'};
  code: string= 'function x() {\nconsole.log("Hello world!");\n}';

  editorInit(editor: MonacoStandaloneCodeEditor) {
    // Here you can access editor instance
     this.editor = editor;
    }
}

You will be able with the editor instance to set your cursor at a specific position, or even better save the view state of the editor and reset it after external update (this allow to conserve scroll position as well) :

// saving view state
const state = this.editor.saveViewState();
// restoring view state
this.editor.restoreViewState(state);

Here are some usefull issues I looked at: https://github.com/Microsoft/monaco-editor/issues/1397, https://github.com/microsoft/monaco-editor/issues/194.

I think the best way to achieve your goal is to use rxjs streams to restore the state view when it is needed, something like that:

externalUpdate$ = new BehaviorSubject();
editorModelContentChange$ = new BehaviorSubject();
// this.editor retrieved on init
this.editor.onDidChangeModelContent((event) => this.editorModelContentChange.next(event));

this.externalUpdate$.pipe(
  skip(1),
  map((code) => ({ code, viewState: this.editor.saveViewState() })),
  tap(({ code }) => {
     this.code = code
  }),
  switchMap(({ viewState }) =>
     this.editorModelContentChange$.asObservable()
       .pipe(
          delay(1),
          tap(() => this.editor.restoreViewState(viewState)),
          take(1)
       )
    )    
).subscribe();

Of course, it is just a short example, and you will have to manage the observables unsubscriptions and the code merging logic, when an external update occured (to not lost the current change in the editor).

Here is a brief stackblitz example of this solution: https://stackblitz.com/edit/materia-ngx-monaco-editor-example-1bgg2k.

Let us know if you find out a better approach. You seems to have a very interesting project! Have a nice day!

sanjay51 commented 4 years ago

Hi @GeoAstronaute , Thank you so much for looking into it. I couldn't get it running though especially with the remote synchronization that I'm doing simultaneously, let me try again this weekend. Thanks a lot! Feel free to close this though, I think either way it's gonna be tricky.