feat(navigation): add symbol index and LSP navigation features
The changes introduce a Tcl symbol index powering LSP navigation features across the workspace. A navigation API exposes snapshots and update hooks, enabling goto-definition, references, and rename using the index. Background indexing now watches Tcl files and rebuilds the index to stay in sync. - Add Tcl symbol index and navigation snapshot API - Wire go-to-definition, references, and rename using the index - Watch Tcl files and refresh the index in the background
This commit is contained in:
@@ -137,6 +137,102 @@ export function hoverCdlHandler(document: vscode.TextDocument, position: vscode.
|
||||
return undefined
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
export function cdlEventAtPosition(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): string | undefined {
|
||||
const line = document.lineAt(position.line).text
|
||||
const match = /^\s*EVENT\s+([^\s{]+)/.exec(line)
|
||||
if (!match) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const declarationStart = match.index
|
||||
const eventEnd = line.indexOf(match[1], match.index) + match[1].length
|
||||
if (position.character < declarationStart || position.character > eventEnd) {
|
||||
return undefined
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
export async function definitionCdlEventHandler(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.Location[] | undefined> {
|
||||
const eventName = cdlEventAtPosition(document, position)
|
||||
if (!eventName) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const handlerName = `MOM_${eventName}`
|
||||
try {
|
||||
const symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
|
||||
"vscode.executeWorkspaceSymbolProvider",
|
||||
handlerName
|
||||
)
|
||||
const indexedLocations = (symbols || [])
|
||||
.filter(
|
||||
(symbol) =>
|
||||
symbol.kind === vscode.SymbolKind.Function &&
|
||||
(symbol.name === handlerName ||
|
||||
symbol.name.endsWith(`::${handlerName}`))
|
||||
)
|
||||
.map((symbol) => symbol.location)
|
||||
if (indexedLocations.length > 0) {
|
||||
return indexedLocations
|
||||
}
|
||||
} catch {
|
||||
// The Tcl language server may still be starting; use the file fallback below.
|
||||
}
|
||||
|
||||
const declaration = new RegExp(
|
||||
`^\\s*proc\\s+(?:::)?${escapeRegExp(handlerName)}(?=\\s|\\{)`
|
||||
)
|
||||
const tclFiles = await vscode.workspace.findFiles(
|
||||
"**/*.tcl",
|
||||
"**/{.git,.nox,.venv,dist,node_modules,out}/**"
|
||||
)
|
||||
const locations: vscode.Location[] = []
|
||||
|
||||
for (const uri of tclFiles) {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined
|
||||
}
|
||||
let tclDocument: vscode.TextDocument
|
||||
try {
|
||||
tclDocument = await vscode.workspace.openTextDocument(uri)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
for (let lineNumber = 0; lineNumber < tclDocument.lineCount; lineNumber++) {
|
||||
const line = tclDocument.lineAt(lineNumber).text
|
||||
const match = declaration.exec(line)
|
||||
if (!match) {
|
||||
continue
|
||||
}
|
||||
const start = line.indexOf(handlerName, match.index)
|
||||
locations.push(
|
||||
new vscode.Location(
|
||||
uri,
|
||||
new vscode.Range(
|
||||
lineNumber,
|
||||
start,
|
||||
lineNumber,
|
||||
start + handlerName.length
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return locations.length > 0 ? locations : undefined
|
||||
}
|
||||
|
||||
export function tclDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] {
|
||||
const symbols: vscode.DocumentSymbol[] = []
|
||||
const lines = document.getText().split("\n")
|
||||
|
||||
Reference in New Issue
Block a user