Bladeren bron

[editor] Add missing keyboard commands to the AceEditor component

Johan Ahlen 5 jaren geleden
bovenliggende
commit
b43e15e056

+ 108 - 23
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/AceEditor.vue

@@ -34,6 +34,7 @@
   import AceAutocomplete from './autocomplete/AceAutocomplete.vue';
   import AceGutterHandler from './AceGutterHandler';
   import AceLocationHandler from './AceLocationHandler';
+  import { formatSql } from 'apps/notebook2/apiUtils';
   import Executor from 'apps/notebook2/execution/executor';
   import SubscriptionTracker from 'components/utils/SubscriptionTracker';
   import { INSERT_AT_CURSOR_EVENT } from 'ko/bindings/ace/ko.aceEditor';
@@ -168,25 +169,6 @@
         }
       }
 
-      const triggerChange = () => {
-        this.$emit('value-changed', removeUnicodes(editor.getValue()));
-      };
-
-      editor.commands.addCommand({
-        name: 'execute',
-        bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter|Ctrl-Enter' },
-        exec: async () => {
-          if (this.aceLocationHandler) {
-            this.aceLocationHandler.refreshStatementLocations();
-          }
-          if (this.editor && this.executor.activeExecutable) {
-            triggerChange();
-            await this.executor.activeExecutable.reset();
-            await this.executor.activeExecutable.execute();
-          }
-        }
-      });
-
       const onFocus = (): void => {
         huePubSub.publish('ace.editor.focused', editor);
 
@@ -235,8 +217,9 @@
         }
       };
 
-      editor.on('change', triggerChange);
-      editor.on('blur', triggerChange);
+      const boundTriggerChange = this.triggerChange.bind(this);
+      editor.on('change', boundTriggerChange);
+      editor.on('blur', boundTriggerChange);
       editor.on('focus', onFocus);
       editor.on('paste', onPaste);
       editor.on('input', onInput);
@@ -244,8 +227,8 @@
 
       this.subTracker.addDisposable({
         dispose: () => {
-          editor.off('change', triggerChange);
-          editor.off('blur', triggerChange);
+          editor.off('change', boundTriggerChange);
+          editor.off('blur', boundTriggerChange);
           editor.off('focus', onFocus);
           editor.off('paster', onPaste);
           editor.off('input', onInput);
@@ -299,6 +282,7 @@
       );
 
       this.editor = editor;
+      this.registerEditorCommands();
       this.addInsertSubscribers();
       this.$emit('ace-created', editor);
     }
@@ -487,6 +471,107 @@
     destroyed(): void {
       this.subTracker.dispose();
     }
+
+    triggerChange(): void {
+      if (this.editor) {
+        this.$emit('value-changed', removeUnicodes(this.editor.getValue()));
+      }
+    }
+
+    registerEditorCommands(): void {
+      if (!this.editor) {
+        return;
+      }
+
+      this.editor.commands.addCommand({
+        name: 'execute',
+        bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter|Ctrl-Enter' },
+        exec: async () => {
+          if (this.aceLocationHandler) {
+            this.aceLocationHandler.refreshStatementLocations();
+          }
+          if (this.editor && this.executor.activeExecutable) {
+            this.triggerChange();
+            await this.executor.activeExecutable.reset();
+            await this.executor.activeExecutable.execute();
+          }
+        }
+      });
+
+      this.editor.commands.addCommand({
+        name: 'switchTheme',
+        bindKey: { win: 'Ctrl-Alt-t', mac: 'Command-Alt-t' },
+        exec: () => {
+          if (
+            this.editor &&
+            this.editor.customMenuOptions &&
+            this.editor.customMenuOptions.getEnableDarkTheme &&
+            this.editor.customMenuOptions.setEnableDarkTheme
+          ) {
+            const enabled = this.editor.customMenuOptions.getEnableDarkTheme();
+            this.editor.customMenuOptions.setEnableDarkTheme(!enabled);
+          }
+        }
+      });
+
+      this.editor.commands.addCommand({
+        name: 'new',
+        bindKey: { win: 'Ctrl-e', mac: 'Command-e' },
+        exec: () => {
+          this.$emit('create-new-doc');
+        }
+      });
+
+      this.editor.commands.addCommand({
+        name: 'save',
+        bindKey: { win: 'Ctrl-s', mac: 'Command-s|Ctrl-s' },
+        exec: () => {
+          this.$emit('save-doc');
+        }
+      });
+
+      this.editor.commands.addCommand({
+        name: 'togglePresentationMode',
+        bindKey: { win: 'Ctrl-Shift-p', mac: 'Ctrl-Shift-p|Command-Shift-p' },
+        exec: () => {
+          this.$emit('toggle-presentation-mode');
+        }
+      });
+
+      this.editor.commands.addCommand({
+        name: 'format',
+        bindKey: {
+          win: 'Ctrl-i|Ctrl-Shift-f|Ctrl-Alt-l',
+          mac: 'Command-i|Ctrl-i|Ctrl-Shift-f|Command-Shift-f|Ctrl-Shift-l|Cmd-Shift-l'
+        },
+        exec: async () => {
+          if (this.editor) {
+            this.editor.setReadOnly(true);
+            try {
+              if (this.editor.getSelectedText()) {
+                const selectionRange = this.editor.getSelectionRange();
+                const formatted = await formatSql({
+                  statements: this.editor.getSelectedText(),
+                  silenceErrors: true
+                });
+                this.editor.getSession().replace(selectionRange, formatted);
+              } else {
+                const formatted = await formatSql({
+                  statements: this.editor.getValue(),
+                  silenceErrors: true
+                });
+                this.editor.setValue(formatted, 1);
+              }
+              this.triggerChange();
+            } catch (e) {}
+            this.editor.setReadOnly(false);
+          }
+        }
+      });
+
+      this.editor.commands.bindKey('Ctrl-P', 'golineup');
+      this.editor.commands.bindKey({ win: 'Ctrl-j', mac: 'Command-j|Ctrl-j' }, 'gotoline');
+    }
   }
 
   export const COMPONENT_NAME = 'ace-editor';

