mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-18 23:43:55 +01:00
Improved autocompletion!
This commit is contained in:
@@ -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 * {
|
||||
|
||||
@@ -119,7 +119,6 @@ function getExtensions({
|
||||
? [
|
||||
EditorView.domEventHandlers({
|
||||
keydown: (e) => {
|
||||
console.log('KEYDOWN', e);
|
||||
if (e.key === 'Enter') onSubmit?.();
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -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',
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export const baseExtensions = [
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
bracketMatching(),
|
||||
autocompletion({ activateOnTyping: false, closeOnBlur: true }),
|
||||
autocompletion({ closeOnBlur: true }),
|
||||
syntaxHighlighting(myHighlightStyle),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
];
|
||||
|
||||
55
src-web/components/Editor/twig/completion.ts
Normal file
55
src-web/components/Editor/twig/completion.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
19
src-web/components/Editor/url/completion.ts
Normal file
19
src-web/components/Editor/url/completion.ts
Normal 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 };
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user