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:
@@ -11,6 +11,7 @@ import pathlib
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from collections import ChainMap
|
||||
from functools import reduce
|
||||
from typing import Any, Optional
|
||||
@@ -52,6 +53,7 @@ from tools.completion_items import (
|
||||
ranked_completion_items,
|
||||
)
|
||||
from tools.folding_ranges import build_folding_ranges
|
||||
from tools.index_cache import IndexCache
|
||||
from tools.inlay_hint import (
|
||||
InlayHintGenerator,
|
||||
build_builtin_inlay_signatures,
|
||||
@@ -89,6 +91,8 @@ from tools.tcl_command_completion import (
|
||||
|
||||
WORKSPACE_SETTINGS = {}
|
||||
GLOBAL_SETTINGS = {}
|
||||
# Extension storage folder of the workspace; without it nothing is persisted.
|
||||
INDEX_CACHE_PATH: dict[str, str | None] = {}
|
||||
|
||||
|
||||
MAX_WORKERS = 5
|
||||
@@ -175,6 +179,7 @@ def _index_tcl_file_from_disk(uri: str) -> None:
|
||||
document,
|
||||
cache_tree=False,
|
||||
require_file_exists=True,
|
||||
from_disk=True,
|
||||
)
|
||||
except (OSError, UnicodeError) as error:
|
||||
log_warning(f"Could not re-index {path}: {error}")
|
||||
@@ -861,6 +866,7 @@ def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
|
||||
log_to_output(f"sys.path used to run Server:\r\n {paths}")
|
||||
|
||||
GLOBAL_SETTINGS.update(**params.initialization_options.get("globalSettings", {}))
|
||||
INDEX_CACHE_PATH["path"] = params.initialization_options.get("indexCachePath")
|
||||
|
||||
settings = params.initialization_options["settings"]
|
||||
_update_workspace_settings(settings)
|
||||
@@ -899,6 +905,8 @@ def initialized(_params: lsp.InitializedParams):
|
||||
log_to_output("Background indexing skipped: no workspace folder is open.")
|
||||
return
|
||||
log_to_output("Background indexing started...")
|
||||
started = time.perf_counter()
|
||||
LSP_SERVER.index_cache = IndexCache.load(INDEX_CACHE_PATH.get("path"))
|
||||
root_path = pathlib.Path(root)
|
||||
skipped_directories = {
|
||||
".git",
|
||||
@@ -916,11 +924,13 @@ def initialized(_params: lsp.InitializedParams):
|
||||
document,
|
||||
cache_tree=False,
|
||||
require_file_exists=True,
|
||||
from_disk=True,
|
||||
)
|
||||
except Exception as error:
|
||||
log_to_output(f"Fehler beim Parsen von {filepath}: {error}")
|
||||
_refresh_psc_index()
|
||||
log_to_output("Background indexing completed.")
|
||||
LSP_SERVER.index_cache.save()
|
||||
log_to_output(f"Background indexing completed in {time.perf_counter() - started:.1f}s.")
|
||||
except Exception as e:
|
||||
log_to_output(f"Background indexing failed: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user