web-console: ACE editor refactoring (#16359)

* Move druid-sql completions to dsql mode

* Use font-size 12

* Convert ace-modes to typescript

* Move aceCompleters to class member

* Use namespace imports
diff --git a/web-console/lib/sql-docs.d.ts b/web-console/lib/sql-docs.d.ts
index a5af232..5948206 100644
--- a/web-console/lib/sql-docs.d.ts
+++ b/web-console/lib/sql-docs.d.ts
@@ -16,5 +16,5 @@
  * limitations under the License.
  */
 
-export const SQL_DATA_TYPES: Record<string, [runtime: string, description: string][]>;
+export const SQL_DATA_TYPES: Record<string, [runtime: string, description: string]>;
 export const SQL_FUNCTIONS: Record<string, [args: string, description: string][]>;
diff --git a/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap
new file mode 100644
index 0000000..2169512
--- /dev/null
+++ b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`makeDocHtml correctly formats helper HTML 1`] = `
+"
+<div class="doc-name">COUNT</div>
+<div class="doc-syntax">COUNT(*)</div>
+<div class="doc-description">Counts the number of things</div>"
+`;
diff --git a/web-console/src/ace-modes/dsql.js b/web-console/src/ace-modes/dsql.js
deleted file mode 100644
index f2349ee..0000000
--- a/web-console/src/ace-modes/dsql.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// This file a modified version of the file located at
-// https://github.com/thlorenz/brace/blob/master/mode/sql.js
-// Originally licensed under the MIT license (https://github.com/thlorenz/brace/blob/master/LICENSE)
-// This file was modified to make the list of keywords more closely adhere to what is found in DruidSQL
-
-var druidKeywords = require('../../lib/keywords');
-var druidFunctions = require('../../lib/sql-docs');
-
-ace.define(
-  'ace/mode/dsql_highlight_rules',
-  ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
-  function (acequire, exports, module) {
-    'use strict';
-
-    var oop = acequire('../lib/oop');
-    var TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;
-
-    var SqlHighlightRules = function () {
-      // Stuff like: 'with|select|from|where|and|or|group|by|order|limit|having|as|case|'
-      var keywords = druidKeywords.SQL_KEYWORDS.concat(druidKeywords.SQL_EXPRESSION_PARTS)
-        .join('|')
-        .replace(/\s/g, '|');
-
-      // Stuff like: 'true|false'
-      var builtinConstants = druidKeywords.SQL_CONSTANTS.join('|');
-
-      // Stuff like: 'avg|count|first|last|max|min'
-      var builtinFunctions = druidKeywords.SQL_DYNAMICS.concat(
-        Object.keys(druidFunctions.SQL_FUNCTIONS),
-      ).join('|');
-
-      // Stuff like: 'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp'
-      var dataTypes = Object.keys(druidFunctions.SQL_DATA_TYPES).join('|');
-
-      var keywordMapper = this.createKeywordMapper(
-        {
-          'support.function': builtinFunctions,
-          'keyword': keywords,
-          'constant.language': builtinConstants,
-          'storage.type': dataTypes,
-        },
-        'identifier',
-        true,
-      );
-
-      this.$rules = {
-        start: [
-          {
-            token: 'comment.issue',
-            regex: '--:ISSUE:.*$',
-          },
-          {
-            token: 'comment',
-            regex: '--.*$',
-          },
-          {
-            token: 'comment',
-            start: '/\\*',
-            end: '\\*/',
-          },
-          {
-            token: 'variable.column', // " quoted reference
-            regex: '".*?"',
-          },
-          {
-            token: 'string', // ' string literal
-            regex: "'.*?'",
-          },
-          {
-            token: 'constant.numeric', // float
-            regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b',
-          },
-          {
-            token: keywordMapper,
-            regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b',
-          },
-          {
-            token: 'keyword.operator',
-            regex: '\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=',
-          },
-          {
-            token: 'paren.lparen',
-            regex: '[\\(]',
-          },
-          {
-            token: 'paren.rparen',
-            regex: '[\\)]',
-          },
-          {
-            token: 'text',
-            regex: '\\s+',
-          },
-        ],
-      };
-      this.normalizeRules();
-    };
-
-    oop.inherits(SqlHighlightRules, TextHighlightRules);
-
-    exports.SqlHighlightRules = SqlHighlightRules;
-  },
-);
-
-ace.define(
-  'ace/mode/dsql',
-  ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/dsql_highlight_rules'],
-  function (acequire, exports, module) {
-    'use strict';
-
-    var oop = acequire('../lib/oop');
-    var TextMode = acequire('./text').Mode;
-    var SqlHighlightRules = acequire('./dsql_highlight_rules').SqlHighlightRules;
-
-    var Mode = function () {
-      this.HighlightRules = SqlHighlightRules;
-      this.$behaviour = this.$defaultBehaviour;
-    };
-    oop.inherits(Mode, TextMode);
-
-    (function () {
-      this.lineCommentStart = '--';
-
-      this.$id = 'ace/mode/dsql';
-    }).call(Mode.prototype);
-
-    exports.Mode = Mode;
-  },
-);
diff --git a/web-console/src/ace-modes/dsql.ts b/web-console/src/ace-modes/dsql.ts
new file mode 100644
index 0000000..e57d17d
--- /dev/null
+++ b/web-console/src/ace-modes/dsql.ts
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file a modified version of the file located at
+// https://github.com/thlorenz/brace/blob/master/mode/sql.js
+// Originally licensed under the MIT license (https://github.com/thlorenz/brace/blob/master/LICENSE)
+// This file was modified to make the list of keywords more closely adhere to what is found in DruidSQL
+
+import type { Ace } from 'ace-builds';
+import ace from 'ace-builds/src-noconflict/ace';
+
+import * as druidKeywords from '../../lib/keywords';
+import * as druidFunctions from '../../lib/sql-docs';
+
+import type { ItemDescription } from './make-doc-html';
+import { makeDocHtml } from './make-doc-html';
+
+ace.define(
+  'ace/mode/dsql_highlight_rules',
+  ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
+  function (acequire: any, exports: any) {
+    'use strict';
+
+    const oop = acequire('../lib/oop');
+    const TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;
+
+    const SqlHighlightRules = function (this: any) {
+      // Stuff like: 'with|select|from|where|and|or|group|by|order|limit|having|as|case|'
+      const keywords = druidKeywords.SQL_KEYWORDS.concat(druidKeywords.SQL_EXPRESSION_PARTS)
+        .join('|')
+        .replace(/\s/g, '|');
+
+      // Stuff like: 'true|false'
+      const builtinConstants = druidKeywords.SQL_CONSTANTS.join('|');
+
+      // Stuff like: 'avg|count|first|last|max|min'
+      const builtinFunctions = druidKeywords.SQL_DYNAMICS.concat(
+        Object.keys(druidFunctions.SQL_FUNCTIONS),
+      ).join('|');
+
+      // Stuff like: 'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp'
+      const dataTypes = Object.keys(druidFunctions.SQL_DATA_TYPES).join('|');
+
+      const keywordMapper = this.createKeywordMapper(
+        {
+          'support.function': builtinFunctions,
+          'keyword': keywords,
+          'constant.language': builtinConstants,
+          'storage.type': dataTypes,
+        },
+        'identifier',
+        true,
+      );
+
+      this.$rules = {
+        start: [
+          {
+            token: 'comment.issue',
+            regex: '--:ISSUE:.*$',
+          },
+          {
+            token: 'comment',
+            regex: '--.*$',
+          },
+          {
+            token: 'comment',
+            start: '/\\*',
+            end: '\\*/',
+          },
+          {
+            token: 'variable.column', // " quoted reference
+            regex: '".*?"',
+          },
+          {
+            token: 'string', // ' string literal
+            regex: "'.*?'",
+          },
+          {
+            token: 'constant.numeric', // float
+            regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b',
+          },
+          {
+            token: keywordMapper,
+            regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b',
+          },
+          {
+            token: 'keyword.operator',
+            regex: '\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|=',
+          },
+          {
+            token: 'paren.lparen',
+            regex: '[\\(]',
+          },
+          {
+            token: 'paren.rparen',
+            regex: '[\\)]',
+          },
+          {
+            token: 'text',
+            regex: '\\s+',
+          },
+        ],
+      };
+      this.normalizeRules();
+    };
+
+    oop.inherits(SqlHighlightRules, TextHighlightRules);
+
+    exports.SqlHighlightRules = SqlHighlightRules;
+  },
+);
+
+ace.define(
+  'ace/mode/dsql',
+  ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/dsql_highlight_rules'],
+  function (acequire: any, exports: any) {
+    'use strict';
+
+    const oop = acequire('../lib/oop');
+    const TextMode = acequire('./text').Mode;
+    const SqlHighlightRules = acequire('./dsql_highlight_rules').SqlHighlightRules;
+
+    const completions = ([] as Ace.Completion[]).concat(
+      druidKeywords.SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
+      druidKeywords.SQL_EXPRESSION_PARTS.map(v => ({
+        name: v,
+        value: v,
+        score: 0,
+        meta: 'keyword',
+      })),
+      druidKeywords.SQL_CONSTANTS.map(v => ({ name: v, value: v, score: 0, meta: 'constant' })),
+      druidKeywords.SQL_DYNAMICS.map(v => ({ name: v, value: v, score: 0, meta: 'dynamic' })),
+      Object.entries(druidFunctions.SQL_DATA_TYPES).map(([name, [runtime, description]]) => {
+        const item: ItemDescription = {
+          name,
+          description,
+          syntax: `Druid runtime type: ${runtime}`,
+        };
+        return {
+          name,
+          value: name,
+          score: 0,
+          meta: 'type',
+          docHTML: makeDocHtml(item),
+          docText: description,
+        };
+      }),
+      Object.entries(druidFunctions.SQL_FUNCTIONS).flatMap(([name, versions]) => {
+        return versions.map(([args, description]) => {
+          const item = { name, description, syntax: `${name}(${args})` };
+          return {
+            name,
+            value: versions.length > 1 ? `${name}(${args})` : name,
+            score: 1100, // Use a high score to appear over the 'local' suggestions that have a score of 1000
+            meta: 'function',
+            docHTML: makeDocHtml(item),
+            docText: description,
+            completer: {
+              insertMatch: (editor: any, data: any) => {
+                editor.completer.insertMatch({ value: data.name });
+              },
+            },
+          } as Ace.Completion;
+        });
+      }),
+    );
+
+    const Mode = function (this: any) {
+      this.HighlightRules = SqlHighlightRules;
+      this.$behaviour = this.$defaultBehaviour;
+      this.$id = 'ace/mode/dsql';
+
+      this.lineCommentStart = '--';
+      this.getCompletions = () => completions;
+    };
+    oop.inherits(Mode, TextMode);
+
+    exports.Mode = Mode;
+  },
+);
diff --git a/web-console/src/ace-modes/hjson.js b/web-console/src/ace-modes/hjson.ts
similarity index 89%
rename from web-console/src/ace-modes/hjson.js
rename to web-console/src/ace-modes/hjson.ts
index 316ce98..1c58a5c 100644
--- a/web-console/src/ace-modes/hjson.js
+++ b/web-console/src/ace-modes/hjson.ts
@@ -22,16 +22,18 @@
 // This file was modified to remove the folding functionality that did not play nice when loaded along side the
 // sql mode (which does not have any folding function)
 
