feat(indexing): add persistent index cache and incremental reparse

- Pass extension storage path to the server (client/ changes) so the
  server can persist a workspace index.
- Introduce IndexCache (server/tools/index_cache.py) and load/save it on
  initialization and after background indexing. Index entries are stored
  only when the file's stat hasn't changed while being read.
- Add incremental reparse logic (server/tools/incremental_parse.py) and
  use a per-file _last_parse cache in the language server to reparse only
  the top-level Tcl commands touched by an edit, falling back to a full
  parse when necessary.
- Use a new _FileIndex dataclass and _build_file_index helper to unify
  what is stored/loaded for a file; update update_poco_completion_for_file
  to use the persistent cache for disk-read files (from_disk/source_stat).
- Keep background indexing non-blocking and persist the index at the
  end of the run. Add basic unit tests for incremental parse and index cache.

Before: edits and background work always required full parsing of files
and no persistent cross-restart index. After: some edits reuse previous
ASTs and files read from disk can use a persisted index to skip
re-indexing across restarts.
This commit is contained in:
Christoph Brandau
2026-09-23 13:20:09 +02:00
parent b2e6e9d250
commit 01e8670cc1
10 changed files with 674 additions and 42 deletions
+10 -3
View File
@@ -22,7 +22,12 @@ import {
import { getLSClientTraceLevel, getProjectRoot } from "./utilities"
import { isVirtualWorkspace } from "./vscodeapi"
export type IInitOptions = { settings: ISettings[]; globalSettings: ISettings }
export type IInitOptions = {
settings: ISettings[]
globalSettings: ISettings
// Folder for the server's persistent index cache; omitted without a workspace.
indexCachePath?: string
}
let _disposables: Disposable[] = []
@@ -114,7 +119,8 @@ export async function restartServer(
serverId: string,
serverName: string,
outputChannel: LogOutputChannel,
lsClient?: LanguageClient
lsClient?: LanguageClient,
indexCachePath?: string
): Promise<LanguageClient | undefined> {
if (lsClient) {
traceInfo(`Server: Stop requested`)
@@ -132,7 +138,8 @@ export async function restartServer(
outputChannel,
{
settings: await getExtensionSettings(serverId, true),
globalSettings: await getGlobalSettings(serverId, false)
globalSettings: await getGlobalSettings(serverId, false),
indexCachePath
}
)
traceInfo(`Server: Start requested.`)
+14 -2
View File
@@ -73,7 +73,13 @@ export async function activate(context: vscode.ExtensionContext) {
traceVerbose(
`Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(" ")}`
)
client = await restartServer(serverId, serverName, outputChannel, client)
client = await restartServer(
serverId,
serverName,
outputChannel,
client,
context.storageUri?.fsPath
)
}
return
}
@@ -83,7 +89,13 @@ export async function activate(context: vscode.ExtensionContext) {
traceVerbose(
`Using interpreter from Python extension: ${interpreterDetails.path.join(" ")}`
)
client = await restartServer(serverId, serverName, outputChannel, client)
client = await restartServer(
serverId,
serverName,
outputChannel,
client,
context.storageUri?.fsPath
)
return
}