diff --git a/client/src/common/handlers.ts b/client/src/common/handlers.ts index fa73667..659763c 100644 --- a/client/src/common/handlers.ts +++ b/client/src/common/handlers.ts @@ -136,3 +136,138 @@ export function hoverCdlHandler(document: vscode.TextDocument, position: vscode. } return undefined } + +export function tclDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] { + const symbols: vscode.DocumentSymbol[] = [] + const lines = document.getText().split("\n") + + interface Scope { + name: string + symbol: vscode.DocumentSymbol + startLine: number + braceCount: number + children: Scope[] + } + + const rootScope: Scope = { + name: "", + symbol: new vscode.DocumentSymbol( + "root", + "", + vscode.SymbolKind.Namespace, + new vscode.Range(0, 0, lines.length, 0), + new vscode.Range(0, 0, 0, 0) + ), + startLine: 0, + braceCount: 0, + children: [] + } + let scopeStack: Scope[] = [rootScope] + + const namespaceRegex = /^\s*namespace\s+eval\s+([^\s\{]+)/ + const procRegex = /^\s*proc\s+([^\s\{]+)\s+\{.*\}\s+\{/ + const eventStartRegex = /^\s*LIB_GE_command_buffer_edit_(prepend|append|insert)\b/ + + let pendingEvent: { editType: string; startLine: number } | null = null + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + + const nsMatch = line.match(namespaceRegex) + const procMatch = line.match(procRegex) + const eventStartMatch = line.match(eventStartRegex) + + if (pendingEvent) { + // Wir suchen nach schließender Klammer mit Eventnamen danach + const closeMatch = line.match(/^\s*\}\s*(\w+)\s*$/) + if (closeMatch) { + const eventName = closeMatch[1] + const eventSymbol = new vscode.DocumentSymbol( + eventName, + `Event (${pendingEvent.editType})`, + vscode.SymbolKind.Event, + new vscode.Range(pendingEvent.startLine, 0, i, line.length), + new vscode.Range(i, 0, i, line.length) + ) + scopeStack[scopeStack.length - 1].children.push({ + name: eventName, + symbol: eventSymbol, + startLine: pendingEvent.startLine, + braceCount: 0, + children: [] + }) + pendingEvent = null + continue // nichts anderes prüfen, wenn wir Event abgeschlossen haben + } + } + + if (eventStartMatch) { + // Beginn eines Event-Blocks erkannt + pendingEvent = { editType: eventStartMatch[1], startLine: i } + continue + } + + if (nsMatch) { + const nsName = nsMatch[1] + const nsSymbol = new vscode.DocumentSymbol( + nsName, + "Namespace", + vscode.SymbolKind.Namespace, + new vscode.Range(i, 0, i, line.length), + new vscode.Range(i, 0, i, line.length) + ) + const nsScope: Scope = { + name: nsName, + symbol: nsSymbol, + startLine: i, + braceCount: 0, + children: [] + } + scopeStack[scopeStack.length - 1].children.push(nsScope) + scopeStack.push(nsScope) + } else if (procMatch) { + const procName = procMatch[1] + const procSymbol = new vscode.DocumentSymbol( + procName, + "Procedure", + vscode.SymbolKind.Function, + new vscode.Range(i, 0, i, line.length), + new vscode.Range(i, 0, i, line.length) + ) + scopeStack[scopeStack.length - 1].children.push({ + name: procName, + symbol: procSymbol, + startLine: i, + braceCount: 0, + children: [] + }) + } + + // Brace-Balancing für Namespaces und Procs + const openCount = (line.match(/\{/g) || []).length + const closeCount = (line.match(/\}/g) || []).length + + scopeStack[scopeStack.length - 1].braceCount += openCount - closeCount + + while (scopeStack.length > 1 && scopeStack[scopeStack.length - 1].braceCount <= 0) { + const finishedScope = scopeStack.pop() + if (finishedScope) { + finishedScope.symbol.range = new vscode.Range( + finishedScope.startLine, + 0, + i, + line.length + ) + } + } + } + + function collectSymbols(scope: Scope): vscode.DocumentSymbol[] { + for (const child of scope.children) { + child.symbol.children = collectSymbols(child) + } + return scope.children.map((child) => child.symbol) + } + + return collectSymbols(rootScope) +} diff --git a/client/src/extension.ts b/client/src/extension.ts index e8ae0ba..23fd4cb 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -11,7 +11,8 @@ import { hoverCdlHandler, formatDefFile, isFirstLineMachine, - diagnosticHandler + diagnosticHandler, + tclDocumentSymbolProvider } from "./common/handlers" let client: LanguageClient @@ -81,6 +82,12 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(formatDefProvider) + const tclOutlineProvider = vscode.languages.registerDocumentSymbolProvider( + { scheme: "file", language: "tcl" }, + { provideDocumentSymbols: tclDocumentSymbolProvider } + ) + context.subscriptions.push(tclOutlineProvider) + // Diagnostics collection const diagnosticCollectionCdl = vscode.languages.createDiagnosticCollection("cdl") const diagnosticCollectionDef = vscode.languages.createDiagnosticCollection("def")