+import ace from 'ace-builds/src-noconflict/ace';
+
 ace.define(
   'ace/mode/hjson_highlight_rules',
   ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
-  function (acequire, exports, module) {
+  function (acequire: any, exports: any) {
     'use strict';
 
-    var oop = acequire('../lib/oop');
-    var TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;
+    const oop = acequire('../lib/oop');
+    const TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;
 
-    var HjsonHighlightRules = function () {
+    const HjsonHighlightRules = function (this: any) {
       this.$rules = {
         'start': [
           {
@@ -107,7 +109,7 @@
         '#keyname': [
           {
             token: 'keyword',
-            regex: /(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/,
+            regex: /(?:[^,{[}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/,
           },
         ],
         '#mstring': [
@@ -166,7 +168,7 @@
         '#rootObject': [
           {
             token: 'paren',
-            regex: /(?=\s*(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*:)/,
+            regex: /(?=\s*(?:[^,{[}\]\s]+|"(?:[^"\\]|\\.)*")\s*:)/,
             push: [
               {
                 token: 'paren.rparen',
@@ -205,7 +207,7 @@
               },
               {
                 token: 'constant.language.escape',
-                regex: /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/,
+                regex: /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/,
               },
               {
                 token: 'invalid.illegal',
@@ -220,7 +222,7 @@
         '#ustring': [
           {
             token: 'string',
-            regex: /\b[^:,0-9\-\{\[\}\]\s].*$/,
+            regex: /\b[^:,0-9\-{[}\]\s].*$/,
           },
         ],
         '#value': [
@@ -277,19 +279,19 @@
     'ace/mode/text',
     'ace/mode/hjson_highlight_rules',
   ],
-  function (acequire, exports, module) {
+  function (acequire: any, exports: any) {
     'use strict';
 
-    var oop = acequire('../lib/oop');
-    var TextMode = acequire('./text').Mode;
-    var HjsonHighlightRules = acequire('./hjson_highlight_rules').HjsonHighlightRules;
+    const oop = acequire('../lib/oop');
+    const TextMode = acequire('./text').Mode;
+    const HjsonHighlightRules = acequire('./hjson_highlight_rules').HjsonHighlightRules;
 
-    var Mode = function () {
+    const Mode = function (this: any) {
       this.HighlightRules = HjsonHighlightRules;
     };
     oop.inherits(Mode, TextMode);
 
-    (function () {
+    (function (this: any) {
       this.lineCommentStart = '//';
       this.blockComment = { start: '/*', end: '*/' };
       this.$id = 'ace/mode/hjson';
diff --git a/web-console/src/ace-modes/make-doc-html.spec.ts b/web-console/src/ace-modes/make-doc-html.spec.ts
new file mode 100644
index 0000000..35172d0
--- /dev/null
+++ b/web-console/src/ace-modes/make-doc-html.spec.ts
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { makeDocHtml } from './make-doc-html';
+
+describe('makeDocHtml', () => {
+  it('correctly formats helper HTML', () => {
+    expect(
+      makeDocHtml({
+        name: 'COUNT',
+        syntax: 'COUNT(*)',
+        description: 'Counts the number of things',
+      }),
+    ).toMatchSnapshot();
+  });
+});
diff --git a/web-console/src/ace-modes/make-doc-html.ts b/web-console/src/ace-modes/make-doc-html.ts
new file mode 100644
index 0000000..996541b
--- /dev/null
+++ b/web-console/src/ace-modes/make-doc-html.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import escape from 'lodash.escape';
+
+export interface ItemDescription {
+  name: string;
+  syntax: string;
+  description: string;
+}
+
+export function makeDocHtml(item: ItemDescription) {
+  return `
+<div class="doc-name">${item.name}</div>
+<div class="doc-syntax">${escape(item.syntax)}</div>
+<div class="doc-description">${item.description}</div>`;
+}
diff --git a/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap b/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap
index e96b7f6..b71b692 100644
--- a/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap
+++ b/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap
@@ -5,7 +5,7 @@
   class="json-input"
 >
   <div
-    class=" ace_editor ace_hidpi ace-tm"
+    class=" ace_editor ace_hidpi ace-solarized-dark ace_dark"
     id="ace-editor"
     style="width: 100%; height: 8vh; font-size: 12px;"
   >
@@ -104,7 +104,7 @@
   class="json-input"
 >
   <div
-    class=" ace_editor ace_hidpi ace-tm"
+    class=" ace_editor ace_hidpi ace-solarized-dark ace_dark"
     id="ace-editor"
     style="width: 100%; height: 8vh; font-size: 12px;"
   >
diff --git a/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap b/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap
index cc8b602..cc09716 100644
--- a/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap
+++ b/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap
@@ -58,7 +58,7 @@
           </button>
         </div>
         <div
-          class=" ace_editor ace_hidpi ace-tm spec-dialog-textarea placeholder-padding"
+          class=" ace_editor ace_hidpi ace-solarized-dark ace_dark spec-dialog-textarea placeholder-padding"
           id="ace-editor"
           style="width: 100%; height: 500px; font-size: 12px;"
         >
@@ -252,7 +252,7 @@
           </button>
         </div>
         <div
-          class=" ace_editor ace_hidpi ace-tm spec-dialog-textarea placeholder-padding"
+          class=" ace_editor ace_hidpi ace-solarized-dark ace_dark spec-dialog-textarea placeholder-padding"
           id="ace-editor"
           style="width: 100%; height: 500px; font-size: 12px;"
         >
diff --git a/web-console/src/setup-tests.ts b/web-console/src/setup-tests.ts
index e75cb3b..518045d 100644
--- a/web-console/src/setup-tests.ts
+++ b/web-console/src/setup-tests.ts
@@ -17,6 +17,7 @@
  */
 
 import 'core-js/stable';
+import './bootstrap/ace';
 
 import { UrlBaser } from './singletons';
 
diff --git a/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap b/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap
index bbbca4b..c0332ad 100644
--- a/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap
@@ -122,7 +122,7 @@
                 enableLiveAutocompletion={false}
                 enableSnippets={false}
                 focus={false}
-                fontSize={13}
+                fontSize={12}
                 height="100%"
                 highlightActiveLine={true}
                 maxLines={null}
@@ -220,7 +220,7 @@
                 enableLiveAutocompletion={false}
                 enableSnippets={false}
                 focus={false}
-                fontSize={13}
+                fontSize={12}
                 height="100%"
                 highlightActiveLine={true}
                 maxLines={null}
@@ -348,7 +348,7 @@
           enableLiveAutocompletion={false}
           enableSnippets={false}
           focus={false}
-          fontSize={13}
+          fontSize={12}
           height="100%"
           highlightActiveLine={true}
           maxLines={null}
diff --git a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
index 3c535ed..4bab7e7 100644
--- a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
+++ b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx
@@ -131,7 +131,7 @@
             theme="solarized_dark"
             className="query-string"
             name="ace-editor"
-            fontSize={13}
+            fontSize={12}
             width="100%"
             height="100%"
             showGutter
diff --git a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap
index 0efa8f7..902b465 100644
--- a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap
@@ -1,12 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`FlexibleQueryInput correctly formats helper HTML 1`] = `
-"
-<div class="doc-name">COUNT</div>
-<div class="doc-syntax">COUNT(*)</div>
-<div class="doc-description">Counts the number of things</div>"
-`;
-
 exports[`FlexibleQueryInput matches snapshot 1`] = `
 <div
   class="flexible-query-input"
@@ -15,9 +8,9 @@
     class="ace-container query-idle"
   >
     <div
-      class=" ace_editor ace_hidpi ace-tm placeholder-padding no-background ace_focus"
+      class=" ace_editor ace_hidpi ace-solarized-dark ace_dark placeholder-padding no-background ace_focus"
       id="ace-editor"
-      style="width: 100%; height: 200px; font-size: 13px;"
+      style="width: 100%; height: 200px; font-size: 12px;"
     >
       <textarea
         autocapitalize="off"
diff --git a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx
index 9a2b5b2..06b7124 100644
--- a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx
+++ b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx
@@ -30,14 +30,4 @@
     const { container } = render(sqlControl);
     expect(container.firstChild).toMatchSnapshot();
   });
-
-  it('correctly formats helper HTML', () => {
-    expect(
-      FlexibleQueryInput.makeDocHtml({
-        name: 'COUNT',
-        syntax: 'COUNT(*)',
-        description: 'Counts the number of things',
-      }),
-    ).toMatchSnapshot();
-  });
 });
diff --git a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
index e9eb368..2417b64 100644
--- a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
+++ b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx
@@ -24,17 +24,9 @@
 import ace from 'ace-builds';
 import classNames from 'classnames';
 import debounce from 'lodash.debounce';
-import escape from 'lodash.escape';
 import React from 'react';
 import AceEditor from 'react-ace';
 
-import {
-  SQL_CONSTANTS,
-  SQL_DYNAMICS,
-  SQL_EXPRESSION_PARTS,
-  SQL_KEYWORDS,
-} from '../../../../lib/keywords';
-import { SQL_DATA_TYPES, SQL_FUNCTIONS } from '../../../../lib/sql-docs';
 import { AppToaster } from '../../../singletons';
 import { AceEditorStateCache } from '../../../singletons/ace-editor-state-cache';
 import type { ColumnMetadata, QuerySlice, RowColumn } from '../../../utils';
@@ -46,18 +38,6 @@
 
 const V_PADDING = 10;
 
-const COMPLETER = {
-  insertMatch: (editor: any, data: Ace.Completion) => {
-    editor.completer.insertMatch({ value: data.name });
-  },
-};
-
-interface ItemDescription {
-  name: string;
-  syntax: string;
-  description: string;
-}
-
 export interface FlexibleQueryInputProps {
   queryString: string;
   onQueryStringChange?: (newQueryString: string) => void;
@@ -87,85 +67,29 @@
   FlexibleQueryInputProps,
   FlexibleQueryInputState
 > {
+  static aceTheme = 'solarized_dark';
+
   private aceEditor: Ace.Editor | undefined;
   private lastFoundQueries: QuerySlice[] = [];
   private highlightFoundQuery: { row: number; marker: number } | undefined;
 
-  static replaceDefaultAutoCompleter(): void {
-    if (!langTools) return;
-
-    const keywordList = ([] as Ace.Completion[]).concat(
-      SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
-      SQL_EXPRESSION_PARTS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
-      SQL_CONSTANTS.map(v => ({ name: v, value: v, score: 0, meta: 'constant' })),
-      SQL_DYNAMICS.map(v => ({ name: v, value: v, score: 0, meta: 'dynamic' })),
-      Object.entries(SQL_DATA_TYPES).map(([name, [runtime, description]]) => ({
-        name,
-        value: name,
-        score: 0,
-        meta: 'type',
-        syntax: `Druid runtime type: ${runtime}`,
-        description,
-      })),
-    );
-
-    langTools.setCompleters([
-      langTools.snippetCompleter,
-      langTools.textCompleter,
-      {
-        getCompletions: (
-          _state: string,
-          _session: Ace.EditSession,
-          _pos: Ace.Point,
-          _prefix: string,
-          callback: any,
-        ) => {
-          return callback(null, keywordList);
-        },
-        getDocTooltip: (item: any) => {
-          if (item.meta === 'type') {
-            item.docHTML = FlexibleQueryInput.makeDocHtml(item);
-          }
-        },
+  private readonly aceCompleters: Ace.Completer[] = [
+    // Prepend with default completers to ensure completion data from
+    // editing mode (e.g. 'dsql') is included in addition to local completions
+    langTools.snippetCompleter,
+    langTools.keyWordCompleter,
+    langTools.textCompleter,
+    // Local completions
+    {
+      getCompletions: (_state, session, pos, prefix, callback) => {
+        const charBeforePrefix = session.getLine(pos.row)[pos.column - prefix.length - 1];
+        callback(
+          null,
+          charBeforePrefix === '"' ? this.state.unquotedCompletions : this.state.quotedCompletions,
+        );
       },
-    ]);
-  }
-
-  static addFunctionAutoCompleter(): void {
-    if (!langTools) return;
-
-    const functionList: Ace.Completion[] = Object.entries(SQL_FUNCTIONS).flatMap(
-      ([name, versions]) => {
-        return versions.map(([args, description]) => ({
-          name: name,
-          value: versions.length > 1 ? `${name}(${args})` : name,
-          score: 1100, // Use a high score to appear over the 'local' suggestions that have a score of 1000
-          meta: 'function',
-          syntax: `${name}(${args})`,
-          description,
-          completer: COMPLETER,
-        }));
-      },
-    );
-
-    langTools.addCompleter({
-      getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => {
-        callback(null, functionList);
-      },
-      getDocTooltip: (item: any) => {
-        if (item.meta === 'function') {
-          item.docHTML = FlexibleQueryInput.makeDocHtml(item);
-        }
-      },
-    });
-  }
-
-  static makeDocHtml(item: ItemDescription) {
-    return `
-<div class="doc-name">${item.name}</div>
-<div class="doc-syntax">${escape(item.syntax)}</div>
-<div class="doc-description">${item.description}</div>`;
-  }
+    },
+  ];
 
   static getCompletions(
     columnMetadata: readonly ColumnMetadata[],
@@ -186,7 +110,7 @@
       ).map(v => ({
         value: quote ? String(T(v)) : v,
         score: 49,
-        meta: 'datasource',
+        meta: 'table',
       })),
       uniq(
         columnMetadata
@@ -244,28 +168,6 @@
   }
 
   componentDidMount(): void {
-    FlexibleQueryInput.replaceDefaultAutoCompleter();
-    FlexibleQueryInput.addFunctionAutoCompleter();
-    if (langTools) {
-      langTools.addCompleter({
-        getCompletions: (
-          _state: string,
-          session: Ace.EditSession,
-          pos: Ace.Point,
-          prefix: string,
-          callback: any,
-        ) => {
-          const charBeforePrefix = session.getLine(pos.row)[pos.column - prefix.length - 1];
-          callback(
-            null,
-            charBeforePrefix === '"'
-              ? this.state.unquotedCompletions
-              : this.state.quotedCompletions,
-          );
-        },
-      });
-    }
-
     this.markQueries();
   }
 
@@ -345,32 +247,32 @@
     return (
       <AceEditor
         mode={jsonMode ? 'hjson' : 'dsql'}
-        theme="solarized_dark"
+        theme={FlexibleQueryInput.aceTheme}
         className={classNames(
           'placeholder-padding',
           this.props.leaveBackground ? undefined : 'no-background',
         )}
+        // 'react-ace' types are incomplete. Completion options can accept completers array.
+        enableBasicAutocompletion={jsonMode ? true : (this.aceCompleters as any)}
+        enableLiveAutocompletion={jsonMode ? true : (this.aceCompleters as any)}
         name="ace-editor"
         onChange={this.handleChange}
         focus
-        fontSize={13}
+        fontSize={12}
         width="100%"
         height={editorHeight + 'px'}
         showGutter={showGutter}
         showPrintMargin={false}
+        tabSize={2}
         value={queryString}
         readOnly={!onQueryStringChange}
         editorProps={{
           $blockScrolling: Infinity,
         }}
         setOptions={{
-          enableBasicAutocompletion: !jsonMode,
-          enableLiveAutocompletion: !jsonMode,
           showLineNumbers: true,
-          tabSize: 2,
           newLineMode: 'unix' as any, // This type is specified incorrectly in AceEditor
         }}
-        style={{}}
         placeholder={placeholder || 'SELECT * FROM ...'}
         onLoad={(editor: Ace.Editor) => {
           editor.renderer.setPadding(V_PADDING);
diff --git a/web-console/src/views/workbench-view/workbench-history-dialog/workbench-history-dialog.tsx b/web-console/src/views/workbench-view/workbench-history-dialog/workbench-history-dialog.tsx
index e5d83cc..2d85a18 100644
--- a/web-console/src/views/workbench-view/workbench-history-dialog/workbench-history-dialog.tsx
+++ b/web-console/src/views/workbench-view/workbench-history-dialog/workbench-history-dialog.tsx
@@ -78,7 +78,7 @@
           theme="solarized_dark"
           className="query-string"
           name="ace-editor"
-          fontSize={13}
+          fontSize={12}
           width="100%"
           showGutter
           showPrintMargin={false}