From 9b4618dd5e58337b73e8804f11d7aca9a9bf1dc3 Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Thu, 7 Mar 2024 17:01:05 +0100 Subject: [PATCH] feat(editor): Add sections to autocomplete dropdown (#8720) Co-authored-by: Giulio Andreini --- cypress/e2e/13-pinning.cy.ts | 4 +- .../design-system/src/css/_tokens.dark.scss | 4 +- packages/design-system/src/css/_tokens.scss | 6 +- packages/editor-ui/src/Interface.ts | 4 + .../CodeNodeEditor/baseExtensions.ts | 61 +-- .../components/CodeNodeEditor/completer.ts | 1 + .../ExpressionEditorModalInput.vue | 31 +- .../src/components/HtmlEditor/HtmlEditor.vue | 27 +- .../InlineExpressionEditorInput.vue | 39 +- .../src/components/JsEditor/JsEditor.vue | 19 +- .../src/components/JsonEditor/JsonEditor.vue | 20 +- .../src/components/SqlEditor/SqlEditor.vue | 20 +- .../editor-ui/src/mixins/completionManager.ts | 4 + .../editor-ui/src/mixins/expressionManager.ts | 27 +- .../completions/__tests__/completions.test.ts | 288 ++++++++----- .../codemirror/completions/constants.ts | 192 +++++++++ .../completions/datatype.completions.ts | 383 ++++++++++++++---- .../completions/dollar.completions.ts | 40 +- .../luxon.instance.docs.ts | 100 +++++ .../plugins/codemirror/completions/types.ts | 8 +- .../plugins/codemirror/completions/utils.ts | 124 ++++-- .../src/plugins/codemirror/keymap.ts | 76 ++++ .../src/plugins/codemirror/n8nLang.ts | 3 + .../codemirror/resolvableHighlighter.ts | 44 +- packages/editor-ui/src/plugins/i18n/index.ts | 6 +- .../src/plugins/i18n/locales/en.json | 20 + .../src/styles/plugins/_codemirror.scss | 87 ++-- packages/editor-ui/src/utils/expressions.ts | 5 +- .../workflow/src/Extensions/DateExtensions.ts | 14 + .../workflow/src/Extensions/Extensions.ts | 8 +- .../src/Extensions/ObjectExtensions.ts | 24 ++ .../src/Extensions/StringExtensions.ts | 37 +- .../src/NativeMethods/Object.Methods.ts | 23 +- .../src/NativeMethods/String.methods.ts | 19 + .../ObjectExtensions.test.ts | 8 + 35 files changed, 1308 insertions(+), 468 deletions(-) create mode 100644 packages/editor-ui/src/plugins/codemirror/completions/constants.ts create mode 100644 packages/editor-ui/src/plugins/codemirror/keymap.ts diff --git a/cypress/e2e/13-pinning.cy.ts b/cypress/e2e/13-pinning.cy.ts index 1643363a5f..e0b3ef3f23 100644 --- a/cypress/e2e/13-pinning.cy.ts +++ b/cypress/e2e/13-pinning.cy.ts @@ -195,5 +195,7 @@ function setExpressionOnStringValueInSet(expression: string) { ndv.getters .inlineExpressionEditorInput() .clear() - .type(expression, { parseSpecialCharSequences: false }); + .type(expression, { parseSpecialCharSequences: false }) + // hide autocomplete + .type('{esc}'); } diff --git a/packages/design-system/src/css/_tokens.dark.scss b/packages/design-system/src/css/_tokens.dark.scss index 918fc8a18d..53104dd036 100644 --- a/packages/design-system/src/css/_tokens.dark.scss +++ b/packages/design-system/src/css/_tokens.dark.scss @@ -69,7 +69,7 @@ --color-sticky-background-7: var(--prim-gray-740); --color-sticky-border-7: var(--prim-gray-670); - // Expressions + // Expressions and autocomplete --color-valid-resolvable-foreground: var(--prim-color-alt-a-tint-300); --color-valid-resolvable-background: var(--prim-color-alt-a-alpha-025); --color-invalid-resolvable-foreground: var(--prim-color-alt-c-tint-250); @@ -78,6 +78,8 @@ --color-pending-resolvable-background: var(--prim-gray-70-alpha-01); --color-expression-editor-background: var(--prim-gray-800); --color-expression-syntax-example: var(--prim-gray-670); + --color-autocomplete-item-selected: var(--prim-color-secondary-tint-200); + --color-autocomplete-section-header-border: var(--prim-gray-540); // Code --color-code-tags-string: var(--prim-color-alt-f-tint-150); diff --git a/packages/design-system/src/css/_tokens.scss b/packages/design-system/src/css/_tokens.scss index aa12bc6aed..0487053fe1 100644 --- a/packages/design-system/src/css/_tokens.scss +++ b/packages/design-system/src/css/_tokens.scss @@ -102,7 +102,7 @@ --color-sticky-background-7: var(--prim-gray-10); --color-sticky-border-7: var(--prim-gray-120); - // Expressions + // Expressions and autocomplete --color-valid-resolvable-foreground: var(--prim-color-alt-a); --color-valid-resolvable-background: var(--prim-color-alt-a-tint-500); --color-invalid-resolvable-foreground: var(--prim-color-alt-c); @@ -111,6 +111,8 @@ --color-pending-resolvable-background: var(--prim-gray-40); --color-expression-editor-background: var(--prim-gray-0); --color-expression-syntax-example: var(--prim-gray-40); + --color-autocomplete-item-selected: var(--color-secondary); + --color-autocomplete-section-header-border: var(--color-foreground-light); // Code --color-code-tags-string: var(--prim-color-alt-f); @@ -138,8 +140,6 @@ --color-code-gutterBackground: var(--prim-gray-0); --color-code-gutterForeground: var(--prim-gray-320); --color-code-tags-comment: var(--prim-gray-420); - --color-autocomplete-selected-background: var(--prim-color-alt-e); - --color-autocomplete-selected-font: var(--prim-gray-0); // Variables --color-variables-usage-font: var(--color-success); diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 870e4d3dfc..58d1b8d914 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -107,6 +107,10 @@ declare global { }; // eslint-disable-next-line @typescript-eslint/naming-convention Cypress: unknown; + + Sentry?: { + captureException: (error: Error, metadata?: unknown) => void; + }; } } diff --git a/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts b/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts index bed55df677..a9b00a0b82 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/baseExtensions.ts @@ -6,24 +6,19 @@ import { highlightSpecialChars, keymap, lineNumbers, - type KeyBinding, } from '@codemirror/view'; import { bracketMatching, foldGutter, indentOnInput } from '@codemirror/language'; -import { acceptCompletion, selectedCompletion } from '@codemirror/autocomplete'; -import { - history, - indentLess, - indentMore, - insertNewlineAndIndent, - toggleComment, - redo, - deleteCharBackward, - undo, -} from '@codemirror/commands'; +import { history, toggleComment, deleteCharBackward } from '@codemirror/commands'; import { lintGutter } from '@codemirror/lint'; import { type Extension, Prec } from '@codemirror/state'; import { codeInputHandler } from '@/plugins/codemirror/inputHandlers/code.inputHandler'; +import { + autocompleteKeyMap, + enterKeyMap, + historyKeyMap, + tabKeyMap, +} from '@/plugins/codemirror/keymap'; export const readOnlyEditorExtensions: readonly Extension[] = [ lineNumbers(), @@ -31,42 +26,6 @@ export const readOnlyEditorExtensions: readonly Extension[] = [ highlightSpecialChars(), ]; -export const tabKeyMap: KeyBinding[] = [ - { - any(editor, event) { - if (event.key === 'Tab' || (event.key === 'Escape' && selectedCompletion(editor.state))) { - event.stopPropagation(); - } - - return false; - }, - }, - { - key: 'Tab', - run: (editor) => { - if (selectedCompletion(editor.state)) { - return acceptCompletion(editor); - } - - return indentMore(editor); - }, - }, - { key: 'Shift-Tab', run: indentLess }, -]; - -export const enterKeyMap: KeyBinding[] = [ - { - key: 'Enter', - run: (editor) => { - if (selectedCompletion(editor.state)) { - return acceptCompletion(editor); - } - - return insertNewlineAndIndent(editor); - }, - }, -]; - export const writableEditorExtensions: readonly Extension[] = [ history(), lintGutter(), @@ -79,11 +38,11 @@ export const writableEditorExtensions: readonly Extension[] = [ highlightActiveLineGutter(), Prec.highest( keymap.of([ - ...tabKeyMap, + ...tabKeyMap(), ...enterKeyMap, + ...autocompleteKeyMap, + ...historyKeyMap, { key: 'Mod-/', run: toggleComment }, - { key: 'Mod-z', run: undo }, - { key: 'Mod-Shift-z', run: redo }, { key: 'Backspace', run: deleteCharBackward, shift: deleteCharBackward }, ]), ), diff --git a/packages/editor-ui/src/components/CodeNodeEditor/completer.ts b/packages/editor-ui/src/components/CodeNodeEditor/completer.ts index 9d64f92a49..6cb2866a75 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/completer.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/completer.ts @@ -37,6 +37,7 @@ export const completerExtension = defineComponent({ } return autocompletion({ + icons: false, compareCompletions: (a: Completion, b: Completion) => { if (/\.json$|id$|id['"]\]$/.test(a.label)) return 0; diff --git a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue index a08d3d964a..74eaa2a39f 100644 --- a/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue +++ b/packages/editor-ui/src/components/ExpressionEditorModal/ExpressionEditorModalInput.vue @@ -6,18 +6,24 @@ import { defineComponent } from 'vue'; import { EditorView, keymap } from '@codemirror/view'; import { EditorState, Prec } from '@codemirror/state'; -import { history, redo, undo } from '@codemirror/commands'; +import { history } from '@codemirror/commands'; import { expressionManager } from '@/mixins/expressionManager'; import { completionManager } from '@/mixins/completionManager'; import { expressionInputHandler } from '@/plugins/codemirror/inputHandlers/expression.inputHandler'; -import { n8nLang } from '@/plugins/codemirror/n8nLang'; +import { n8nAutocompletion, n8nLang } from '@/plugins/codemirror/n8nLang'; import { highlighter } from '@/plugins/codemirror/resolvableHighlighter'; import { inputTheme } from './theme'; import { forceParse } from '@/utils/forceParse'; -import { acceptCompletion, autocompletion } from '@codemirror/autocomplete'; +import { completionStatus } from '@codemirror/autocomplete'; import type { IVariableItemSelected } from '@/Interface'; +import { + autocompleteKeyMap, + enterKeyMap, + historyKeyMap, + tabKeyMap, +} from '@/plugins/codemirror/keymap'; export default defineComponent({ name: 'ExpressionEditorModalInput', @@ -44,13 +50,15 @@ export default defineComponent({ mounted() { const extensions = [ inputTheme(), - autocompletion(), Prec.highest( keymap.of([ - { key: 'Tab', run: acceptCompletion }, + ...tabKeyMap(), + ...historyKeyMap, + ...enterKeyMap, + ...autocompleteKeyMap, { - any: (_: EditorView, event: KeyboardEvent) => { - if (event.key === 'Escape') { + any: (view, event) => { + if (event.key === 'Escape' && completionStatus(view.state) === null) { event.stopPropagation(); this.$emit('close'); } @@ -58,11 +66,10 @@ export default defineComponent({ return false; }, }, - { key: 'Mod-z', run: undo }, - { key: 'Mod-Shift-z', run: redo }, ]), ), n8nLang(), + n8nAutocompletion(), history(), expressionInputHandler(), EditorView.lineWrapping, @@ -71,7 +78,11 @@ export default defineComponent({ EditorView.contentAttributes.of({ 'data-gramm': 'false' }), // disable grammarly EditorView.domEventHandlers({ scroll: forceParse }), EditorView.updateListener.of((viewUpdate) => { - if (!this.editor || !viewUpdate.docChanged) return; + if (!this.editor) return; + + this.completionStatus = completionStatus(viewUpdate.view.state); + + if (!viewUpdate.docChanged) return; this.editorState = this.editor.state; diff --git a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue index 01d4ea5a5c..59f66dabd0 100644 --- a/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue +++ b/packages/editor-ui/src/components/HtmlEditor/HtmlEditor.vue @@ -6,8 +6,7 @@