A bit more chaining cleanup

This commit is contained in:
Gregory Schier
2024-08-19 16:38:28 -07:00
parent bd02206df2
commit 323e27a047
16 changed files with 173 additions and 51 deletions

View File

@@ -3,13 +3,22 @@ import type { CompletionContext } from '@codemirror/autocomplete';
const openTag = '${[ ';
const closeTag = ' ]}';
export interface TwigCompletionOption {
export type TwigCompletionOptionVariable = {
type: 'variable';
};
export type TwigCompletionOptionFunction = {
args: { name: string }[];
type: 'function';
};
export type TwigCompletionOption = (TwigCompletionOptionFunction | TwigCompletionOptionVariable) & {
name: string;
label: string;
type: 'function' | 'variable' | 'unknown';
onClick: (rawTag: string, startPos: number) => void;
value: string | null;
onClick?: (rawTag: string, startPos: number) => void;
}
invalid?: boolean;
};
export interface TwigCompletionConfig {
options: TwigCompletionOption[];
@@ -46,10 +55,9 @@ export function twigCompletion({ options }: TwigCompletionConfig) {
options: options
.filter((v) => v.name.trim())
.map((v) => {
const innerLabel = v.type === 'function' ? `${v.name}()` : v.name;
const tagSyntax = openTag + innerLabel + closeTag;
const tagSyntax = openTag + v.label + closeTag;
return {
label: innerLabel,
label: v.label,
apply: tagSyntax,
type: v.type === 'variable' ? 'variable' : 'function',
matchLen: matchLen,

View File

@@ -1,8 +1,7 @@
import type { LanguageSupport } from '@codemirror/language';
import { LRLanguage } from '@codemirror/language';
import { parseMixed } from '@lezer/common';
import type { EnvironmentVariable } from '@yaakapp/api';
import type { TemplateFunction } from '../../../../hooks/useTemplateFunctions';
import type { EnvironmentVariable, TemplateFunction } from '@yaakapp/api';
import type { GenericCompletionConfig } from '../genericCompletion';
import { genericCompletion } from '../genericCompletion';
import { textLanguageName } from '../text/extension';
@@ -18,6 +17,7 @@ export function twig({
autocomplete,
onClickFunction,
onClickVariable,
onClickMissingVariable,
}: {
base: LanguageSupport;
environmentVariables: EnvironmentVariable[];
@@ -25,6 +25,7 @@ export function twig({
autocomplete?: GenericCompletionConfig;
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
}) {
const language = mixLanguage(base);
@@ -35,14 +36,23 @@ export function twig({
label: v.name,
onClick: (rawTag: string, startPos: number) => onClickVariable(v, rawTag, startPos),
})) ?? [];
const functionOptions: TwigCompletionOption[] =
templateFunctions.map((fn) => ({
name: fn.name,
type: 'function',
value: null,
label: fn.name + '(' + fn.args.length + ')',
onClick: (rawTag: string, startPos: number) => onClickFunction(fn, rawTag, startPos),
})) ?? [];
templateFunctions.map((fn) => {
const shortArgs =
fn.args
.slice(0, 2)
.map((a) => a.name)
.join(', ') + (fn.args.length > 2 ? ', …' : '');
return {
name: fn.name,
type: 'function',
args: fn.args.map((a) => ({ name: a.name })),
value: null,
label: `${fn.name}(${shortArgs})`,
onClick: (rawTag: string, startPos: number) => onClickFunction(fn, rawTag, startPos),
};
}) ?? [];
const options = [...variableOptions, ...functionOptions];
@@ -51,7 +61,7 @@ export function twig({
return [
language,
base.support,
templateTags(options),
templateTags(options, onClickMissingVariable),
language.data.of({ autocomplete: completions }),
base.language.data.of({ autocomplete: completions }),
language.data.of({ autocomplete: genericCompletion(autocomplete) }),

View File

@@ -1,11 +1,8 @@
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
import { truncate } from '../../../../lib/truncate';
import { BetterMatchDecorator } from '../BetterMatchDecorator';
import type { TwigCompletionOption } from './completion';
const TAG_TRUNCATE_LEN = 30;
class TemplateTagWidget extends WidgetType {
readonly #clickListenerCallback: () => void;
@@ -32,17 +29,15 @@ class TemplateTagWidget extends WidgetType {
toDOM() {
const elt = document.createElement('span');
elt.className = `x-theme-templateTag template-tag ${
this.option.type === 'unknown'
this.option.invalid
? 'x-theme-templateTag--danger'
: this.option.type === 'variable'
? 'x-theme-templateTag--primary'
: 'x-theme-templateTag--info'
}`;
elt.title = this.option.type === 'unknown' ? '__NOT_FOUND__' : this.option.value ?? '';
elt.textContent = truncate(
this.rawTag.replace('${[', '').replace(']}', '').trim(),
TAG_TRUNCATE_LEN,
);
elt.title = this.option.invalid ? 'Not Found' : this.option.value ?? '';
elt.setAttribute('data-tag-type', this.option.type);
elt.textContent = this.option.label;
elt.addEventListener('click', this.#clickListenerCallback);
return elt;
}
@@ -57,7 +52,10 @@ class TemplateTagWidget extends WidgetType {
}
}
export function templateTags(options: TwigCompletionOption[]) {
export function templateTags(
options: TwigCompletionOption[],
onClickMissingVariable: (name: string, rawTag: string, startPos: number) => void,
) {
const templateTagMatcher = new BetterMatchDecorator({
regexp: /\$\{\[\s*(.+)(?!]})\s*]}/g,
decoration(match, view, matchStartPos) {
@@ -82,7 +80,14 @@ export function templateTags(options: TwigCompletionOption[]) {
let option = options.find((v) => v.name === name);
if (option == null) {
option = { type: 'unknown', name: innerTagMatch, value: null, label: innerTagMatch };
option = {
invalid: true,
type: 'variable',
name: innerTagMatch,
value: null,
label: innerTagMatch,
onClick: () => onClickMissingVariable(name, match[0], matchStartPos),
};
}
return Decoration.replace({