feat: 添加多个 LSP 测试示例,包括能力测试、调用层次和原始 LSP 测试

This commit is contained in:
catlog22
2026-01-21 10:43:53 +08:00
parent 200812d204
commit f7dd3d23ff
9 changed files with 690 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
"""Debug URI format issues."""
import asyncio
from pathlib import Path
from urllib.parse import quote
def test_uri_formats():
"""Compare different URI formats."""
file_path = Path("D:/Claude_dms3/codex-lens/test_simple_function.py")
print("URI Format Comparison")
print("="*80)
# Method 1: Path.as_uri()
uri1 = file_path.resolve().as_uri()
print(f"1. Path.as_uri(): {uri1}")
# Method 2: Manual construction
uri2 = f"file:///{str(file_path.resolve()).replace(chr(92), '/')}"
print(f"2. Manual (forward /): {uri2}")
# Method 3: With quote
path_str = str(file_path.resolve()).replace(chr(92), '/')
uri3 = f"file:///{quote(path_str, safe='/:')}"
print(f"3. With quote: {uri3}")
# Method 4: Lowercase drive
path_lower = str(file_path.resolve()).replace(chr(92), '/')
if len(path_lower) > 1 and path_lower[1] == ':':
path_lower = path_lower[0].lower() + path_lower[1:]
uri4 = f"file:///{path_lower}"
print(f"4. Lowercase drive: {uri4}")
# What Pyright shows in logs
print(f"\n5. Pyright log format: file:///d%3A/Claude_dms3/codex-lens/...")
return uri1, uri4
if __name__ == "__main__":
test_uri_formats()

View File

@@ -0,0 +1,79 @@
"""Test LSP server capabilities."""
import asyncio
import json
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
async def test_capabilities():
"""Test what capabilities Pyright provides."""
workspace_root = Path("D:/Claude_dms3/codex-lens/src")
print("Testing LSP Capabilities")
print("="*80)
# Create LSP manager
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=10.0,
)
try:
# Start LSP manager
print("\n1. Starting LSP manager...")
await manager.start()
print(" [OK] LSP manager started")
# Get server state for Python
print("\n2. Getting Python server state...")
test_file = str(workspace_root / "codexlens/search/hybrid_search.py")
state = await manager._get_server(test_file)
if not state:
print(" [ERROR] Could not get server state!")
return
print(f" [OK] Server state obtained")
print(f" Initialized: {state.initialized}")
# Print capabilities
print("\n3. Server Capabilities:")
print("-"*80)
caps = state.capabilities
# Key capabilities to check
important_caps = [
"callHierarchyProvider",
"definitionProvider",
"referencesProvider",
"documentSymbolProvider",
"workspaceSymbolProvider",
"hoverProvider",
"completionProvider",
"signatureHelpProvider",
]
for cap in important_caps:
value = caps.get(cap)
status = "[YES]" if value else "[NO]"
print(f" {status} {cap}: {value}")
# Print all capabilities as JSON for reference
print("\n4. Full capabilities (formatted):")
print("-"*80)
print(json.dumps(caps, indent=2))
except Exception as e:
print(f"\n[ERROR] Error: {e}")
import traceback
traceback.print_exc()
finally:
# Cleanup
print("\n5. Cleaning up...")
await manager.stop()
print(" [OK] LSP manager stopped")
if __name__ == "__main__":
asyncio.run(test_capabilities())

View File

@@ -0,0 +1,76 @@
"""Test LSP references as alternative to call hierarchy."""
import asyncio
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
async def test_references():
"""Test using references as alternative to call hierarchy."""
workspace_root = Path("D:/Claude_dms3/codex-lens")
test_file = workspace_root / "test_simple_function.py"
print("Testing LSP References (Alternative)")
print("="*80)
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=30.0,
)
try:
print("\n1. Starting LSP manager...")
await manager.start()
print(" [OK] Started")
# Wait for analysis
await asyncio.sleep(2)
# Test references for hello_world function
print("\n2. Testing references for 'hello_world' (line 4)...")
refs = await manager.get_references(
file_path=str(test_file),
line=4,
character=5,
include_declaration=True,
)
print(f" Found: {len(refs)} references")
for ref in refs[:5]:
uri = ref.get('uri', '')
range_obj = ref.get('range', {})
start = range_obj.get('start', {})
print(f" - {uri.split('/')[-1]}:{start.get('line', 0)+1}")
# Test definition
print("\n3. Testing definition for 'hello_world' call (line 13)...")
defs = await manager.get_definition(
file_path=str(test_file),
line=13,
character=11,
)
print(f" Found: {len(defs)} definitions")
for d in defs:
uri = d.get('uri', '')
range_obj = d.get('range', {})
start = range_obj.get('start', {})
print(f" - {uri.split('/')[-1]}:{start.get('line', 0)+1}")
# Test document symbols
print("\n4. Testing document symbols...")
symbols = await manager.get_document_symbols(str(test_file))
print(f" Found: {len(symbols)} symbols")
for sym in symbols:
print(f" - {sym.get('name')} ({sym.get('kind')})")
except Exception as e:
print(f"\n[ERROR] {e}")
import traceback
traceback.print_exc()
finally:
print("\n5. Cleanup...")
await manager.stop()
print(" [OK] Done")
if __name__ == "__main__":
asyncio.run(test_references())

