From 548aa4c7cd2153294920611cdeff1e39a5046249 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 2 Mar 2023 11:14:51 -0800 Subject: [PATCH] Improved autocompletion! --- src-web/components/Editor/Editor.css | 22 +++----- src-web/components/Editor/Editor.tsx | 1 - .../Editor/completion/completion.ts | 38 ------------- src-web/components/Editor/extensions.ts | 2 +- src-web/components/Editor/twig/completion.ts | 55 +++++++++++++++++++ src-web/components/Editor/twig/extension.ts | 6 +- src-web/components/Editor/url/completion.ts | 19 +++++++ src-web/components/Editor/url/extension.ts | 10 +--- 8 files changed, 89 insertions(+), 64 deletions(-) delete mode 100644 src-web/components/Editor/completion/completion.ts create mode 100644 src-web/components/Editor/twig/completion.ts create mode 100644 src-web/components/Editor/url/completion.ts diff --git a/src-web/components/Editor/Editor.css b/src-web/components/Editor/Editor.css index f2d9f684..3fdeaebf 100644 --- a/src-web/components/Editor/Editor.css +++ b/src-web/components/Editor/Editor.css @@ -13,30 +13,24 @@ } .cm-editor { - width: 100%; - display: block; + @apply w-full block; } .cm-singleline .cm-scroller { overflow: hidden !important;; } -.cm-editor .cm-tooltip { - color: gray; -} - .cm-editor .placeholder-widget { - background-color: hsl(var(--color-blue-400)); + @apply text-xs text-white bg-blue-400 py-[1px] px-1 mx-[1px] rounded border border-gray-50 cursor-pointer; text-shadow: 0 0 0.2em black; - padding: 0.05em 0.3em; - border-radius: 0.2em; - color: hsl(var(--color-blue-900)); - cursor: pointer; } .cm-editor .cm-scroller { - border-radius: var(--border-radius-lg); - background-color: hsl(var(--color-gray-50)); + @apply rounded-lg bg-gray-50; +} + +.cm-multiline .cm-editor .cm-scroller { + padding-bottom: 300px; } .cm-editor.cm-focused { @@ -137,7 +131,7 @@ /* <-- */ .cm-editor .cm-tooltip { - @apply shadow-lg border-0 bg-background rounded overflow-hidden text-gray-900; + @apply shadow-lg bg-background rounded overflow-hidden text-gray-900 border border-gray-100/70; } .cm-editor .cm-tooltip * { diff --git a/src-web/components/Editor/Editor.tsx b/src-web/components/Editor/Editor.tsx index b57a0119..1cea2e51 100644 --- a/src-web/components/Editor/Editor.tsx +++ b/src-web/components/Editor/Editor.tsx @@ -119,7 +119,6 @@ function getExtensions({ ? [ EditorView.domEventHandlers({ keydown: (e) => { - console.log('KEYDOWN', e); if (e.key === 'Enter') onSubmit?.(); }, }), diff --git a/src-web/components/Editor/completion/completion.ts b/src-web/components/Editor/completion/completion.ts deleted file mode 100644 index b073d9b3..00000000 --- a/src-web/components/Editor/completion/completion.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { CompletionContext } from '@codemirror/autocomplete'; - -const openTag = '${[ '; -const closeTag = ' ]}'; - -const variables = [ - { name: 'DOMAIN' }, - { name: 'BASE_URL' }, - { name: 'TOKEN' }, - { name: 'PROJECT_ID' }, - { name: 'DUMMY' }, - { name: 'DUMMY_2' }, -]; - -export function myCompletions(context: CompletionContext) { - const toStartOfName = context.explicit ? context.matchBefore(/\w*/) : context.matchBefore(/\w+/); - const toStartOfVariable = context.matchBefore(/\$\{?\[?\s*\w*/); - const toMatch = toStartOfVariable ?? toStartOfName ?? null; - - if (toMatch === null) { - return null; - } - - // Match a minimum of two characters when typing a variable ${[...]} to prevent it - // from opening on "$" - if (toStartOfVariable !== null && toMatch.to - toMatch.from < 2 && !context.explicit) { - return null; - } - - return { - from: toMatch.from, - options: variables.map((v) => ({ - label: toStartOfVariable ? `${openTag}${v.name}${closeTag}` : v.name, - apply: `${openTag}${v.name}${closeTag}`, - type: 'variable', - })), - }; -} diff --git a/src-web/components/Editor/extensions.ts b/src-web/components/Editor/extensions.ts index 43fa55a5..d3c7fcf0 100644 --- a/src-web/components/Editor/extensions.ts +++ b/src-web/components/Editor/extensions.ts @@ -107,7 +107,7 @@ export const baseExtensions = [ drawSelection(), dropCursor(), bracketMatching(), - autocompletion({ activateOnTyping: false, closeOnBlur: true }), + autocompletion({ closeOnBlur: true }), syntaxHighlighting(myHighlightStyle), EditorState.allowMultipleSelections.of(true), ]; diff --git a/src-web/components/Editor/twig/completion.ts b/src-web/components/Editor/twig/completion.ts new file mode 100644 index 00000000..751e12e2 --- /dev/null +++ b/src-web/components/Editor/twig/completion.ts @@ -0,0 +1,55 @@ +import type { CompletionContext } from '@codemirror/autocomplete'; +import { match } from 'assert'; + +const openTag = '${[ '; +const closeTag = ' ]}'; + +const variables = [ + { name: 'DOMAIN' }, + { name: 'BASE_URL' }, + { name: 'TOKEN' }, + { name: 'PROJECT_ID' }, + { name: 'DUMMY' }, + { name: 'DUMMY_2' }, + { name: 'STRIPE_PUB_KEY' }, + { name: 'RAILWAY_TOKEN' }, + { name: 'SECRET' }, + { name: 'PORT' }, +]; + +const MIN_MATCH_VAR = 2; +const MIN_MATCH_NAME = 2; + +export function completions(context: CompletionContext) { + const toStartOfName = context.matchBefore(/\w*/); + const toStartOfVariable = context.matchBefore(/\$\{?\[?\s*\w*/); + const toMatch = toStartOfVariable ?? toStartOfName ?? null; + + if (toMatch === null) return null; + + const matchLen = toMatch.to - toMatch.from; + + const failedVarLen = toStartOfVariable !== null && matchLen < MIN_MATCH_VAR; + if (failedVarLen && !context.explicit) { + return null; + } + + const failedNameLen = toStartOfVariable === null && matchLen < MIN_MATCH_NAME; + if (failedNameLen && !context.explicit) { + return null; + } + + // TODO: Figure out how to make autocomplete stay open if opened explicitly. It sucks when you explicitly + // open it, then it closes when you type the next character. + return { + from: toMatch.from, + options: variables + .map((v) => ({ + label: toStartOfVariable ? `${openTag}${v.name}${closeTag}` : v.name, + apply: `${openTag}${v.name}${closeTag}`, + type: 'variable', + })) + // Filter out exact matches + .filter((o) => o.label !== toMatch.text), + }; +} diff --git a/src-web/components/Editor/twig/extension.ts b/src-web/components/Editor/twig/extension.ts index b0d226f1..3e9999bf 100644 --- a/src-web/components/Editor/twig/extension.ts +++ b/src-web/components/Editor/twig/extension.ts @@ -1,18 +1,18 @@ import { LanguageSupport, LRLanguage } from '@codemirror/language'; import { parseMixed } from '@lezer/common'; -import { myCompletions } from '../completion/completion'; +import { completions } from './completion'; import { placeholders } from '../widgets'; import { parser as twigParser } from './twig'; export function twig(base?: LanguageSupport) { const language = mixedOrPlainLanguage(base); const completion = language.data.of({ - autocomplete: myCompletions, + autocomplete: completions, }); const languageSupport = new LanguageSupport(language, [completion]); if (base) { - const completion2 = base.language.data.of({ autocomplete: myCompletions }); + const completion2 = base.language.data.of({ autocomplete: completions }); const languageSupport2 = new LanguageSupport(base.language, [completion2]); return [languageSupport, languageSupport2, placeholders, base.support]; } else { diff --git a/src-web/components/Editor/url/completion.ts b/src-web/components/Editor/url/completion.ts new file mode 100644 index 00000000..c81c34bd --- /dev/null +++ b/src-web/components/Editor/url/completion.ts @@ -0,0 +1,19 @@ +import type { CompletionContext } from '@codemirror/autocomplete'; + +const options = [ + { label: 'http://', type: 'constant' }, + { label: 'https://', type: 'constant' }, +]; + +const MIN_MATCH = 1; + +export function completions(context: CompletionContext) { + const toMatch = context.matchBefore(/^[\w:/]*/); + if (toMatch === null) return null; + + const matchedMinimumLength = toMatch.to - toMatch.from >= MIN_MATCH; + if (!matchedMinimumLength && !context.explicit) return null; + + const optionsWithoutExactMatches = options.filter((o) => o.label !== toMatch.text); + return { from: toMatch.from, options: optionsWithoutExactMatches }; +} diff --git a/src-web/components/Editor/url/extension.ts b/src-web/components/Editor/url/extension.ts index 5b871760..3abf9da9 100644 --- a/src-web/components/Editor/url/extension.ts +++ b/src-web/components/Editor/url/extension.ts @@ -1,5 +1,6 @@ import { completeFromList } from '@codemirror/autocomplete'; import { LanguageSupport, LRLanguage } from '@codemirror/language'; +import { completions } from './completion'; import { parser } from './url'; const urlLanguage = LRLanguage.define({ @@ -7,13 +8,8 @@ const urlLanguage = LRLanguage.define({ languageData: {}, }); -const exampleCompletion = urlLanguage.data.of({ - autocomplete: completeFromList([ - { label: 'http://', type: 'constant' }, - { label: 'https://', type: 'constant' }, - ]), -}); +const completion = urlLanguage.data.of({ autocomplete: completions }); export function url() { - return new LanguageSupport(urlLanguage, [exampleCompletion]); + return new LanguageSupport(urlLanguage, [completion]); }