mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
A bit more chaining cleanup
This commit is contained in:
@@ -180,7 +180,30 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
);
|
||||
|
||||
const onClickVariable = useCallback(
|
||||
async (v: EnvironmentVariable, tagValue: string, startPos: number) => {
|
||||
async (_v: EnvironmentVariable, tagValue: string, startPos: number) => {
|
||||
const initialTokens = await parseTemplate(tagValue);
|
||||
dialog.show({
|
||||
size: 'dynamic',
|
||||
id: 'template-variable',
|
||||
title: 'Configure Variable',
|
||||
render: ({ hide }) => (
|
||||
<TemplateVariableDialog
|
||||
hide={hide}
|
||||
initialTokens={initialTokens}
|
||||
onChange={(insert) => {
|
||||
cm.current?.view.dispatch({
|
||||
changes: [{ from: startPos, to: startPos + tagValue.length, insert }],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
[dialog],
|
||||
);
|
||||
|
||||
const onClickMissingVariable = useCallback(
|
||||
async (_name: string, tagValue: string, startPos: number) => {
|
||||
const initialTokens = await parseTemplate(tagValue);
|
||||
dialog.show({
|
||||
size: 'dynamic',
|
||||
@@ -188,7 +211,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
title: 'Configure Variable',
|
||||
render: ({ hide }) => (
|
||||
<TemplateVariableDialog
|
||||
definition={v}
|
||||
hide={hide}
|
||||
initialTokens={initialTokens}
|
||||
onChange={(insert) => {
|
||||
@@ -215,6 +237,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
templateFunctions,
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
});
|
||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||
}, [
|
||||
@@ -225,6 +248,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
templateFunctions,
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
@@ -247,6 +271,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
templateFunctions,
|
||||
onClickVariable,
|
||||
onClickFunction,
|
||||
onClickMissingVariable,
|
||||
});
|
||||
|
||||
const state = EditorState.create({
|
||||
@@ -358,7 +383,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
justifyContent="end"
|
||||
className={classNames(
|
||||
'absolute bottom-2 left-0 right-0',
|
||||
'pointer-events-none', // No pointer events so we don't block the editor
|
||||
'pointer-events-none', // No pointer events, so we don't block the editor
|
||||
)}
|
||||
>
|
||||
{decoratedActions}
|
||||
|
||||
@@ -31,10 +31,9 @@ import {
|
||||
rectangularSelection,
|
||||
} from '@codemirror/view';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import type { EnvironmentVariable } from '@yaakapp/api';
|
||||
import type { EnvironmentVariable, TemplateFunction } from '@yaakapp/api';
|
||||
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { TemplateFunction } from '../../../hooks/useTemplateFunctions';
|
||||
import type { EditorProps } from './index';
|
||||
import { pairs } from './pairs/extension';
|
||||
import { text } from './text/extension';
|
||||
@@ -84,11 +83,13 @@ export function getLanguageExtension({
|
||||
templateFunctions,
|
||||
onClickVariable,
|
||||
onClickFunction,
|
||||
onClickMissingVariable,
|
||||
}: {
|
||||
environmentVariables: EnvironmentVariable[];
|
||||
templateFunctions: TemplateFunction[];
|
||||
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
||||
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
||||
} & Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
|
||||
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
||||
if (justContentType === 'application/graphql') {
|
||||
@@ -106,6 +107,7 @@ export function getLanguageExtension({
|
||||
autocomplete,
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) }),
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user