superqode 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LSP Tools - Expose Language Server Protocol Operations to Agents.
|
|
3
|
+
|
|
4
|
+
Provides code intelligence operations through LSP including:
|
|
5
|
+
- Go to definition
|
|
6
|
+
- Find references
|
|
7
|
+
- Hover information (docs, types)
|
|
8
|
+
- Document symbols
|
|
9
|
+
- Workspace symbols
|
|
10
|
+
- Go to implementation
|
|
11
|
+
- Call hierarchy
|
|
12
|
+
|
|
13
|
+
Features:
|
|
14
|
+
- Multi-language support via configured language servers
|
|
15
|
+
- Async execution for non-blocking operations
|
|
16
|
+
- Graceful fallback when LSP unavailable
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
from .base import Tool, ToolResult, ToolContext
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LSPTool(Tool):
|
|
29
|
+
"""
|
|
30
|
+
Language Server Protocol operations tool.
|
|
31
|
+
|
|
32
|
+
Exposes LSP capabilities to agents for code intelligence
|
|
33
|
+
features like navigation, symbol lookup, and documentation.
|
|
34
|
+
|
|
35
|
+
Operations:
|
|
36
|
+
- goto_definition: Jump to where a symbol is defined
|
|
37
|
+
- find_references: Find all usages of a symbol
|
|
38
|
+
- hover: Get documentation and type information
|
|
39
|
+
- document_symbols: List all symbols in a file
|
|
40
|
+
- workspace_symbols: Search symbols across workspace
|
|
41
|
+
- goto_implementation: Find interface implementations
|
|
42
|
+
- call_hierarchy: Get incoming/outgoing function calls
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Supported LSP operations
|
|
46
|
+
OPERATIONS = [
|
|
47
|
+
"goto_definition",
|
|
48
|
+
"find_references",
|
|
49
|
+
"hover",
|
|
50
|
+
"document_symbols",
|
|
51
|
+
"workspace_symbols",
|
|
52
|
+
"goto_implementation",
|
|
53
|
+
"call_hierarchy",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def name(self) -> str:
|
|
58
|
+
return "lsp"
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
return """Language Server Protocol operations for code intelligence.
|
|
63
|
+
|
|
64
|
+
Operations:
|
|
65
|
+
- goto_definition: Find where a symbol is defined
|
|
66
|
+
- find_references: Find all references to a symbol
|
|
67
|
+
- hover: Get documentation and type info for symbol at position
|
|
68
|
+
- document_symbols: List all symbols (functions, classes, etc.) in a file
|
|
69
|
+
- workspace_symbols: Search for symbols across the entire workspace
|
|
70
|
+
- goto_implementation: Find implementations of interfaces/abstract classes
|
|
71
|
+
- call_hierarchy: Get functions that call or are called by a function"""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def parameters(self) -> Dict[str, Any]:
|
|
75
|
+
return {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"operation": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": self.OPERATIONS,
|
|
81
|
+
"description": "LSP operation to perform",
|
|
82
|
+
},
|
|
83
|
+
"file_path": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "Absolute or relative path to the file",
|
|
86
|
+
},
|
|
87
|
+
"line": {
|
|
88
|
+
"type": "integer",
|
|
89
|
+
"description": "1-based line number (required for position-based operations)",
|
|
90
|
+
},
|
|
91
|
+
"character": {
|
|
92
|
+
"type": "integer",
|
|
93
|
+
"description": "1-based column number (required for position-based operations)",
|
|
94
|
+
},
|
|
95
|
+
"query": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "Search query (for workspace_symbols operation)",
|
|
98
|
+
},
|
|
99
|
+
"direction": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": ["incoming", "outgoing"],
|
|
102
|
+
"description": "Direction for call_hierarchy (incoming = callers, outgoing = callees)",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
"required": ["operation", "file_path"],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
|
|
109
|
+
operation = args.get("operation", "")
|
|
110
|
+
file_path = args.get("file_path", "")
|
|
111
|
+
line = args.get("line", 1)
|
|
112
|
+
character = args.get("character", 1)
|
|
113
|
+
query = args.get("query", "")
|
|
114
|
+
direction = args.get("direction", "incoming")
|
|
115
|
+
|
|
116
|
+
if operation not in self.OPERATIONS:
|
|
117
|
+
return ToolResult(
|
|
118
|
+
success=False,
|
|
119
|
+
output="",
|
|
120
|
+
error=f"Unknown operation: {operation}. Valid operations: {', '.join(self.OPERATIONS)}",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if not file_path:
|
|
124
|
+
return ToolResult(success=False, output="", error="file_path is required")
|
|
125
|
+
|
|
126
|
+
# Resolve file path
|
|
127
|
+
target_path = Path(file_path)
|
|
128
|
+
if not target_path.is_absolute():
|
|
129
|
+
target_path = ctx.working_directory / target_path
|
|
130
|
+
|
|
131
|
+
if not target_path.exists() and operation != "workspace_symbols":
|
|
132
|
+
return ToolResult(success=False, output="", error=f"File not found: {file_path}")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Import LSP client
|
|
136
|
+
from superqode.lsp.client import LSPClient, LSPConfig
|
|
137
|
+
|
|
138
|
+
client = LSPClient(ctx.working_directory, LSPConfig())
|
|
139
|
+
|
|
140
|
+
# Determine language and start server
|
|
141
|
+
language = client._get_language(str(target_path))
|
|
142
|
+
if not language:
|
|
143
|
+
return ToolResult(
|
|
144
|
+
success=False,
|
|
145
|
+
output="",
|
|
146
|
+
error=f"No language server configured for file type: {target_path.suffix}",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
started = await client.start_server(language)
|
|
150
|
+
if not started:
|
|
151
|
+
return ToolResult(
|
|
152
|
+
success=False,
|
|
153
|
+
output="",
|
|
154
|
+
error=f"Failed to start language server for {language}",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Open the file to initialize
|
|
158
|
+
rel_path = str(target_path.relative_to(ctx.working_directory))
|
|
159
|
+
await client.open_file(rel_path)
|
|
160
|
+
|
|
161
|
+
# Wait for initialization
|
|
162
|
+
await asyncio.sleep(0.5)
|
|
163
|
+
|
|
164
|
+
# Dispatch to operation handler
|
|
165
|
+
try:
|
|
166
|
+
if operation == "goto_definition":
|
|
167
|
+
result = await self._goto_definition(
|
|
168
|
+
client, language, target_path, line, character
|
|
169
|
+
)
|
|
170
|
+
elif operation == "find_references":
|
|
171
|
+
result = await self._find_references(
|
|
172
|
+
client, language, target_path, line, character
|
|
173
|
+
)
|
|
174
|
+
elif operation == "hover":
|
|
175
|
+
result = await self._hover(client, language, target_path, line, character)
|
|
176
|
+
elif operation == "document_symbols":
|
|
177
|
+
result = await self._document_symbols(client, language, target_path)
|
|
178
|
+
elif operation == "workspace_symbols":
|
|
179
|
+
result = await self._workspace_symbols(client, language, query)
|
|
180
|
+
elif operation == "goto_implementation":
|
|
181
|
+
result = await self._goto_implementation(
|
|
182
|
+
client, language, target_path, line, character
|
|
183
|
+
)
|
|
184
|
+
elif operation == "call_hierarchy":
|
|
185
|
+
result = await self._call_hierarchy(
|
|
186
|
+
client, language, target_path, line, character, direction
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
result = ToolResult(success=False, output="", error="Operation not implemented")
|
|
190
|
+
finally:
|
|
191
|
+
await client.shutdown()
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
except ImportError:
|
|
196
|
+
return ToolResult(
|
|
197
|
+
success=False,
|
|
198
|
+
output="",
|
|
199
|
+
error="LSP client not available. Install language server dependencies.",
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
return ToolResult(success=False, output="", error=f"LSP operation failed: {str(e)}")
|
|
203
|
+
|
|
204
|
+
async def _goto_definition(
|
|
205
|
+
self, client: Any, language: str, file_path: Path, line: int, character: int
|
|
206
|
+
) -> ToolResult:
|
|
207
|
+
"""Go to symbol definition."""
|
|
208
|
+
uri = f"file://{file_path}"
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
result = await client._send_request(
|
|
212
|
+
language,
|
|
213
|
+
"textDocument/definition",
|
|
214
|
+
{
|
|
215
|
+
"textDocument": {"uri": uri},
|
|
216
|
+
"position": {"line": line - 1, "character": character - 1},
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if not result:
|
|
221
|
+
return ToolResult(
|
|
222
|
+
success=True,
|
|
223
|
+
output="No definition found at this position",
|
|
224
|
+
metadata={"operation": "goto_definition"},
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Handle single location or array
|
|
228
|
+
locations = result if isinstance(result, list) else [result]
|
|
229
|
+
|
|
230
|
+
output_lines = ["Definitions found:"]
|
|
231
|
+
for loc in locations:
|
|
232
|
+
loc_uri = loc.get("uri", "").replace("file://", "")
|
|
233
|
+
loc_range = loc.get("range", {})
|
|
234
|
+
start = loc_range.get("start", {})
|
|
235
|
+
output_lines.append(
|
|
236
|
+
f" {loc_uri}:{start.get('line', 0) + 1}:{start.get('character', 0) + 1}"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return ToolResult(
|
|
240
|
+
success=True,
|
|
241
|
+
output="\n".join(output_lines),
|
|
242
|
+
metadata={"operation": "goto_definition", "count": len(locations)},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
return ToolResult(success=False, output="", error=f"goto_definition failed: {str(e)}")
|
|
247
|
+
|
|
248
|
+
async def _find_references(
|
|
249
|
+
self, client: Any, language: str, file_path: Path, line: int, character: int
|
|
250
|
+
) -> ToolResult:
|
|
251
|
+
"""Find all references to a symbol."""
|
|
252
|
+
uri = f"file://{file_path}"
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
result = await client._send_request(
|
|
256
|
+
language,
|
|
257
|
+
"textDocument/references",
|
|
258
|
+
{
|
|
259
|
+
"textDocument": {"uri": uri},
|
|
260
|
+
"position": {"line": line - 1, "character": character - 1},
|
|
261
|
+
"context": {"includeDeclaration": True},
|
|
262
|
+
},
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if not result:
|
|
266
|
+
return ToolResult(
|
|
267
|
+
success=True,
|
|
268
|
+
output="No references found",
|
|
269
|
+
metadata={"operation": "find_references", "count": 0},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
output_lines = [f"References found ({len(result)}):"]
|
|
273
|
+
for loc in result[:50]: # Limit output
|
|
274
|
+
loc_uri = loc.get("uri", "").replace("file://", "")
|
|
275
|
+
loc_range = loc.get("range", {})
|
|
276
|
+
start = loc_range.get("start", {})
|
|
277
|
+
output_lines.append(
|
|
278
|
+
f" {loc_uri}:{start.get('line', 0) + 1}:{start.get('character', 0) + 1}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if len(result) > 50:
|
|
282
|
+
output_lines.append(f" ... and {len(result) - 50} more")
|
|
283
|
+
|
|
284
|
+
return ToolResult(
|
|
285
|
+
success=True,
|
|
286
|
+
output="\n".join(output_lines),
|
|
287
|
+
metadata={"operation": "find_references", "count": len(result)},
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
return ToolResult(success=False, output="", error=f"find_references failed: {str(e)}")
|
|
292
|
+
|
|
293
|
+
async def _hover(
|
|
294
|
+
self, client: Any, language: str, file_path: Path, line: int, character: int
|
|
295
|
+
) -> ToolResult:
|
|
296
|
+
"""Get hover information (docs, types)."""
|
|
297
|
+
uri = f"file://{file_path}"
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
result = await client._send_request(
|
|
301
|
+
language,
|
|
302
|
+
"textDocument/hover",
|
|
303
|
+
{
|
|
304
|
+
"textDocument": {"uri": uri},
|
|
305
|
+
"position": {"line": line - 1, "character": character - 1},
|
|
306
|
+
},
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if not result:
|
|
310
|
+
return ToolResult(
|
|
311
|
+
success=True,
|
|
312
|
+
output="No hover information available at this position",
|
|
313
|
+
metadata={"operation": "hover"},
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
contents = result.get("contents", {})
|
|
317
|
+
|
|
318
|
+
# Handle different content formats
|
|
319
|
+
if isinstance(contents, str):
|
|
320
|
+
output = contents
|
|
321
|
+
elif isinstance(contents, dict):
|
|
322
|
+
output = contents.get("value", str(contents))
|
|
323
|
+
elif isinstance(contents, list):
|
|
324
|
+
parts = []
|
|
325
|
+
for item in contents:
|
|
326
|
+
if isinstance(item, str):
|
|
327
|
+
parts.append(item)
|
|
328
|
+
elif isinstance(item, dict):
|
|
329
|
+
parts.append(item.get("value", str(item)))
|
|
330
|
+
output = "\n\n".join(parts)
|
|
331
|
+
else:
|
|
332
|
+
output = str(contents)
|
|
333
|
+
|
|
334
|
+
return ToolResult(
|
|
335
|
+
success=True,
|
|
336
|
+
output=f"Hover information:\n{output}",
|
|
337
|
+
metadata={"operation": "hover"},
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
return ToolResult(success=False, output="", error=f"hover failed: {str(e)}")
|
|
342
|
+
|
|
343
|
+
async def _document_symbols(self, client: Any, language: str, file_path: Path) -> ToolResult:
|
|
344
|
+
"""List all symbols in a document."""
|
|
345
|
+
uri = f"file://{file_path}"
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
result = await client._send_request(
|
|
349
|
+
language, "textDocument/documentSymbol", {"textDocument": {"uri": uri}}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if not result:
|
|
353
|
+
return ToolResult(
|
|
354
|
+
success=True,
|
|
355
|
+
output="No symbols found in document",
|
|
356
|
+
metadata={"operation": "document_symbols", "count": 0},
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Format symbols
|
|
360
|
+
symbols = self._format_symbols(result)
|
|
361
|
+
|
|
362
|
+
return ToolResult(
|
|
363
|
+
success=True,
|
|
364
|
+
output=f"Document symbols ({len(symbols)} top-level):\n" + "\n".join(symbols),
|
|
365
|
+
metadata={"operation": "document_symbols", "count": len(result)},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
return ToolResult(success=False, output="", error=f"document_symbols failed: {str(e)}")
|
|
370
|
+
|
|
371
|
+
def _format_symbols(self, symbols: List[Dict], indent: int = 0) -> List[str]:
|
|
372
|
+
"""Format symbols hierarchically."""
|
|
373
|
+
result = []
|
|
374
|
+
prefix = " " * indent
|
|
375
|
+
|
|
376
|
+
# Symbol kind names
|
|
377
|
+
kind_names = {
|
|
378
|
+
1: "File",
|
|
379
|
+
2: "Module",
|
|
380
|
+
3: "Namespace",
|
|
381
|
+
4: "Package",
|
|
382
|
+
5: "Class",
|
|
383
|
+
6: "Method",
|
|
384
|
+
7: "Property",
|
|
385
|
+
8: "Field",
|
|
386
|
+
9: "Constructor",
|
|
387
|
+
10: "Enum",
|
|
388
|
+
11: "Interface",
|
|
389
|
+
12: "Function",
|
|
390
|
+
13: "Variable",
|
|
391
|
+
14: "Constant",
|
|
392
|
+
15: "String",
|
|
393
|
+
16: "Number",
|
|
394
|
+
17: "Boolean",
|
|
395
|
+
18: "Array",
|
|
396
|
+
19: "Object",
|
|
397
|
+
20: "Key",
|
|
398
|
+
21: "Null",
|
|
399
|
+
22: "EnumMember",
|
|
400
|
+
23: "Struct",
|
|
401
|
+
24: "Event",
|
|
402
|
+
25: "Operator",
|
|
403
|
+
26: "TypeParameter",
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for sym in symbols:
|
|
407
|
+
name = sym.get("name", "unknown")
|
|
408
|
+
kind = sym.get("kind", 0)
|
|
409
|
+
kind_name = kind_names.get(kind, "Unknown")
|
|
410
|
+
|
|
411
|
+
# Get range for line number
|
|
412
|
+
sym_range = sym.get("range", sym.get("location", {}).get("range", {}))
|
|
413
|
+
start_line = sym_range.get("start", {}).get("line", 0) + 1
|
|
414
|
+
|
|
415
|
+
result.append(f"{prefix}{kind_name}: {name} (line {start_line})")
|
|
416
|
+
|
|
417
|
+
# Handle children (DocumentSymbol format)
|
|
418
|
+
children = sym.get("children", [])
|
|
419
|
+
if children:
|
|
420
|
+
result.extend(self._format_symbols(children, indent + 1))
|
|
421
|
+
|
|
422
|
+
return result
|
|
423
|
+
|
|
424
|
+
async def _workspace_symbols(self, client: Any, language: str, query: str) -> ToolResult:
|
|
425
|
+
"""Search for symbols across workspace."""
|
|
426
|
+
try:
|
|
427
|
+
result = await client._send_request(language, "workspace/symbol", {"query": query})
|
|
428
|
+
|
|
429
|
+
if not result:
|
|
430
|
+
return ToolResult(
|
|
431
|
+
success=True,
|
|
432
|
+
output=f"No symbols found matching '{query}'",
|
|
433
|
+
metadata={"operation": "workspace_symbols", "count": 0},
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
output_lines = [f"Workspace symbols matching '{query}' ({len(result)}):"]
|
|
437
|
+
|
|
438
|
+
for sym in result[:50]: # Limit output
|
|
439
|
+
name = sym.get("name", "unknown")
|
|
440
|
+
kind = sym.get("kind", 0)
|
|
441
|
+
location = sym.get("location", {})
|
|
442
|
+
uri = location.get("uri", "").replace("file://", "")
|
|
443
|
+
sym_range = location.get("range", {})
|
|
444
|
+
start = sym_range.get("start", {})
|
|
445
|
+
|
|
446
|
+
output_lines.append(
|
|
447
|
+
f" {name} ({self._kind_name(kind)}) - {uri}:{start.get('line', 0) + 1}"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if len(result) > 50:
|
|
451
|
+
output_lines.append(f" ... and {len(result) - 50} more")
|
|
452
|
+
|
|
453
|
+
return ToolResult(
|
|
454
|
+
success=True,
|
|
455
|
+
output="\n".join(output_lines),
|
|
456
|
+
metadata={"operation": "workspace_symbols", "count": len(result)},
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
except Exception as e:
|
|
460
|
+
return ToolResult(success=False, output="", error=f"workspace_symbols failed: {str(e)}")
|
|
461
|
+
|
|
462
|
+
def _kind_name(self, kind: int) -> str:
|
|
463
|
+
"""Get symbol kind name."""
|
|
464
|
+
kind_names = {
|
|
465
|
+
1: "File",
|
|
466
|
+
2: "Module",
|
|
467
|
+
3: "Namespace",
|
|
468
|
+
4: "Package",
|
|
469
|
+
5: "Class",
|
|
470
|
+
6: "Method",
|
|
471
|
+
7: "Property",
|
|
472
|
+
8: "Field",
|
|
473
|
+
9: "Constructor",
|
|
474
|
+
10: "Enum",
|
|
475
|
+
11: "Interface",
|
|
476
|
+
12: "Function",
|
|
477
|
+
13: "Variable",
|
|
478
|
+
14: "Constant",
|
|
479
|
+
15: "String",
|
|
480
|
+
16: "Number",
|
|
481
|
+
17: "Boolean",
|
|
482
|
+
18: "Array",
|
|
483
|
+
19: "Object",
|
|
484
|
+
20: "Key",
|
|
485
|
+
21: "Null",
|
|
486
|
+
22: "EnumMember",
|
|
487
|
+
23: "Struct",
|
|
488
|
+
24: "Event",
|
|
489
|
+
25: "Operator",
|
|
490
|
+
26: "TypeParameter",
|
|
491
|
+
}
|
|
492
|
+
return kind_names.get(kind, "Unknown")
|
|
493
|
+
|
|
494
|
+
async def _goto_implementation(
|
|
495
|
+
self, client: Any, language: str, file_path: Path, line: int, character: int
|
|
496
|
+
) -> ToolResult:
|
|
497
|
+
"""Go to implementation of interface/abstract."""
|
|
498
|
+
uri = f"file://{file_path}"
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
result = await client._send_request(
|
|
502
|
+
language,
|
|
503
|
+
"textDocument/implementation",
|
|
504
|
+
{
|
|
505
|
+
"textDocument": {"uri": uri},
|
|
506
|
+
"position": {"line": line - 1, "character": character - 1},
|
|
507
|
+
},
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
if not result:
|
|
511
|
+
return ToolResult(
|
|
512
|
+
success=True,
|
|
513
|
+
output="No implementations found",
|
|
514
|
+
metadata={"operation": "goto_implementation", "count": 0},
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
locations = result if isinstance(result, list) else [result]
|
|
518
|
+
|
|
519
|
+
output_lines = [f"Implementations found ({len(locations)}):"]
|
|
520
|
+
for loc in locations[:50]:
|
|
521
|
+
loc_uri = loc.get("uri", "").replace("file://", "")
|
|
522
|
+
loc_range = loc.get("range", {})
|
|
523
|
+
start = loc_range.get("start", {})
|
|
524
|
+
output_lines.append(
|
|
525
|
+
f" {loc_uri}:{start.get('line', 0) + 1}:{start.get('character', 0) + 1}"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
if len(locations) > 50:
|
|
529
|
+
output_lines.append(f" ... and {len(locations) - 50} more")
|
|
530
|
+
|
|
531
|
+
return ToolResult(
|
|
532
|
+
success=True,
|
|
533
|
+
output="\n".join(output_lines),
|
|
534
|
+
metadata={"operation": "goto_implementation", "count": len(locations)},
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
except Exception as e:
|
|
538
|
+
return ToolResult(
|
|
539
|
+
success=False, output="", error=f"goto_implementation failed: {str(e)}"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
async def _call_hierarchy(
|
|
543
|
+
self, client: Any, language: str, file_path: Path, line: int, character: int, direction: str
|
|
544
|
+
) -> ToolResult:
|
|
545
|
+
"""Get call hierarchy (callers or callees)."""
|
|
546
|
+
uri = f"file://{file_path}"
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
# First, prepare call hierarchy
|
|
550
|
+
prep_result = await client._send_request(
|
|
551
|
+
language,
|
|
552
|
+
"textDocument/prepareCallHierarchy",
|
|
553
|
+
{
|
|
554
|
+
"textDocument": {"uri": uri},
|
|
555
|
+
"position": {"line": line - 1, "character": character - 1},
|
|
556
|
+
},
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if not prep_result:
|
|
560
|
+
return ToolResult(
|
|
561
|
+
success=True,
|
|
562
|
+
output="No call hierarchy available at this position",
|
|
563
|
+
metadata={"operation": "call_hierarchy"},
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
items = prep_result if isinstance(prep_result, list) else [prep_result]
|
|
567
|
+
|
|
568
|
+
output_lines = []
|
|
569
|
+
|
|
570
|
+
for item in items:
|
|
571
|
+
item_name = item.get("name", "unknown")
|
|
572
|
+
output_lines.append(f"Call hierarchy for: {item_name}")
|
|
573
|
+
|
|
574
|
+
# Get incoming or outgoing calls
|
|
575
|
+
if direction == "incoming":
|
|
576
|
+
calls = await client._send_request(
|
|
577
|
+
language, "callHierarchy/incomingCalls", {"item": item}
|
|
578
|
+
)
|
|
579
|
+
label = "Called by"
|
|
580
|
+
else:
|
|
581
|
+
calls = await client._send_request(
|
|
582
|
+
language, "callHierarchy/outgoingCalls", {"item": item}
|
|
583
|
+
)
|
|
584
|
+
label = "Calls"
|
|
585
|
+
|
|
586
|
+
if not calls:
|
|
587
|
+
output_lines.append(f" No {direction} calls found")
|
|
588
|
+
else:
|
|
589
|
+
output_lines.append(f" {label} ({len(calls)}):")
|
|
590
|
+
for call in calls[:30]:
|
|
591
|
+
call_item = call.get("from" if direction == "incoming" else "to", {})
|
|
592
|
+
call_name = call_item.get("name", "unknown")
|
|
593
|
+
call_uri = call_item.get("uri", "").replace("file://", "")
|
|
594
|
+
call_range = call_item.get("range", {})
|
|
595
|
+
start = call_range.get("start", {})
|
|
596
|
+
output_lines.append(
|
|
597
|
+
f" {call_name} - {call_uri}:{start.get('line', 0) + 1}"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if len(calls) > 30:
|
|
601
|
+
output_lines.append(f" ... and {len(calls) - 30} more")
|
|
602
|
+
|
|
603
|
+
return ToolResult(
|
|
604
|
+
success=True,
|
|
605
|
+
output="\n".join(output_lines),
|
|
606
|
+
metadata={"operation": "call_hierarchy", "direction": direction},
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
except Exception as e:
|
|
610
|
+
return ToolResult(success=False, output="", error=f"call_hierarchy failed: {str(e)}")
|