View File

@@ -0,0 +1,91 @@
"""Test LSP Association Tree building directly."""
import asyncio
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
from codexlens.search.association_tree import AssociationTreeBuilder
async def test_lsp_tree():
"""Test building LSP association tree for a known Python file."""
# Setup
workspace_root = Path("D:/Claude_dms3/codex-lens/src")
test_file = "codexlens/search/hybrid_search.py"
test_line = 115 # search() method definition
print(f"Testing LSP tree for: {test_file}:{test_line}")
print("="*80)
# Create LSP manager
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=10.0,
)
try:
# Start LSP manager
print("\n1. Starting LSP manager...")
await manager.start()
print(" [OK] LSP manager started")
# Test get_call_hierarchy_items directly
print(f"\n2. Testing get_call_hierarchy_items for {test_file}:{test_line}...")
items = await manager.get_call_hierarchy_items(
file_path=str(workspace_root / test_file),
line=test_line,
character=10,
)
print(f" Result: {len(items)} items")
if items:
for i, item in enumerate(items, 1):
print(f" {i}. {item.get('name')} ({item.get('kind')})")
print(f" URI: {item.get('uri')}")
print(f" Range: {item.get('range')}")
else:
print(" [WARN] No call hierarchy items returned!")
print(" This means either:")
print(" - The file/line doesn't contain a symbol")
print(" - LSP server doesn't support call hierarchy")
print(" - Pyright isn't running correctly")
# If we got items, try building a tree
if items:
print(f"\n3. Building association tree...")
builder = AssociationTreeBuilder(
lsp_manager=manager,
timeout=10.0,
)
tree = await builder.build_tree(
seed_file_path=str(workspace_root / test_file),
seed_line=test_line,
seed_character=10,
max_depth=2,
expand_callers=True,
expand_callees=True,
)
print(f" Tree built successfully!")
print(f" - Roots: {len(tree.roots)}")
print(f" - Total nodes: {len(tree.node_list)}")
print(f" - Depth reached: {tree.depth_reached}")
if tree.node_list:
print(f"\n First 5 nodes:")
for i, node in enumerate(tree.node_list[:5], 1):
print(f" {i}. {node.item.name} @ {node.item.file_path}:{node.item.range.start_line}")
print(f" Depth: {node.depth}, Is cycle: {node.is_cycle}")
except Exception as e:
print(f"\n[ERROR] Error: {e}")
import traceback
traceback.print_exc()
finally:
# Cleanup
print("\n4. Cleaning up...")
await manager.stop()
print(" [OK] LSP manager stopped")
if __name__ == "__main__":
asyncio.run(test_lsp_tree())

View File

@@ -0,0 +1,104 @@
"""Raw LSP test with debug logging."""
import asyncio
import json
import logging
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("codexlens.lsp")
logger.setLevel(logging.DEBUG)
async def test_raw_lsp():
"""Test LSP with debug logging enabled."""
workspace_root = Path("D:/Claude_dms3/codex-lens")
test_file = workspace_root / "test_simple_function.py"
print("Testing Raw LSP Call Hierarchy")
print("="*80)
# Create LSP manager
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=30.0,
)
try:
# Start LSP manager
print("\n1. Starting LSP manager...")
await manager.start()
print(" [OK] Started")
# Get server state
state = await manager._get_server(str(test_file))
if not state:
print(" [ERROR] No server state!")
return
print(f" Server initialized: {state.initialized}")
print(f" Call hierarchy supported: {state.capabilities.get('callHierarchyProvider')}")
# Open document
print("\n2. Opening document...")
await manager._open_document(state, str(test_file))
print(" [OK] Document opened")
# Wait a bit for Pyright to analyze
print("\n3. Waiting for analysis...")
await asyncio.sleep(2)
print(" [OK] Waited 2 seconds")
# Try call hierarchy on main function (line 12)
print("\n4. Sending prepareCallHierarchy request...")
# Direct request using _send_request
params = {
"textDocument": {"uri": test_file.as_uri()},
"position": {"line": 11, "character": 4} # 0-indexed, "main" function
}
print(f" Params: {json.dumps(params, indent=2)}")
result = await manager._send_request(
state,
"textDocument/prepareCallHierarchy",
params,
)
print(f"\n5. Result: {result}")
print(f" Type: {type(result)}")
if result:
print(f" Items: {len(result)}")
for item in result:
print(f" - {item.get('name')}")
else:
print(" [WARN] No items returned")
print(" This could mean:")
print(" - Position doesn't point to a symbol")
print(" - Pyright hasn't finished analyzing")
print(" - Some other issue")
# Try with the higher-level API
print("\n6. Testing with get_call_hierarchy_items API...")
items = await manager.get_call_hierarchy_items(
file_path=str(test_file),
line=12,
character=5,
)
print(f" Result: {len(items)} items")
except Exception as e:
print(f"\n[ERROR] Error: {e}")
import traceback
traceback.print_exc()
finally:
print("\n7. Cleanup...")
await manager.stop()
print(" [OK] Done")
if __name__ == "__main__":
asyncio.run(test_raw_lsp())

