feat(protocol): Add template preview functionality with localization support

This commit is contained in:
web
2025-08-14 09:55:13 -07:00
parent f190c68eb0
commit 0448d213a3
38 changed files with 522 additions and 92 deletions
@@ -66,6 +66,7 @@ const REGEX_PATTERNS = {
FIELD_PATH: /^(\.\w+(?:\.\w+)*)$/,
NESTED_DOT: /(\.\w+(?:\.\w+)*)\./,
NESTED_VAR: /(\$\w+\.\w+(?:\.\w+)*)\.$/,
VAR_DOT: /(\$\w+)\.$/,
NESTED_GENERAL: /([\w.]+)\.$/,
WORD_WITH_SPACES: /(\s*)(\S*)$/,
LEADING_SPACES: /^(\s+)/,
@@ -694,6 +695,33 @@ export function GoTemplateEditor({ schema, enableSprig = true, ...props }: GoTem
});
}
});
} else if (
isSchemaProperty(currentSchema) &&
currentSchema.type === 'array' &&
currentSchema.items &&
currentSchema.items.type === 'object' &&
currentSchema.items.properties
) {
Object.keys(currentSchema.items.properties).forEach((key) => {
const prop = currentSchema.items!.properties![key];
if (isSchemaProperty(prop)) {
items.push({
label: key,
kind: COMPLETION_KINDS.PROPERTY,
insertText: key,
documentation: `${prop.description || key} (${prop.type}) - from array item`,
sortText: `${SORT_PREFIXES.NESTED}${key}`,
});
} else {
items.push({
label: key,
kind: COMPLETION_KINDS.PROPERTY,
insertText: key,
documentation: `Field: ${key} - from array item`,
sortText: `${SORT_PREFIXES.NESTED}${key}`,
});
}
});
}
return items;
@@ -848,24 +876,67 @@ export function GoTemplateEditor({ schema, enableSprig = true, ...props }: GoTem
Math.max(templateStartNormal, templateStartTrim) +
(templateStartTrim > templateStartNormal ? 3 : 2);
const actualStart = Math.max(wordStart, templateStart);
const currentWord = textUntilPosition.substring(actualStart).trim();
const currentWord = textUntilPosition.substring(actualStart);
const currentWordTrimmed = currentWord.trim();
let dotMatches = currentWord.match(REGEX_PATTERNS.NESTED_DOT);
let dotMatches = currentWordTrimmed.match(REGEX_PATTERNS.NESTED_DOT);
if (!dotMatches) {
const beforeCursor = textUntilPosition.substring(Math.max(templateStart, 0));
dotMatches =
beforeCursor.match(REGEX_PATTERNS.NESTED_DOT) ||
beforeCursor.match(REGEX_PATTERNS.NESTED_VAR) ||
beforeCursor.match(REGEX_PATTERNS.VAR_DOT) ||
beforeCursor.match(REGEX_PATTERNS.NESTED_GENERAL);
}
const isNestedField = dotMatches && textUntilPosition.endsWith('.') && schema;
const justTypedDot = currentWord.endsWith('.') || textUntilPosition.endsWith('.');
const varDotMatch = textUntilPosition.match(REGEX_PATTERNS.VAR_DOT);
const isVarDot =
varDotMatch &&
textUntilPosition.endsWith('.') &&
schema &&
activeRangeField &&
rangeVariable;
const isNestedField =
(dotMatches && textUntilPosition.endsWith('.') && schema) || isVarDot;
const justTypedDot =
currentWordTrimmed.endsWith('.') || textUntilPosition.endsWith('.');
const justTypedSpace = textUntilPosition.endsWith(' ');
const wordForFiltering = justTypedDot ? currentWord.slice(0, -1) : currentWord;
let wordForFiltering = currentWordTrimmed;
if (justTypedDot) {
wordForFiltering = currentWordTrimmed.slice(0, -1);
}
if ((justTypedDot && activeRangeField) || isVarDot) {
console.log('Go Template Debug:', {
justTypedDot,
isVarDot,
varDotMatch,
activeRangeField,
rangeVariable,
currentWord,
currentWordTrimmed,
wordForFiltering,
textUntilPosition,
allCompletionsCount: allCompletions.length,
});
}
if (isNestedField && schema) {
let fieldPath = '';
if (isVarDot && varDotMatch) {
// 处理变量后跟点的情况 ($n.)
const variableName = varDotMatch[1];
if (variableName === rangeVariable && activeRangeField) {
// 使用activeRangeField作为字段路径
fieldPath = activeRangeField;
}
} else if (dotMatches && dotMatches[1]) {
fieldPath = dotMatches[1];
}
if (isNestedField && schema && dotMatches) {
const fieldPath = dotMatches[1];
if (fieldPath) {
const nestedCompletions = getNestedFieldCompletions(
schema,
@@ -889,26 +960,45 @@ export function GoTemplateEditor({ schema, enableSprig = true, ...props }: GoTem
);
};
const isRangeContextItem = (item: CompletionItem): boolean => {
return (
isVariableOrFieldItem(item) ||
item.sortText?.startsWith(SORT_PREFIXES.ROOT_IN_RANGE)
);
};
const filteredCompletions = allCompletions.filter((item) => {
if (isNestedField) {
return item.sortText?.startsWith(SORT_PREFIXES.NESTED);
}
if (justTypedDot && activeRangeField) {
return (
item.label.startsWith('.') ||
item.label.startsWith('$') ||
item.sortText?.startsWith(SORT_PREFIXES.RANGE_VAR) ||
item.sortText?.startsWith(SORT_PREFIXES.VAR_FIELD) ||
item.sortText?.startsWith(SORT_PREFIXES.CURRENT) ||
item.sortText?.startsWith(SORT_PREFIXES.ROOT_FIELD) ||
item.sortText?.startsWith(SORT_PREFIXES.ROOT_IN_RANGE)
);
}
if (justTypedDot) {
return activeRangeField ? isRangeContextItem(item) : isVariableOrFieldItem(item);
return isVariableOrFieldItem(item);
}
if (justTypedSpace) {
return true;
}
if (
activeRangeField &&
(item.sortText?.startsWith(SORT_PREFIXES.RANGE_VAR) ||
item.sortText?.startsWith(SORT_PREFIXES.VAR_FIELD) ||
item.sortText?.startsWith(SORT_PREFIXES.CURRENT))
) {
if (!wordForFiltering) {
return true;
}
const label = item.label.toLowerCase();
const word = wordForFiltering.toLowerCase();
return label.includes(word) || label.startsWith(word) || word === '';
}
if (!wordForFiltering) {
return true;
}
@@ -1006,18 +1096,11 @@ export function GoTemplateEditor({ schema, enableSprig = true, ...props }: GoTem
const wordMatch = templateContent.match(REGEX_PATTERNS.WORD_WITH_SPACES);
if (wordMatch) {
const [, leadingSpaces, currentWordInTemplate] = wordMatch;
if (leadingSpaces && currentWordInTemplate) {
if (currentWordInTemplate) {
startColumn =
templateStart + templateContent.length - currentWordInTemplate.length;
} else {
const spaceMatch = templateContent.match(REGEX_PATTERNS.LEADING_SPACES);
if (
spaceMatch &&
spaceMatch[1] &&
templateContent.trim() === currentWordInTemplate
) {
startColumn = templateStart + spaceMatch[1].length;
}
} else if (leadingSpaces) {
startColumn = position.column;
}
}
}
@@ -20,6 +20,7 @@ export interface MonacoEditorProps {
beforeMount?: (monaco: Monaco) => void;
language?: string;
className?: string;
showLineNumbers?: boolean;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -43,6 +44,7 @@ export function MonacoEditor({
beforeMount,
language = 'markdown',
className,
showLineNumbers = false,
}: MonacoEditorProps) {
const [internalValue, setInternalValue] = useState<string | undefined>(propValue);
const [isFullscreen, setIsFullscreen] = useState(false);
@@ -133,7 +135,7 @@ export function MonacoEditor({
formatOnPaste: true,
formatOnType: true,
glyphMargin: false,
lineNumbers: 'off',
lineNumbers: showLineNumbers ? 'on' : 'off',
minimap: { enabled: false },
overviewRulerLanes: 0,
renderLineHighlight: 'none',