From 2ce48ad558b32d042af674b62d9d01463692ceb4 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Tue, 12 Aug 2025 18:43:41 +0200 Subject: [PATCH] add: Outline support for DEF and CDL files --- client/src/common/handlers.ts | 177 ++++++++++++++++++++++++++++++++++ client/src/extension.ts | 22 ++++- 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/client/src/common/handlers.ts b/client/src/common/handlers.ts index 659763c..51f3974 100644 --- a/client/src/common/handlers.ts +++ b/client/src/common/handlers.ts @@ -271,3 +271,180 @@ export function tclDocumentSymbolProvider(document: vscode.TextDocument): vscode return collectSymbols(rootScope) } + +export function cdlDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] { + const lines = document.getText().split("\n") + + interface Scope { + symbol: vscode.DocumentSymbol + startLine: number + } + + const root = new vscode.DocumentSymbol( + "root", + "", + vscode.SymbolKind.Namespace, + new vscode.Range(0, 0, lines.length, 0), + new vscode.Range(0, 0, 0, 0) + ) + root.children = [] + const stack: Scope[] = [{ symbol: root, startLine: 0 }] + + let pending: { name: string; kind: vscode.SymbolKind; startLine: number } | null = null + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const trimmed = line.trim() + + const createSymbol = () => { + if (!pending) { + return + } + const startRange = new vscode.Range( + pending.startLine, + 0, + pending.startLine, + lines[pending.startLine].length + ) + const symbol = new vscode.DocumentSymbol( + pending.name, + "", + pending.kind, + startRange, + startRange + ) + symbol.children = [] + stack[stack.length - 1].symbol.children?.push(symbol) + stack.push({ symbol, startLine: pending.startLine }) + pending = null + } + + if (pending && trimmed.includes("{")) { + createSymbol() + } + + if (trimmed.startsWith("}")) { + const scope = stack.pop() + if (scope) { + scope.symbol.range = new vscode.Range(scope.startLine, 0, i, line.length) + } + continue + } + + let match + if ((match = /^\s*EVENT\s+(\w+)/.exec(line))) { + pending = { + name: match[1], + kind: vscode.SymbolKind.Event, + startLine: i + } + if (line.includes("{")) { + createSymbol() + } + } else if ((match = /^\s*PARAM\s+(\w+)/.exec(line))) { + pending = { + name: match[1], + kind: vscode.SymbolKind.Field, + startLine: i + } + if (line.includes("{")) { + createSymbol() + } + } + } + + return root.children || [] +} + +export function defDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] { + const lines = document.getText().split("\n") + + interface Scope { + symbol: vscode.DocumentSymbol + startLine: number + } + + const root = new vscode.DocumentSymbol( + "root", + "", + vscode.SymbolKind.Namespace, + new vscode.Range(0, 0, lines.length, 0), + new vscode.Range(0, 0, 0, 0) + ) + root.children = [] + const stack: Scope[] = [{ symbol: root, startLine: 0 }] + + let pending: { name: string; kind: vscode.SymbolKind; startLine: number } | null = null + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const trimmed = line.trim() + + const createSymbol = () => { + if (!pending) { + return + } + const startRange = new vscode.Range( + pending.startLine, + 0, + pending.startLine, + lines[pending.startLine].length + ) + const symbol = new vscode.DocumentSymbol( + pending.name, + "", + pending.kind, + startRange, + startRange + ) + symbol.children = [] + stack[stack.length - 1].symbol.children?.push(symbol) + stack.push({ symbol, startLine: pending.startLine }) + pending = null + } + + if (pending && trimmed.includes("{")) { + createSymbol() + } + + if (trimmed.startsWith("}")) { + const scope = stack.pop() + if (scope) { + scope.symbol.range = new vscode.Range(scope.startLine, 0, i, line.length) + } + continue + } + + let match + if ((match = /^\s*FORMATTING\b/.exec(line))) { + pending = { + name: "FORMATTING", + kind: vscode.SymbolKind.Module, + startLine: i + } + if (line.includes("{")) { + createSymbol() + } + } else if ((match = /^\s*ADDRESS\s+(\w+)/.exec(line))) { + pending = { + name: match[1], + kind: vscode.SymbolKind.Field, + startLine: i + } + if (line.includes("{")) { + createSymbol() + } + } else if ((match = /^\s*BLOCK_TEMPLATE\s+(\w+)/.exec(line))) { + pending = { + name: match[1], + kind: vscode.SymbolKind.Function, + startLine: i + } + if (line.includes("{")) { + createSymbol() + } + } + } + + return root.children || [] +} diff --git a/client/src/extension.ts b/client/src/extension.ts index 74b5d17..8460e3b 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -12,7 +12,8 @@ import { formatDefFile, isFirstLineMachine, diagnosticHandler, - tclDocumentSymbolProvider + cdlDocumentSymbolProvider, + defDocumentSymbolProvider } from "./common/handlers" import { registerLogger, traceError, traceLog, traceVerbose } from "./common/log/logging" import { @@ -179,7 +180,26 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(formatDefProvider) + const cdlSymbolProvider = vscode.languages.registerDocumentSymbolProvider( + { scheme: "file", language: "cdl" }, + { + provideDocumentSymbols(document: vscode.TextDocument) { + return cdlDocumentSymbolProvider(document) + } + } + ) + context.subscriptions.push(cdlSymbolProvider) + // Outline now provided by the language server (Document Symbols). Removing TS provider. + const defSymbolProvider = vscode.languages.registerDocumentSymbolProvider( + { scheme: "file", language: "def" }, + { + provideDocumentSymbols(document: vscode.TextDocument) { + return defDocumentSymbolProvider(document) + } + } + ) + context.subscriptions.push(defSymbolProvider) // Diagnostics collection const diagnosticCollectionCdl = vscode.languages.createDiagnosticCollection("cdl")