View File

@@ -0,0 +1,96 @@
"""Test to see raw LSP response."""
import asyncio
import json
import logging
from pathlib import Path
# Patch the _process_messages to log the full response
async def patched_process_messages(self, language_id: str):
"""Patched version that logs full response."""
from codexlens.lsp.standalone_manager import logger
state = self._servers.get(language_id)
if not state:
return
try:
while True:
message = await state.message_queue.get()
msg_id = message.get("id")
method = message.get("method", "")
# Log FULL message for debugging
if msg_id is not None and not method:
print(f"\n>>> FULL RESPONSE (id={msg_id}):")
print(json.dumps(message, indent=2))
# Response handling
if msg_id is not None and not method:
if msg_id in state.pending_requests:
future = state.pending_requests.pop(msg_id)
if "error" in message:
print(f">>> ERROR in response: {message['error']}")
future.set_exception(
Exception(message["error"].get("message", "Unknown error"))
)
else:
print(f">>> Result: {message.get('result')}")
future.set_result(message.get("result"))
else:
print(f">>> No pending request for id={msg_id}")
elif msg_id is not None and method:
await self._handle_server_request(state, message)
elif method:
pass # Skip notifications
state.message_queue.task_done()
except asyncio.CancelledError:
pass
async def test_raw():
from codexlens.lsp.standalone_manager import StandaloneLspManager
workspace_root = Path("D:/Claude_dms3/codex-lens")
test_file = workspace_root / "test_simple_function.py"
manager = StandaloneLspManager(workspace_root=str(workspace_root), timeout=30.0)
# Monkey-patch the method
import types
manager._process_messages = types.MethodType(patched_process_messages, manager)
try:
print("Starting LSP...")
await manager.start()
state = await manager._get_server(str(test_file))
await manager._open_document(state, str(test_file))
await asyncio.sleep(2)
print("\nSending prepareCallHierarchy request...")
uri = test_file.resolve().as_uri()
params = {
"textDocument": {"uri": uri},
"position": {"line": 11, "character": 4}
}
# Need to restart the message processor with our patched version
# Actually, the original is already running. Let's just send and see logs.
result = await manager._send_request(
state,
"textDocument/prepareCallHierarchy",
params
)
print(f"\nFinal result: {result}")
finally:
await manager.stop()
if __name__ == "__main__":
asyncio.run(test_raw())

View File

