Parcourir la source

[editor] Keep track of the cursor position in the AceEditor Vue component

Johan Ahlen il y a 5 ans
Parent
commit
766a95fbab

+ 18 - 1
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/AceEditor.vue

@@ -24,6 +24,7 @@
 </template>
 
 <script lang="ts">
+  import { CURSOR_POSITION_CHANGED_EVENT } from 'ko/bindings/ace/aceLocationHandler';
   import { INSERT_AT_CURSOR_EVENT } from 'ko/bindings/ace/ko.aceEditor';
   import { IdentifierChainEntry } from 'parse/types';
   import huePubSub from 'utils/huePubSub';
@@ -57,8 +58,10 @@
     methods: { I18n }
   })
   export default class AceEditor extends Vue {
-    @Prop()
+    @Prop({ required: false, default: '' })
     initialValue!: string;
+    @Prop({ required: false })
+    initialCursorPosition?: Ace.Position;
     @Prop()
     id!: string;
     @Prop()
@@ -292,6 +295,20 @@
       this.subTracker.subscribe('assist.set.manual.visibility', resizeAce);
       this.subTracker.subscribe('split.panel.resized', resizeAce);
 
+      if (this.initialCursorPosition) {
+        editor.moveCursorToPosition(this.initialCursorPosition);
+        editor.renderer.scrollCursorIntoView();
+      }
+
+      this.subTracker.subscribe(
+        CURSOR_POSITION_CHANGED_EVENT,
+        (event: { editorId: string; position: Ace.Position }) => {
+          if (event.editorId === this.id) {
+            this.$emit('cursor-changed', event.position);
+          }
+        }
+      );
+
       this.editor = editor;
       this.addInsertSubscribers();
       this.$emit('ace-created', editor);

+ 29 - 6
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/AceEditorKoBridge.vue

@@ -20,11 +20,13 @@
   <ace-editor
     v-if="initialized && editorId"
     :id="editorId"
-    :executor="executor"
     :ace-options="aceOptions"
+    :executor="executor"
+    :initial-cursor-position="cursorPosition"
     :initial-value="value"
-    @value-changed="valueChanged"
     @ace-created="aceCreated"
+    @cursor-changed="cursorChanged"
+    @value-changed="valueChanged"
   />
 </template>
 
@@ -50,8 +52,11 @@
     @Prop()
     valueObservable!: KnockoutObservable<string | undefined>;
     @Prop()
+    cursorPositionObservable!: KnockoutObservable<Ace.Position | undefined>;
+    @Prop()
     aceOptions?: Ace.Options;
 
+    cursorPosition?: Ace.Position;
     value?: string;
     editorId?: string;
     subTracker = new SubscriptionTracker();
@@ -60,13 +65,25 @@
     updated(): void {
       if (!this.initialized) {
         this.value = this.valueObservable();
-        this.subTracker.subscribe(this.valueObservable, (value: string) => {
+        this.subTracker.subscribe(this.valueObservable, (value?: string) => {
           this.value = value;
         });
+
         this.editorId = this.idObservable();
-        this.subTracker.subscribe(this.idObservable, (id: string) => {
-          this.editorId = id;
-        });
+        if (!this.editorId) {
+          this.subTracker.whenDefined<string>(this.idObservable).then(id => {
+            this.editorId = id;
+          });
+        }
+
+        this.cursorPosition = this.cursorPositionObservable();
+        if (!this.cursorPosition) {
+          this.subTracker
+            .whenDefined<Ace.Position>(this.cursorPositionObservable)
+            .then(position => {
+              this.cursorPosition = position;
+            });
+        }
         this.initialized = true;
       }
     }
@@ -79,6 +96,12 @@
       this.$el.dispatchEvent(new CustomEvent('ace-created', { bubbles: true, detail: editor }));
     }
 
+    cursorChanged(cursorPosition: Ace.Position): void {
+      this.$el.dispatchEvent(
+        new CustomEvent('cursor-changed', { bubbles: true, detail: cursorPosition })
+      );
+    }
+
     destroyed(): void {
       this.subTracker.dispose();
     }

+ 0 - 6
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -370,12 +370,6 @@ export default class Snippet {
       this.inFocus(editor === this.ace());
     });
 
-    huePubSub.subscribe(CURSOR_POSITION_CHANGED_EVENT, details => {
-      if (details.editorId === this.id()) {
-        this.aceCursorPosition(details.position);
-      }
-    });
-
     huePubSub.subscribe(
       ACTIVE_STATEMENT_CHANGED_EVENT,
       statementDetails => {

+ 19 - 0
desktop/core/src/desktop/js/components/utils/SubscriptionTracker.ts

@@ -41,6 +41,25 @@ export default class SubscriptionTracker {
     }
   }
 
+  async whenDefined<T>(observable: KnockoutSubscribable<T | undefined>): Promise<T> {
+    return new Promise((resolve, reject) => {
+      let disposed = false;
+      const sub = observable.subscribe(val => {
+        if (typeof val !== 'undefined') {
+          disposed = true;
+          sub.dispose();
+          resolve(val);
+        }
+      });
+      this.disposals.push(() => {
+        if (!disposed) {
+          sub.dispose();
+          reject();
+        }
+      });
+    });
+  }
+
   addDisposable(disposable: Disposable): void {
     this.disposals.push(disposable.dispose.bind(disposable));
   }

+ 4 - 0
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -907,11 +907,15 @@
             },
             'value-changed': function (event) {
               statement_raw(event.detail);
+            },
+            'cursor-changed': function (event) {
+              aceCursorPosition(event.detail);
             }
           },
           vueKoProps: {
             executor: executor,
             valueObservable: statement_raw,
+            cursorPositionObservable: aceCursorPosition,
             idObservable: id,
             aceOptions: {
               showLineNumbers: $root.editorMode(),