From 4f53beba510fb41e09a1787eeed66bdd28e44cdf Mon Sep 17 00:00:00 2001 From: Dana Lee Date: Wed, 5 Mar 2025 15:53:47 +0100 Subject: [PATCH] fix(editor): Match nodes for autocomplete --- .../typescript/client/completions.ts | 27 +++++++++---- .../client/tests/completions.test.ts | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/tests/completions.test.ts diff --git a/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/completions.ts b/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/completions.ts index 6a6cac8282..ff427c3cbe 100644 --- a/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/completions.ts +++ b/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/completions.ts @@ -1,5 +1,6 @@ import { escapeMappingString } from '@/utils/mappingUtils'; import { + type CompletionContext, insertCompletionText, pickedCompletion, type Completion, @@ -16,16 +17,21 @@ import { blockCommentSnippet, snippets } from './snippets'; const START_CHARACTERS = ['"', "'", '(', '.', '@']; const START_CHARACTERS_REGEX = /[\.\(\'\"\@]/; -export const typescriptCompletionSource: CompletionSource = async (context) => { - const { worker } = context.state.facet(typescriptWorkerFacet); - +export const matchText = (context: CompletionContext) => { let word = context.matchBefore(START_CHARACTERS_REGEX); - if (!word?.text) { - word = context.matchBefore(/[\"\'].*/); - } if (!word?.text) { word = context.matchBefore(/[\$\w]+/); } + if (!word?.text) { + word = context.matchBefore(/[\"\'].*/); + } + return word; +}; + +export const typescriptCompletionSource: CompletionSource = async (context) => { + const { worker } = context.state.facet(typescriptWorkerFacet); + + const word = matchText(context); const blockComment = context.matchBefore(/\/\*?\*?/); if (blockComment) { @@ -46,7 +52,14 @@ export const typescriptCompletionSource: CompletionSource = async (context) => { if (isGlobal) { options = options .flatMap((opt) => { - if (opt.label === '$') { + let word = context.matchBefore(START_CHARACTERS_REGEX); + if (!word?.text) { + word = context.matchBefore(/[\"\'].*/); + } + if (!word?.text) { + word = context.matchBefore(/[\$\w]+/); + } + if (opt.label === '$()') { return [ opt, ...autocompletableNodeNames().map((name) => ({ diff --git a/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/tests/completions.test.ts b/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/tests/completions.test.ts new file mode 100644 index 0000000000..e2b5e886d0 --- /dev/null +++ b/packages/frontend/editor-ui/src/plugins/codemirror/typescript/client/tests/completions.test.ts @@ -0,0 +1,39 @@ +import { matchText } from '../completions'; +import { CompletionContext } from '@codemirror/autocomplete'; +import { EditorState } from '@codemirror/state'; +import { n8nLang } from '@/plugins/codemirror/n8nLang'; + +describe('matchText', () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + it('should match on previous nodes', () => { + const previousNodes = [ + { doc: '$(|)', expected: '(' }, + { doc: '$("|', expected: '"' }, + { doc: "$('|", expected: "'" }, + { doc: '$(""|', expected: '"' }, + { doc: "$('|", expected: "'" }, + { doc: '$("|")', expected: '"' }, + { doc: "$('|')", expected: "'" }, + { doc: '$("|)', expected: '"' }, + { doc: '$("No|")', expected: 'No' }, + { doc: "$('No|')", expected: 'No' }, + { doc: '$("N|")', expected: 'N' }, + { doc: "$('N|')", expected: 'N' }, + ]; + + previousNodes.forEach((node) => { + const cursorPosition = node.doc.indexOf('|'); + const state = EditorState.create({ + doc: node.doc.replace('|', ''), + selection: { anchor: cursorPosition }, + extensions: [n8nLang()], + }); + const context = new CompletionContext(state, cursorPosition, false); + + expect(matchText(context)?.text).toEqual(node.expected); + }); + }); +});