+ 19 - 4
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/AceEditorKoBridge.vue

@@ -25,7 +25,10 @@
     :initial-cursor-position="cursorPosition"
     :initial-value="value"
     @ace-created="aceCreated"
+    @create-new-doc="createNewDoc"
     @cursor-changed="cursorChanged"
+    @save-doc="saveDoc"
+    @toggle-presentation-mode="togglePresentationMode"
     @value-changed="valueChanged"
   />
 </template>
@@ -88,22 +91,34 @@
       }
     }
 
-    valueChanged(value: string): void {
-      this.$el.dispatchEvent(new CustomEvent('value-changed', { bubbles: true, detail: value }));
+    destroyed(): void {
+      this.subTracker.dispose();
     }
 
     aceCreated(editor: Ace.Editor): void {
       this.$el.dispatchEvent(new CustomEvent('ace-created', { bubbles: true, detail: editor }));
     }
 
+    createNewDoc(): void {
+      this.$el.dispatchEvent(new CustomEvent('create-new-doc', { bubbles: true }));
+    }
+
     cursorChanged(cursorPosition: Ace.Position): void {
       this.$el.dispatchEvent(
         new CustomEvent('cursor-changed', { bubbles: true, detail: cursorPosition })
       );
     }
 
-    destroyed(): void {
-      this.subTracker.dispose();
+    saveDoc(): void {
+      this.$el.dispatchEvent(new CustomEvent('save-doc', { bubbles: true }));
+    }
+
+    togglePresentationMode(): void {
+      this.$el.dispatchEvent(new CustomEvent('toggle-presentation-mode', { bubbles: true }));
+    }
+
+    valueChanged(value: string): void {
+      this.$el.dispatchEvent(new CustomEvent('value-changed', { bubbles: true, detail: value }));
     }
   }
 

+ 5 - 1
desktop/core/src/desktop/js/ext/ace.d.ts

@@ -26,9 +26,10 @@ declare namespace Ace {
     commands: {
       addCommand(command: {
         name: string,
-        bindKey: { win: string; mac: string },
+        bindKey: { win: string; mac: string } | string,
         exec(): void
       }): void;
+      bindKey(key: { win: string; mac: string } | string, command: string): void;
     }
     completer: any;
     completers: any[];
@@ -82,7 +83,9 @@ declare namespace Ace {
     session: Session;
     setOption(option: string, value: OptionValue): void;
     setOptions(options: Options): void;
+    setReadOnly(readOnly: boolean): void;
     setTheme(theme: string): void;
+    setValue(value: string, cursorPosition: number): void;
     useHueAutocompleter: boolean;
   }
 
@@ -150,6 +153,7 @@ declare namespace Ace {
     remove(range: Range): void;
     removeGutterDecoration(line: number, clazz: string): void;
     removeMarker(markerId: number): void;
+    replace(range: Range, value: string): void;
     setMode(mode: string): void;
   }
 

+ 12 - 3
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -905,11 +905,20 @@
             'ace-created': function (event) {
               ace(event.detail);
             },
-            'value-changed': function (event) {
-              statement_raw(event.detail);
-            },
             'cursor-changed': function (event) {
               aceCursorPosition(event.detail);
+            },
+            'create-new-doc': function () {
+              huePubSub.publish('editor.create.new');
+            },
+            'save-doc': function () {
+              huePubSub.publish('editor.save');
+            },
+            'toggle-presentation-mode': function () {
+              huePubSub.publish('editor.presentation.toggle');
+            },
+            'value-changed': function (event) {
+              statement_raw(event.detail);
             }
           },
           vueKoProps: {