Add benchmark results and tests for StandaloneLspManager path normalization

- Created a new JSON file with benchmark results from the run on 2026-02-09.
- Added tests for the StandaloneLspManager to verify path normalization on Windows, including handling of percent-encoded URIs and ensuring plain Windows paths remain unchanged.
This commit is contained in:
catlog22
2026-02-09 13:02:37 +08:00
parent 964292ebdb
commit c3fd0624de
12 changed files with 4747 additions and 1407 deletions

View File

@@ -0,0 +1,453 @@
{
"summary": {
"timestamp": "2026-02-09 11:26:54",
"source": "src",
"k": 10,
"coarse_k": 100,
"query_count": 7,
"avg_jaccard_topk": 0.39589733329229126,
"avg_rbo_topk": 0.23139636799510202,
"staged": {
"success": 7,
"avg_latency_ms": 32194.107242865222
},
"dense_rerank": {
"success": 7,
"avg_latency_ms": 2643.366857132741
}
},
"comparisons": [
{
"query": "class Config",
"staged": {
"strategy": "staged",
"query": "class Config",
"latency_ms": 43041.41250002384,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\api\\semantic.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\parsers\\factory.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\watcher\\file_watcher.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\lsp\\server.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\api\\references.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\__init__.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 9864.638805389404,
"stage2_expand_ms": 13012.29190826416,
"stage3_cluster_ms": 13297.565460205078,
"stage4_rerank_ms": 6821.892261505127
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 149,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "class Config",
"latency_ms": 3209.129799991846,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\query_parser.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.1111111111111111,
"rbo_topk": 0.05429729885142857,
"staged_unique_files_topk": 10,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 8,
"dense_unique_dirs_topk": 4
},
{
"query": "def search",
"staged": {
"strategy": "staged",
"query": "def search",
"latency_ms": 37827.209600031376,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\ann_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 531.8794250488281,
"stage2_expand_ms": 27009.481191635132,
"stage3_cluster_ms": 7948.509931564331,
"stage4_rerank_ms": 2268.9380645751953
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 101,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "def search",
"latency_ms": 2540.472400009632,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\query_parser.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.26666666666666666,
"rbo_topk": 0.2983708721671428,
"staged_unique_files_topk": 9,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
},
{
"query": "LspBridge",
"staged": {
"strategy": "staged",
"query": "LspBridge",
"latency_ms": 24744.686599999666,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\merkle_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 517.8542137145996,
"stage2_expand_ms": 12839.622735977173,
"stage3_cluster_ms": 9154.959678649902,
"stage4_rerank_ms": 2160.0701808929443
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 100,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "LspBridge",
"latency_ms": 2482.5908999741077,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.5384615384615384,
"rbo_topk": 0.36639083062285716,
"staged_unique_files_topk": 10,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
},
{
"query": "graph expansion",
"staged": {
"strategy": "staged",
"query": "graph expansion",
"latency_ms": 25239.59050002694,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\gpu_support.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 631.9081783294678,
"stage2_expand_ms": 12570.756196975708,
"stage3_cluster_ms": 9557.724952697754,
"stage4_rerank_ms": 2409.7683429718018
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 100,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "graph expansion",
"latency_ms": 2574.1938000023365,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.42857142857142855,
"rbo_topk": 0.13728894791142857,
"staged_unique_files_topk": 10,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
},
{
"query": "clustering strategy",
"staged": {
"strategy": "staged",
"query": "clustering strategy",
"latency_ms": 28572.93939998746,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\__init__.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 659.6193313598633,
"stage2_expand_ms": 14207.426309585571,
"stage3_cluster_ms": 11513.370037078857,
"stage4_rerank_ms": 2117.546319961548
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 100,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "clustering strategy",
"latency_ms": 2536.551799982786,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\__init__.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\gpu_support.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.17647058823529413,
"rbo_topk": 0.07116480920571429,
"staged_unique_files_topk": 10,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
},
{
"query": "error handling",
"staged": {
"strategy": "staged",
"query": "error handling",
"latency_ms": 23812.726000010967,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\__init__.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 475.42428970336914,
"stage2_expand_ms": 12454.935789108276,
"stage3_cluster_ms": 8576.019525527954,
"stage4_rerank_ms": 2265.360116958618
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 100,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "error handling",
"latency_ms": 2648.7773999869823,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\__init__.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.6666666666666666,
"rbo_topk": 0.21230026104857144,
"staged_unique_files_topk": 10,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
},
{
"query": "how to parse json",
"staged": {
"strategy": "staged",
"query": "how to parse json",
"latency_ms": 42120.1860999763,
"num_results": 9,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py"
],
"stage_stats": {
"stage_times": {
"stage1_binary_ms": 570.8920955657959,
"stage2_expand_ms": 30054.06880378723,
"stage3_cluster_ms": 9285.51697731018,
"stage4_rerank_ms": 2142.771005630493
},
"stage_counts": {
"stage1_candidates": 100,
"stage2_expanded": 100,
"stage3_clustered": 20,
"stage4_reranked": 20
}
},
"error": null
},
"dense_rerank": {
"strategy": "dense_rerank",
"query": "how to parse json",
"latency_ms": 2511.8518999814987,
"num_results": 10,
"topk_paths": [
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\ann_index.py"
],
"stage_stats": null,
"error": null
},
"jaccard_topk": 0.5833333333333334,
"rbo_topk": 0.4799615561585714,
"staged_unique_files_topk": 9,
"dense_unique_files_topk": 10,
"staged_unique_dirs_topk": 4,
"dense_unique_dirs_topk": 4
}
]
}

View File

@@ -517,10 +517,11 @@ class LspBridge:
# Parse URI
uri = from_item.get("uri", "")
if uri.startswith("file:///"):
fp = uri[8:] if uri[8:9].isalpha() and uri[9:10] == ":" else uri[7:]
elif uri.startswith("file://"):
fp = uri[7:]
if uri.startswith("file://"):
raw = unquote(uri[7:]) # keep leading slash for Unix paths
if raw.startswith("/") and len(raw) > 2 and raw[2] == ":":
raw = raw[1:]
fp = raw
else:
fp = uri

View File

@@ -21,6 +21,7 @@ import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import unquote, urlparse
logger = logging.getLogger(__name__)
@@ -341,6 +342,7 @@ class StandaloneLspManager:
Returns:
ServerState for the appropriate language server, or None
"""
file_path = self._normalize_file_path(file_path)
language_id = self.get_language_id(file_path)
if not language_id:
logger.debug(f"No language server configured for: {file_path}")
@@ -357,6 +359,43 @@ class StandaloneLspManager:
# Start new server
return await self._start_server(language_id)
def _normalize_file_path(self, file_path_or_uri: str) -> str:
"""Normalize a file path that may be an LSP file URI or URI-path.
LSP responses often contain `file://` URIs with percent-encoding
(e.g. `file:///d%3A/...`). Some code paths may forward the parsed
URI path (`/d%3A/...`) without the scheme. On Windows, `Path(...)`
would interpret that as a root path on the current drive, producing
invalid paths like `D:\\d%3A\\...`.
"""
if not file_path_or_uri:
return file_path_or_uri
raw = str(file_path_or_uri).strip()
if raw.startswith("file:"):
try:
parsed = urlparse(raw)
if parsed.scheme == "file":
raw = unquote(parsed.path)
else:
raw = raw.replace("file:///", "").replace("file://", "")
except Exception:
raw = raw.replace("file:///", "").replace("file://", "")
# Decode percent-encoded segments (e.g. d%3A -> d:)
if "%3a" in raw.lower():
try:
raw = unquote(raw)
except Exception:
pass
# Windows: file URI paths frequently look like "/C:/path"; strip the extra slash.
if raw.startswith("/") and len(raw) > 2 and raw[2] == ":":
raw = raw[1:]
return raw
async def _initialize_server(self, state: ServerState) -> None:
"""Send initialize request and wait for response via the message queue.
@@ -771,6 +810,7 @@ class StandaloneLspManager:
def _to_text_document_identifier(self, file_path: str) -> Dict[str, str]:
"""Create TextDocumentIdentifier from file path."""
file_path = self._normalize_file_path(file_path)
uri = Path(file_path).resolve().as_uri()
return {"uri": uri}
@@ -783,6 +823,7 @@ class StandaloneLspManager:
async def _open_document(self, state: ServerState, file_path: str) -> None:
"""Send textDocument/didOpen notification."""
file_path = self._normalize_file_path(file_path)
resolved_path = Path(file_path).resolve()
try:
@@ -1044,7 +1085,7 @@ class StandaloneLspManager:
"""
# Determine language from item's uri
uri = item.get("uri", "")
file_path = uri.replace("file:///", "").replace("file://", "")
file_path = self._normalize_file_path(uri)
state = await self._get_server(file_path)
if not state:
@@ -1075,7 +1116,7 @@ class StandaloneLspManager:
"""
# Determine language from item's uri
uri = item.get("uri", "")
file_path = uri.replace("file:///", "").replace("file://", "")
file_path = self._normalize_file_path(uri)
state = await self._get_server(file_path)
if not state:

View File

@@ -0,0 +1,48 @@
"""Tests for StandaloneLspManager path normalization (Windows URI handling)."""
from __future__ import annotations
import platform
from codexlens.lsp.standalone_manager import StandaloneLspManager
def test_normalize_file_uri_percent_encoded_windows_drive() -> None:
if platform.system() != "Windows":
return
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
raw = "file:///d%3A/Claude_dms3/codex-lens/src/codexlens/lsp/standalone_manager.py"
normalized = manager._normalize_file_path(raw)
assert normalized.lower().startswith("d:/")
assert "%3a" not in normalized.lower()
assert "d%3a" not in normalized.lower()
assert "/d%3a" not in normalized.lower()
def test_normalize_uri_path_percent_encoded_windows_drive() -> None:
if platform.system() != "Windows":
return
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
raw = "/d%3A/Claude_dms3/codex-lens/src/codexlens/lsp/standalone_manager.py"
normalized = manager._normalize_file_path(raw)
assert normalized.lower().startswith("d:/")
assert "%3a" not in normalized.lower()
def test_normalize_plain_windows_path_is_unchanged() -> None:
if platform.system() != "Windows":
return
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
raw = r"D:\Claude_dms3\codex-lens\src\codexlens\lsp\standalone_manager.py"
normalized = manager._normalize_file_path(raw)
assert normalized == raw