From 541704f45a53a08fab0398b9b061cbb8f751b98c Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Wed, 19 Aug 2026 09:45:57 +0200 Subject: [PATCH] feat(cdl): add CDL event handler parsing and snippet support This adds a small DSL-aware helper to extract CDL event declarations and generate a ready-to-use snippet. It also extends hover support to show the snippet and parameter hints for CDL events, improving developer productivity. - Generate a MOM event handler scaffold and local vars - Hover shows the generated snippet and mom_ parameter hints --- client/src/common/cdlEventHandler.ts | 116 +++++++++++++++++++++++++++ client/src/common/handlers.ts | 45 ++++++----- 2 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 client/src/common/cdlEventHandler.ts diff --git a/client/src/common/cdlEventHandler.ts b/client/src/common/cdlEventHandler.ts new file mode 100644 index 0000000..cdb7742 --- /dev/null +++ b/client/src/common/cdlEventHandler.ts @@ -0,0 +1,116 @@ +export interface CdlEventHandler { + eventName: string + parameterNames: string[] +} + +function structuralCode(line: string): string { + let result = "" + let inString = false + let escaped = false + + for (const character of line) { + if (escaped) { + escaped = false + result += inString ? " " : character + continue + } + if (character === "\\") { + escaped = true + result += inString ? " " : character + continue + } + if (character === '"') { + inString = !inString + result += " " + continue + } + if (character === "#" && !inString) { + break + } + result += inString ? " " : character + } + + return result +} + +function braceDelta(line: string): number { + let delta = 0 + for (const character of structuralCode(line)) { + if (character === "{") { + delta += 1 + } else if (character === "}") { + delta -= 1 + } + } + return delta +} + +export function cdlEventHandlerAtLine( + source: string, + declarationLine: number +): CdlEventHandler | undefined { + const lines = source.split(/\r?\n/) + const declaration = lines[declarationLine] + if (declaration === undefined) { + return undefined + } + + const eventMatch = /^\s*EVENT\s+([^\s{]+)/.exec(structuralCode(declaration)) + if (!eventMatch) { + return undefined + } + + const parameterNames: string[] = [] + let eventOpened = false + let depth = 0 + + for (let lineNumber = declarationLine; lineNumber < lines.length; lineNumber++) { + const line = lines[lineNumber] + const code = structuralCode(line) + + if (eventOpened && depth === 1) { + const parameterMatch = /^\s*PARAM\s+([^\s{]+)/.exec(code) + if (parameterMatch) { + parameterNames.push(parameterMatch[1]) + } + } + + const delta = braceDelta(line) + if (!eventOpened && delta > 0) { + eventOpened = true + } + if (eventOpened) { + depth += delta + if (depth <= 0) { + break + } + } + } + + return { + eventName: eventMatch[1], + parameterNames + } +} + +function momEventName(eventName: string): string { + return `MOM_${eventName.replace(/^MOM_/i, "")}` +} + +function momVariableName(parameterName: string): string { + return `mom_${parameterName.replace(/^mom_/i, "")}` +} + +export function createCdlEventHandlerSnippet(handler: CdlEventHandler): string { + const globals = [ + ...new Set(handler.parameterNames.map((parameter) => momVariableName(parameter))) + ] + const lines = [`proc ${momEventName(handler.eventName)} { } {`] + + if (globals.length > 0) { + lines.push(...globals.map((variable) => ` global ${variable}`), "") + } + lines.push(" #Put your UDE Handler Tcl here", "", "}") + + return lines.join("\n") +} diff --git a/client/src/common/handlers.ts b/client/src/common/handlers.ts index 4e8f827..ce5dd57 100644 --- a/client/src/common/handlers.ts +++ b/client/src/common/handlers.ts @@ -1,4 +1,8 @@ import * as vscode from "vscode" +import { + cdlEventHandlerAtLine, + createCdlEventHandlerSnippet +} from "./cdlEventHandler" export function formatCdlFile(content: string): string { let indentLevel = 0 @@ -110,30 +114,33 @@ export function completionHandlerCdl(document: vscode.TextDocument, position: vs } export function hoverCdlHandler(document: vscode.TextDocument, position: vscode.Position) { - const wordRange = document.getWordRangeAtPosition(position) - const word = document.getText(wordRange) - const text = document.getText() - const lines = text.split("\n") - - let hoverText: string | undefined - - for (const line of lines) { - const words = line.split(/\s+/) - const wordIndex = words.indexOf(word) - - if (wordIndex > 0) { - if (words[wordIndex - 1] === "EVENT") { - hoverText = `MOM_${word}` - } else if (words[wordIndex - 1] === "PARAM") { - hoverText = `mom_${word}` + const line = document.lineAt(position.line).text + const eventMatch = /^\s*EVENT\s+([^\s{]+)/.exec(line) + if (eventMatch) { + const eventStart = line.indexOf(eventMatch[1], eventMatch.index) + const declarationEnd = eventStart + eventMatch[1].length + if (position.character <= declarationEnd) { + const handler = cdlEventHandlerAtLine(document.getText(), position.line) + if (handler) { + const markdown = new vscode.MarkdownString() + markdown.appendCodeblock(createCdlEventHandlerSnippet(handler), "tcl") + return new vscode.Hover( + markdown, + new vscode.Range(position.line, eventMatch.index, position.line, declarationEnd) + ) } - break } } - if (hoverText) { - return new vscode.Hover(hoverText) + const parameterMatch = /^\s*PARAM\s+([^\s{]+)/.exec(line) + if (parameterMatch) { + const parameterStart = line.indexOf(parameterMatch[1], parameterMatch.index) + const parameterEnd = parameterStart + parameterMatch[1].length + if (position.character >= parameterStart && position.character <= parameterEnd) { + return new vscode.Hover(`mom_${parameterMatch[1].replace(/^mom_/i, "")}`) + } } + return undefined }