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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. 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)}")