From 79ae159329718780cf755225b4d2cbabc2ab51fb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 11 Mar 2024 21:10:53 +0000 Subject: [PATCH] Add code mirror completions for snippets --- .../common/CodeEditor/CodeEditor.svelte | 67 +++++++++++++++---- .../src/components/common/CodeEditor/index.js | 38 +++++++++++ .../common/bindings/BindingPanel.svelte | 2 + 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index b63c646ed4..1ddc3d802a 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -83,8 +83,8 @@ }) } - // For handlebars only. - const bindStyle = new MatchDecorator({ + // Match decoration for HBS bindings + const hbsMatchDeco = new MatchDecorator({ regexp: FIND_ANY_HBS_REGEX, decoration: () => { return Decoration.mark({ @@ -95,12 +95,35 @@ }) }, }) - - let plugin = ViewPlugin.define( + const hbsMatchDecoPlugin = ViewPlugin.define( view => ({ - decorations: bindStyle.createDeco(view), + decorations: hbsMatchDeco.createDeco(view), update(u) { - this.decorations = bindStyle.updateDeco(u, this.decorations) + this.decorations = hbsMatchDeco.updateDeco(u, this.decorations) + }, + }), + { + decorations: v => v.decorations, + } + ) + + // Match decoration for snippets + const snippetMatchDeco = new MatchDecorator({ + regexp: /snippets.[^\s(]+/g, + decoration: () => { + return Decoration.mark({ + tag: "span", + attributes: { + class: "snippet-wrap", + }, + }) + }, + }) + const snippetMatchDecoPlugin = ViewPlugin.define( + view => ({ + decorations: snippetMatchDeco.createDeco(view), + update(u) { + this.decorations = snippetMatchDeco.updateDeco(u, this.decorations) }, }), { @@ -142,7 +165,6 @@ const buildBaseExtensions = () => { return [ - ...(mode.name === "handlebars" ? [plugin] : []), drawSelection(), dropCursor(), bracketMatching(), @@ -165,7 +187,10 @@ override: [...completions], closeOnBlur: true, icons: false, - optionClass: () => "autocomplete-option", + optionClass: completion => + completion.simple + ? "autocomplete-option-simple" + : "autocomplete-option", }) ) complete.push( @@ -191,18 +216,23 @@ view.dispatch(tr) return true } - return false }) ) } + // JS only plugins if (mode.name === "javascript") { + complete.push(snippetMatchDecoPlugin) complete.push(javascript()) if (!readonly) { complete.push(highlightWhitespace()) } } + // HBS only plugins + else { + complete.push(hbsMatchDecoPlugin) + } if (placeholder) { complete.push(placeholderFn(placeholder)) @@ -376,9 +406,12 @@ font-style: italic; } - /* Highlight bindings */ + /* Highlight bindings and snippets */ .code-editor :global(.binding-wrap) { - color: var(--spectrum-global-color-blue-700); + color: var(--spectrum-global-color-blue-700) !important; + } + .code-editor :global(.snippet-wrap *) { + color: #61afef !important; } /* Completion popover */ @@ -407,7 +440,8 @@ } /* Completion item container */ - .code-editor :global(.autocomplete-option) { + .code-editor :global(.autocomplete-option), + .code-editor :global(.autocomplete-option-simple) { padding: var(--spacing-s) var(--spacing-m) !important; padding-left: calc(16px + 2 * var(--spacing-m)) !important; display: flex; @@ -415,9 +449,13 @@ align-items: center; color: var(--spectrum-alias-text-color); } + .code-editor :global(.autocomplete-option-simple) { + padding-left: var(--spacing-s) !important; + } /* Highlighted completion item */ - .code-editor :global(.autocomplete-option[aria-selected]) { + .code-editor :global(.autocomplete-option[aria-selected]), + .code-editor :global(.autocomplete-option-simple[aria-selected]) { background: var(--spectrum-global-color-blue-400); color: white; } @@ -433,6 +471,9 @@ font-family: var(--font-sans); text-transform: capitalize; } + .code-editor :global(.autocomplete-option-simple .cm-completionLabel) { + text-transform: none; + } /* Completion item type */ .code-editor :global(.autocomplete-option .cm-completionDetail) { diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index c104267aa4..14c0084f3a 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -102,6 +102,29 @@ export const getHelperCompletions = mode => { }, []) } +export const snippetAutoComplete = snippets => { + return function myCompletions(context) { + if (!snippets?.length) { + return null + } + const word = context.matchBefore(/\w*/) + if (word.from == word.to && !context.explicit) { + return null + } + return { + from: word.from, + options: snippets.map(snippet => ({ + label: `snippets.${snippet.name}`, + type: "text", + simple: true, + apply: (view, completion, from, to) => { + insertSnippet(view, from, to, completion.label) + }, + })), + } + } +} + const bindingFilter = (options, query) => { return options.filter(completion => { const section_parsed = completion.section.name.toLowerCase() @@ -247,6 +270,21 @@ export const insertBinding = (view, from, to, text, mode) => { }) } +export const insertSnippet = (view, from, to, text, mode) => { + const parsedInsert = `${text}()` + let cursorPos = from + parsedInsert.length - 1 + view.dispatch({ + changes: { + from, + to, + insert: parsedInsert, + }, + selection: { + anchor: cursorPos, + }, + }) +} + export const bindingsToCompletions = (bindings, mode) => { const bindingByCategory = groupBy(bindings, "category") const categoryMeta = bindings?.reduce((acc, ele) => { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 96a2187755..01c2f5d55b 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -19,6 +19,7 @@ getHelperCompletions, jsAutocomplete, hbAutocomplete, + snippetAutoComplete, EditorModes, bindingsToCompletions, } from "../CodeEditor" @@ -98,6 +99,7 @@ ...bindingCompletions, ...getHelperCompletions(EditorModes.JS), ]), + snippetAutoComplete(snippets), ] const getModeOptions = (allowHBS, allowJS) => {