Improved autocompletion!

This commit is contained in:
Gregory Schier
2023-03-02 11:14:51 -08:00
parent 0ccceaac77
commit 548aa4c7cd
8 changed files with 89 additions and 64 deletions

View File

@@ -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 * {

View File

@@ -119,7 +119,6 @@ function getExtensions({
? [
EditorView.domEventHandlers({
keydown: (e) => {
console.log('KEYDOWN', e);
if (e.key === 'Enter') onSubmit?.();
},
}),

View File

@@ -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',
})),
};
}

View File

@@ -107,7 +107,7 @@ export const baseExtensions = [
drawSelection(),
dropCursor(),
bracketMatching(),
autocompletion({ activateOnTyping: false, closeOnBlur: true }),
autocompletion({ closeOnBlur: true }),
syntaxHighlighting(myHighlightStyle),
EditorState.allowMultipleSelections.of(true),
];

View File

@@ -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),
};
}

View File

@@ -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 {

View File

@@ -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 };
}

View File

@@ -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]);
}