Răsfoiți Sursa

[editor] Add support for "? from table" suggestions in the AceAutocomplete Vue component

Johan Ahlen 5 ani în urmă
părinte
comite
142e1a0c23

+ 42 - 8
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/autocomplete/AceAutocomplete.vue

@@ -81,6 +81,7 @@
 <script lang="ts">
   import { Ace } from 'ext/ace';
   import ace from 'ext/aceHelper';
+  import { REFRESH_STATEMENT_LOCATIONS_EVENT } from 'ko/bindings/ace/aceLocationHandler';
   import hueDebug from 'utils/hueDebug';
   import Vue from 'vue';
   import Component from 'vue-class-component';
@@ -143,6 +144,7 @@
     autocompleteResults?: AutocompleteResults;
     suggestions: Suggestion[] = [];
 
+    reTriggerTimeout = -1;
     changeTimeout = -1;
     positionInterval = -1;
     keyboardHandler: Ace.HashHandler | null = null;
@@ -207,7 +209,9 @@
           lineHeight: number;
           position: { top: number; left: number };
         }) => {
-          if (details.editor !== this.editor || !this.autocompleter) {
+          // The autocomplete can be triggered right after insertion of a suggestion
+          // when live autocomplete is enabled, hence if already active we ignore.
+          if (this.active || details.editor !== this.editor || !this.autocompleter) {
             return;
           }
           const session = this.editor.getSession();
@@ -216,13 +220,8 @@
           const prefix = aceUtil.retrievePrecedingIdentifier(line, pos.column);
           const newBase = session.doc.createAnchor(pos.row, pos.column - prefix.length);
 
-          this.positionAutocompleteDropdown();
-
-          if (this.active) {
-            this.detach();
-          }
-
           if (!this.base || newBase.column !== this.base.column || newBase.row !== this.base.row) {
+            this.positionAutocompleteDropdown();
             try {
               this.loading = true;
               const parseResult = await this.autocompleter.autocomplete();
@@ -250,7 +249,6 @@
               if (typeof console.warn !== 'undefined') {
                 console.warn(err);
               }
-              this.active = false;
               this.detach();
             }
           }
@@ -480,6 +478,42 @@
       this.editor.execCommand('insertstring', valueToInsert);
       this.editor.renderer.scrollCursorIntoView();
       this.detach();
+
+      if (this.editor.getOption('enableLiveAutocompletion')) {
+        if (/\S+\(\)$/.test(valueToInsert)) {
+          this.editor.moveCursorTo(
+            this.editor.getCursorPosition().row,
+            this.editor.getCursorPosition().column - 1
+          );
+          return;
+        }
+
+        window.clearTimeout(this.reTriggerTimeout);
+        this.reTriggerTimeout = window.setTimeout(() => {
+          if (this.active) {
+            return;
+          }
+
+          let reTrigger;
+          if (/(\? from \S+[^.]\s*$)/i.test(valueToInsert)) {
+            this.editor.moveCursorTo(
+              this.editor.getCursorPosition().row,
+              this.editor.getCursorPosition().column - (valueToInsert.length - 1)
+            );
+            this.editor.removeTextBeforeCursor(1);
+            reTrigger = true;
+          } else {
+            reTrigger = /\.$/.test(valueToInsert);
+          }
+
+          if (reTrigger) {
+            huePubSub.publish(REFRESH_STATEMENT_LOCATIONS_EVENT, this.editorId);
+            window.setTimeout(() => {
+              this.editor.execCommand('startAutocomplete');
+            }, 1);
+          }
+        }, 400);
+      }
     }
 
     positionAutocompleteDropdown(): void {

+ 47 - 65
desktop/core/src/desktop/js/apps/notebook2/components/aceEditor/autocomplete/SqlAutocompleter.ts

@@ -24,7 +24,7 @@ import Executor from 'apps/notebook2/execution/executor';
 import SubscriptionTracker, { Disposable } from 'components/utils/SubscriptionTracker';
 import { Ace } from 'ext/ace';
 import { ParsedSqlStatement } from 'parse/sqlStatementsParser';
-import { AutocompleteParser, AutocompleteParseResult } from 'parse/types';
+import { AutocompleteParseResult } from 'parse/types';
 import { EditorInterpreter } from 'types/config';
 import AutocompleteResults from './AutocompleteResults';
 import huePubSub from 'utils/huePubSub';
@@ -77,73 +77,55 @@ export default class SqlAutocompleter implements Disposable {
     return (<EditorInterpreter>this.executor.connector()).dialect;
   }
 
-  async parseActiveStatement(): Promise<AutocompleteParseResult> {
-    return new Promise((resolve, reject) => {
-      if (this.activeStatement) {
-        const activeStatementLocation = this.activeStatement.location;
-        const cursorPosition = this.editor.getCursorPosition();
-
-        if (
-          (activeStatementLocation.first_line - 1 < cursorPosition.row ||
-            (activeStatementLocation.first_line - 1 === cursorPosition.row &&
-              activeStatementLocation.first_column <= cursorPosition.column)) &&
-          (activeStatementLocation.last_line - 1 > cursorPosition.row ||
-            (activeStatementLocation.last_line - 1 === cursorPosition.row &&
-              activeStatementLocation.last_column >= cursorPosition.column))
-        ) {
-          const beforeCursor =
-            this.fixedPrefix() +
-            this.editor.session.getTextRange({
-              start: {
-                row: activeStatementLocation.first_line - 1,
-                column: activeStatementLocation.first_column
-              },
-              end: cursorPosition
-            });
-          const afterCursor =
-            this.editor.session.getTextRange({
-              start: cursorPosition,
-              end: {
-                row: activeStatementLocation.last_line - 1,
-                column: activeStatementLocation.last_column
-              }
-            }) + this.fixedPostfix();
-          const parserPromise = <Promise<AutocompleteParser>>(
-            sqlParserRepository.getAutocompleter(this.getDialect())
-          );
-          parserPromise
-            .then(autocompleteParser => {
-              resolve(autocompleteParser.parseSql(beforeCursor, afterCursor));
-            })
-            .catch(err => {
-              console.warn(err);
-              reject(err);
-            });
-        } else {
-          resolve();
-        }
-      } else {
-        resolve();
+  async parseActiveStatement(): Promise<AutocompleteParseResult | undefined> {
+    if (!this.activeStatement) {
+      return;
+    }
+    const activeStatementLocation = this.activeStatement.location;
+    const cursorPosition = this.editor.getCursorPosition();
+
+    if (
+      (activeStatementLocation.first_line - 1 < cursorPosition.row ||
+        (activeStatementLocation.first_line - 1 === cursorPosition.row &&
+          activeStatementLocation.first_column <= cursorPosition.column)) &&
+      (activeStatementLocation.last_line - 1 > cursorPosition.row ||
+        (activeStatementLocation.last_line - 1 === cursorPosition.row &&
+          activeStatementLocation.last_column >= cursorPosition.column))
+    ) {
+      const beforeCursor =
+        this.fixedPrefix() +
+        this.editor.session.getTextRange({
+          start: {
+            row: activeStatementLocation.first_line - 1,
+            column: activeStatementLocation.first_column
+          },
+          end: cursorPosition
+        });
+      const afterCursor =
+        this.editor.session.getTextRange({
+          start: cursorPosition,
+          end: {
+            row: activeStatementLocation.last_line - 1,
+            column: activeStatementLocation.last_column
+          }
+        }) + this.fixedPostfix();
+
+      try {
+        const parser = await sqlParserRepository.getAutocompleter(this.getDialect());
+        return parser.parseSql(beforeCursor, afterCursor);
+      } catch (err) {
+        console.warn(err);
       }
-    });
+    }
   }
 
-  async parseAll(): Promise<AutocompleteParseResult> {
-    return new Promise((resolve, reject) => {
-      const parserPromise = <Promise<AutocompleteParser>>(
-        sqlParserRepository.getAutocompleter(this.getDialect())
-      );
-      parserPromise
-        .then(autocompleteParser => {
-          resolve(
-            autocompleteParser.parseSql(
-              this.editor.getTextBeforeCursor(),
-              this.editor.getTextAfterCursor()
-            )
-          );
-        })
-        .catch(reject);
-    });
+  async parseAll(): Promise<AutocompleteParseResult | undefined> {
+    const parser = await sqlParserRepository.getAutocompleter(this.getDialect());
+    try {
+      return parser.parseSql(this.editor.getTextBeforeCursor(), this.editor.getTextAfterCursor());
+    } catch (err) {
+      console.warn(err);
+    }
   }
 
   async autocomplete(): Promise<AutocompleteParseResult | undefined> {