@@ -0,0 +1,87 @@
"""Test call hierarchy on a simple Python file."""
import asyncio
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
async def test_simple_call_hierarchy():
"""Test call hierarchy on test_simple_function.py."""
workspace_root = Path("D:/Claude_dms3/codex-lens")
test_file = workspace_root / "test_simple_function.py"
print("Testing Call Hierarchy on Simple Function")
print("="*80)
print(f"File: {test_file}")
# Create LSP manager
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=10.0,
)
try:
# Start LSP manager
print("\n1. Starting LSP manager...")
await manager.start()
print(" [OK] LSP manager started")
# Test different function positions
test_cases = [
("hello_world", 4, 5, "def hello_world():"),
("greet", 8, 5, "def greet(name: str):"),
("main", 12, 5, "def main():"),
]
for func_name, line, char, expected in test_cases:
print(f"\n2. Testing {func_name} at line {line}:")
print(f" Expected: {expected}")
items = await manager.get_call_hierarchy_items(
file_path=str(test_file),
line=line,
character=char,
)
print(f" Result: {len(items)} items")
if items:
for i, item in enumerate(items, 1):
print(f" {i}. Name: {item.get('name')}")
print(f" Kind: {item.get('kind')}")
print(f" URI: {item.get('uri')}")
range_obj = item.get('range', {})
start = range_obj.get('start', {})
print(f" Line: {start.get('line', 0) + 1}")
# If we got items, try getting incoming/outgoing calls
print(f"\n Testing incoming/outgoing calls for {func_name}:")
first_item = items[0]
incoming = await manager.get_incoming_calls(first_item)
print(f" - Incoming calls: {len(incoming)}")
for call in incoming:
caller = call.get('from', {})
print(f" Called by: {caller.get('name')}")
outgoing = await manager.get_outgoing_calls(first_item)
print(f" - Outgoing calls: {len(outgoing)}")
for call in outgoing:
callee = call.get('to', {})
print(f" Calls: {callee.get('name')}")
else:
print(f" [WARN] No call hierarchy items for {func_name}!")
except Exception as e:
print(f"\n[ERROR] Error: {e}")
import traceback
traceback.print_exc()
finally:
# Cleanup
print("\n3. Cleaning up...")
await manager.stop()
print(" [OK] LSP manager stopped")
if __name__ == "__main__":
asyncio.run(test_simple_call_hierarchy())

View File

@@ -0,0 +1,98 @@
"""Test if URI inconsistency causes the issue."""
import asyncio
import json
from pathlib import Path
from codexlens.lsp.standalone_manager import StandaloneLspManager
async def test_with_consistent_uri():
"""Test prepareCallHierarchy with different URI formats."""
workspace_root = Path("D:/Claude_dms3/codex-lens")
test_file = workspace_root / "test_simple_function.py"
resolved = test_file.resolve()
print("Testing URI Consistency")
print("="*80)
# Different URI formats to try
uri_standard = resolved.as_uri()
uri_lowercase = uri_standard.replace("file:///D:", "file:///d:")
print(f"Standard URI: {uri_standard}")
print(f"Lowercase URI: {uri_lowercase}")
manager = StandaloneLspManager(
workspace_root=str(workspace_root),
timeout=30.0,
)
try:
print("\n1. Starting LSP manager...")
await manager.start()
state = await manager._get_server(str(test_file))
if not state:
print(" [ERROR] No server state")
return
print(" [OK] Server ready")
# Open document
print("\n2. Opening document...")
await manager._open_document(state, str(test_file))
await asyncio.sleep(2)
print(" [OK] Document opened, waited 2s")
# Test 1: Standard URI (as_uri)
print("\n3. Test with standard URI...")
params1 = {
"textDocument": {"uri": uri_standard},
"position": {"line": 11, "character": 4} # main function
}
print(f" Params: {json.dumps(params1)}")
result1 = await manager._send_request(state, "textDocument/prepareCallHierarchy", params1)
print(f" Result: {result1}")
# Test 2: Lowercase drive letter
print("\n4. Test with lowercase drive letter URI...")
params2 = {
"textDocument": {"uri": uri_lowercase},
"position": {"line": 11, "character": 4}
}
print(f" Params: {json.dumps(params2)}")
result2 = await manager._send_request(state, "textDocument/prepareCallHierarchy", params2)
print(f" Result: {result2}")
# Test 3: Position at function name start
print("\n5. Test with position at 'def' keyword (char 0)...")
params3 = {
"textDocument": {"uri": uri_lowercase},
"position": {"line": 11, "character": 0}
}
result3 = await manager._send_request(state, "textDocument/prepareCallHierarchy", params3)
print(f" Result: {result3}")
# Test 4: Different positions on line 12 (1-indexed = line 11 0-indexed)
print("\n6. Testing different character positions on 'def main():'...")
for char in [0, 4, 5, 6, 7, 8]:
params = {
"textDocument": {"uri": uri_lowercase},
"position": {"line": 11, "character": char}
}
result = await manager._send_request(state, "textDocument/prepareCallHierarchy", params)
status = "OK" if result else "None"
print(f" char={char}: {status} - {result[:1] if result else '[]'}")
except Exception as e:
print(f"\n[ERROR] {e}")
import traceback
traceback.print_exc()
finally:
print("\n7. Cleanup...")
await manager.stop()
print(" [OK]")
if __name__ == "__main__":
asyncio.run(test_with_consistent_uri())

View File

@@ -0,0 +1,19 @@
"""Simple test file with clear function definitions."""
def hello_world():
"""A simple function."""
return "Hello, World!"
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
def main():
"""Main function that calls other functions."""
msg = hello_world()
greeting = greet("Alice")
print(msg)
print(greeting)
if __name__ == "__main__":
main()