@trac3er/oh-my-god 2.0.0 → 2.0.2
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.
- package/.claude-plugin/marketplace.json +8 -8
- package/.claude-plugin/plugin.json +5 -4
- package/.claude-plugin/scripts/uninstall.sh +74 -3
- package/.claude-plugin/scripts/update.sh +78 -3
- package/.coveragerc +26 -0
- package/.mcp.json +4 -4
- package/CHANGELOG.md +14 -0
- package/CODE_OF_CONDUCT.md +27 -0
- package/CONTRIBUTING.md +62 -0
- package/OMG-setup.sh +1201 -355
- package/README.md +77 -56
- package/SECURITY.md +25 -0
- package/agents/__init__.py +1 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-architect-mode.md +3 -5
- package/agents/omg-backend-engineer.md +3 -5
- package/agents/omg-database-engineer.md +3 -5
- package/agents/omg-frontend-designer.md +4 -5
- package/agents/omg-implement-mode.md +4 -5
- package/agents/omg-infra-engineer.md +3 -5
- package/agents/omg-research-mode.md +4 -6
- package/agents/omg-security-auditor.md +3 -5
- package/agents/omg-testing-engineer.md +3 -5
- package/build/lib/yaml.py +321 -0
- package/commands/OMG:ai-commit.md +101 -14
- package/commands/OMG:arch.md +302 -19
- package/commands/OMG:ccg.md +12 -7
- package/commands/OMG:compat.md +25 -17
- package/commands/OMG:cost.md +173 -13
- package/commands/OMG:crazy.md +1 -1
- package/commands/OMG:create-agent.md +170 -20
- package/commands/OMG:deps.md +235 -17
- package/commands/OMG:domain-init.md +1 -1
- package/commands/OMG:escalate.md +41 -12
- package/commands/OMG:health-check.md +37 -13
- package/commands/OMG:init.md +122 -14
- package/commands/OMG:project-init.md +1 -1
- package/commands/OMG:session-branch.md +76 -9
- package/commands/OMG:session-fork.md +42 -5
- package/commands/OMG:session-merge.md +124 -8
- package/commands/OMG:setup.md +69 -12
- package/commands/OMG:stats.md +215 -14
- package/commands/OMG:teams.md +19 -10
- package/config/lsp_languages.yaml +8 -0
- package/hooks/__init__.py +0 -0
- package/hooks/_agent_registry.py +423 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +569 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +236 -0
- package/hooks/budget_governor.py +232 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +216 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +1020 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +48 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +95 -0
- package/hooks/intentgate-keyword-detector.py +188 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +505 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +219 -0
- package/hooks/post_write.py +46 -0
- package/hooks/pre-compact.py +398 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +672 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +61 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +277 -0
- package/hooks/setup_wizard.py +582 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +225 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +945 -0
- package/hooks/test-validator.py +361 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +149 -0
- package/hooks/trust_review.py +585 -0
- package/hud/omg-hud.mjs +31 -1
- package/lab/__init__.py +1 -0
- package/lab/pipeline.py +75 -0
- package/lab/policies.py +52 -0
- package/package.json +7 -18
- package/plugins/README.md +33 -61
- package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
- package/plugins/advanced/commands/OMG:learn.md +1 -1
- package/plugins/advanced/commands/OMG:security-review.md +3 -3
- package/plugins/advanced/commands/OMG:ship.md +1 -1
- package/plugins/advanced/plugin.json +1 -1
- package/plugins/core/plugin.json +8 -3
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/cve_scanner.py +188 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +169 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +81 -0
- package/rules/contextual/write-verify.md +2 -2
- package/rules/core/00-truth.md +1 -1
- package/rules/core/01-surgical.md +1 -1
- package/rules/core/02-circuit-breaker.md +2 -2
- package/rules/core/03-ensemble.md +3 -3
- package/rules/core/04-testing.md +3 -3
- package/runtime/__init__.py +32 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/gpt.py +53 -0
- package/runtime/adapters/local.py +53 -0
- package/runtime/adoption.py +212 -0
- package/runtime/business_workflow.py +220 -0
- package/runtime/cli_provider.py +85 -0
- package/runtime/compat.py +1299 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +115 -0
- package/runtime/mcp_lifecycle.py +153 -0
- package/runtime/mcp_memory_server.py +135 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +215 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/codex_provider.py +112 -0
- package/runtime/providers/gemini_provider.py +128 -0
- package/runtime/providers/kimi_provider.py +151 -0
- package/runtime/providers/opencode_provider.py +144 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +1167 -0
- package/runtime/tmux_session_manager.py +169 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +193 -0
- package/scripts/check-omg-standalone-clean.py +103 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +492 -0
- package/scripts/settings-merge.py +283 -0
- package/scripts/verify-standalone.sh +8 -4
- package/settings.json +126 -29
- package/templates/profile.yaml +1 -1
- package/tools/__init__.py +2 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +746 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +656 -0
- package/tools/python_sandbox.py +609 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +736 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +294 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +622 -0
- package/yaml.py +321 -0
- package/.claude-plugin/scripts/install.sh +0 -9
- package/bun.lock +0 -23
- package/bunfig.toml +0 -3
- package/hooks/_budget.ts +0 -1
- package/hooks/_common.ts +0 -63
- package/hooks/circuit-breaker.ts +0 -101
- package/hooks/config-guard.ts +0 -4
- package/hooks/firewall.ts +0 -20
- package/hooks/policy_engine.ts +0 -156
- package/hooks/post-tool-failure.ts +0 -22
- package/hooks/post-write.ts +0 -4
- package/hooks/pre-tool-inject.ts +0 -4
- package/hooks/prompt-enhancer.ts +0 -46
- package/hooks/quality-runner.ts +0 -24
- package/hooks/secret-guard.ts +0 -4
- package/hooks/session-end-capture.ts +0 -19
- package/hooks/session-start.ts +0 -19
- package/hooks/shadow_manager.ts +0 -81
- package/hooks/stop-gate.ts +0 -22
- package/hooks/stop_dispatcher.ts +0 -147
- package/hooks/test-generator-hook.ts +0 -4
- package/hooks/tool-ledger.ts +0 -27
- package/hooks/trust_review.ts +0 -175
- package/lab/pipeline.ts +0 -75
- package/lab/policies.ts +0 -68
- package/runtime/common.ts +0 -111
- package/runtime/compat.ts +0 -174
- package/runtime/dispatcher.ts +0 -25
- package/runtime/ecosystem.ts +0 -186
- package/runtime/provider_bootstrap.ts +0 -99
- package/runtime/provider_smoke.ts +0 -34
- package/runtime/release_readiness.ts +0 -186
- package/runtime/team_router.ts +0 -144
- package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
- package/scripts/check-omg-standalone-clean.ts +0 -12
- package/scripts/check-runtime-clean.ts +0 -94
- package/scripts/omg.ts +0 -352
- package/scripts/settings-merge.ts +0 -93
- package/tools/commit_splitter.ts +0 -23
- package/tools/git_inspector.ts +0 -18
- package/tools/session_snapshot.ts +0 -47
- package/trac3er-oh-my-god-2.0.0.tgz +0 -0
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""LSP operations — 11 high-level functions exposing LSP tools to agents.
|
|
2
|
+
|
|
3
|
+
Built on top of ``tools.lsp_client.LSPClient``. Each function:
|
|
4
|
+
- Returns a graceful default (empty list / None / False / dict) when disabled or on error.
|
|
5
|
+
- Never raises exceptions to callers.
|
|
6
|
+
- Checks the ``OMG_LSP_TOOLS_ENABLED`` feature flag via env var or settings.json.
|
|
7
|
+
|
|
8
|
+
Feature flag: ``OMG_LSP_TOOLS_ENABLED`` (default: False / opt-in only).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from tools.lsp_client import LSPClient
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Module-level singleton
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
_client: LSPClient | None = None
|
|
27
|
+
|
|
28
|
+
# LSP diagnostic severity codes → human-readable names
|
|
29
|
+
_SEVERITY_MAP: dict[int, str] = {
|
|
30
|
+
1: "error",
|
|
31
|
+
2: "warning",
|
|
32
|
+
3: "information",
|
|
33
|
+
4: "hint",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# LSP SymbolKind enum → human-readable names
|
|
37
|
+
_SYMBOL_KIND_MAP: dict[int, str] = {
|
|
38
|
+
1: "File", 2: "Module", 3: "Namespace", 4: "Package",
|
|
39
|
+
5: "Class", 6: "Method", 7: "Property", 8: "Field",
|
|
40
|
+
9: "Constructor", 10: "Enum", 11: "Interface", 12: "Function",
|
|
41
|
+
13: "Variable", 14: "Constant", 15: "String", 16: "Number",
|
|
42
|
+
17: "Boolean", 18: "Array", 19: "Object", 20: "Key",
|
|
43
|
+
21: "Null", 22: "EnumMember", 23: "Struct", 24: "Event",
|
|
44
|
+
25: "Operator", 26: "TypeParameter",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Internal helpers
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def _is_enabled() -> bool:
|
|
53
|
+
"""Check whether the LSP tools feature flag is on.
|
|
54
|
+
|
|
55
|
+
Resolution order mirrors ``hooks/_common.get_feature_flag``:
|
|
56
|
+
env var ``OMG_LSP_TOOLS_ENABLED`` → ``settings.json`` → default (False).
|
|
57
|
+
"""
|
|
58
|
+
env_val = os.environ.get("OMG_LSP_TOOLS_ENABLED", "").lower()
|
|
59
|
+
if env_val in ("0", "false", "no"):
|
|
60
|
+
return False
|
|
61
|
+
if env_val in ("1", "true", "yes"):
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
# Slow path: try get_feature_flag for settings.json support
|
|
65
|
+
try:
|
|
66
|
+
import sys as _sys
|
|
67
|
+
|
|
68
|
+
_hooks = os.path.join(
|
|
69
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
70
|
+
"hooks",
|
|
71
|
+
)
|
|
72
|
+
if _hooks not in _sys.path:
|
|
73
|
+
_sys.path.insert(0, _hooks)
|
|
74
|
+
from _common import get_feature_flag # type: ignore[import-untyped]
|
|
75
|
+
|
|
76
|
+
return get_feature_flag("LSP_TOOLS", default=False)
|
|
77
|
+
except Exception:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _file_uri(file_path: str) -> str:
|
|
82
|
+
"""Convert a filesystem path to a ``file://`` URI."""
|
|
83
|
+
return Path(file_path).resolve().as_uri()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _position_params(file_path: str, line: int, character: int) -> dict[str, Any]:
|
|
87
|
+
"""Build ``TextDocumentPositionParams``."""
|
|
88
|
+
return {
|
|
89
|
+
"textDocument": {"uri": _file_uri(file_path)},
|
|
90
|
+
"position": {"line": line, "character": character},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _normalize_locations(result: Any) -> list[dict]:
|
|
95
|
+
"""Normalize an LSP Location / Location[] / LocationLink[] result."""
|
|
96
|
+
if result is None:
|
|
97
|
+
return []
|
|
98
|
+
if isinstance(result, dict):
|
|
99
|
+
return [{"uri": result.get("uri", ""), "range": result.get("range", {})}]
|
|
100
|
+
if isinstance(result, list):
|
|
101
|
+
locations: list[dict] = []
|
|
102
|
+
for item in result:
|
|
103
|
+
if isinstance(item, dict):
|
|
104
|
+
uri = item.get("uri", item.get("targetUri", ""))
|
|
105
|
+
range_ = item.get("range", item.get("targetRange", {}))
|
|
106
|
+
locations.append({"uri": uri, "range": range_})
|
|
107
|
+
return locations
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Public API
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def get_client() -> LSPClient:
|
|
116
|
+
"""Return the module-level LSP client singleton (lazy-init).
|
|
117
|
+
|
|
118
|
+
Raises ``RuntimeError`` when ``OMG_LSP_TOOLS_ENABLED`` is False.
|
|
119
|
+
"""
|
|
120
|
+
global _client
|
|
121
|
+
if not _is_enabled():
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
"LSP tools are disabled — set OMG_LSP_TOOLS_ENABLED=1 to enable"
|
|
124
|
+
)
|
|
125
|
+
if _client is None:
|
|
126
|
+
_client = LSPClient()
|
|
127
|
+
return _client
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# -- 1. Diagnostics --------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def lsp_diagnostics(file_path: str) -> list[dict]:
|
|
133
|
+
"""Pull diagnostics for *file_path*.
|
|
134
|
+
|
|
135
|
+
Returns a list of ``{severity, message, range}`` dicts.
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
client = get_client()
|
|
139
|
+
result = client.send_request(
|
|
140
|
+
"textDocument/diagnostic",
|
|
141
|
+
{"textDocument": {"uri": _file_uri(file_path)}},
|
|
142
|
+
)
|
|
143
|
+
if result is None:
|
|
144
|
+
return []
|
|
145
|
+
items = result.get("items", [])
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
"severity": _SEVERITY_MAP.get(d.get("severity", 1), "unknown"),
|
|
149
|
+
"message": d.get("message", ""),
|
|
150
|
+
"range": d.get("range", {}),
|
|
151
|
+
}
|
|
152
|
+
for d in items
|
|
153
|
+
if isinstance(d, dict)
|
|
154
|
+
]
|
|
155
|
+
except Exception:
|
|
156
|
+
return []
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# -- 2. Go to definition ---------------------------------------------------
|
|
160
|
+
|
|
161
|
+
def lsp_definition(file_path: str, line: int, character: int) -> list[dict]:
|
|
162
|
+
"""Go-to-definition at the given position.
|
|
163
|
+
|
|
164
|
+
Returns a list of ``{uri, range}`` location dicts.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
client = get_client()
|
|
168
|
+
result = client.send_request(
|
|
169
|
+
"textDocument/definition", _position_params(file_path, line, character),
|
|
170
|
+
)
|
|
171
|
+
return _normalize_locations(result)
|
|
172
|
+
except Exception:
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# -- 3. Go to type definition ----------------------------------------------
|
|
177
|
+
|
|
178
|
+
def lsp_type_definition(file_path: str, line: int, character: int) -> list[dict]:
|
|
179
|
+
"""Go-to-type-definition at the given position.
|
|
180
|
+
|
|
181
|
+
Returns a list of ``{uri, range}`` location dicts.
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
client = get_client()
|
|
185
|
+
result = client.send_request(
|
|
186
|
+
"textDocument/typeDefinition",
|
|
187
|
+
_position_params(file_path, line, character),
|
|
188
|
+
)
|
|
189
|
+
return _normalize_locations(result)
|
|
190
|
+
except Exception:
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# -- 4. Go to implementation -----------------------------------------------
|
|
195
|
+
|
|
196
|
+
def lsp_implementation(file_path: str, line: int, character: int) -> list[dict]:
|
|
197
|
+
"""Find implementations at the given position.
|
|
198
|
+
|
|
199
|
+
Returns a list of ``{uri, range}`` location dicts.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
client = get_client()
|
|
203
|
+
result = client.send_request(
|
|
204
|
+
"textDocument/implementation",
|
|
205
|
+
_position_params(file_path, line, character),
|
|
206
|
+
)
|
|
207
|
+
return _normalize_locations(result)
|
|
208
|
+
except Exception:
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# -- 5. Find references ----------------------------------------------------
|
|
213
|
+
|
|
214
|
+
def lsp_references(
|
|
215
|
+
file_path: str,
|
|
216
|
+
line: int,
|
|
217
|
+
character: int,
|
|
218
|
+
include_declaration: bool = True,
|
|
219
|
+
) -> list[dict]:
|
|
220
|
+
"""Find all references to the symbol at the given position.
|
|
221
|
+
|
|
222
|
+
Returns a list of ``{uri, range}`` location dicts.
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
client = get_client()
|
|
226
|
+
params = _position_params(file_path, line, character)
|
|
227
|
+
params["context"] = {"includeDeclaration": include_declaration}
|
|
228
|
+
result = client.send_request("textDocument/references", params)
|
|
229
|
+
return _normalize_locations(result)
|
|
230
|
+
except Exception:
|
|
231
|
+
return []
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# -- 6. Hover --------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
def lsp_hover(file_path: str, line: int, character: int) -> str | None:
|
|
237
|
+
"""Hover information at the given position.
|
|
238
|
+
|
|
239
|
+
Returns the hover text as a string, or ``None``.
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
client = get_client()
|
|
243
|
+
result = client.send_request(
|
|
244
|
+
"textDocument/hover", _position_params(file_path, line, character),
|
|
245
|
+
)
|
|
246
|
+
if result is None:
|
|
247
|
+
return None
|
|
248
|
+
contents = result.get("contents", "")
|
|
249
|
+
if isinstance(contents, str):
|
|
250
|
+
return contents
|
|
251
|
+
if isinstance(contents, dict):
|
|
252
|
+
return contents.get("value", str(contents))
|
|
253
|
+
if isinstance(contents, list):
|
|
254
|
+
parts: list[str] = []
|
|
255
|
+
for part in contents:
|
|
256
|
+
if isinstance(part, str):
|
|
257
|
+
parts.append(part)
|
|
258
|
+
elif isinstance(part, dict):
|
|
259
|
+
parts.append(part.get("value", str(part)))
|
|
260
|
+
return "\n".join(parts)
|
|
261
|
+
return str(contents)
|
|
262
|
+
except Exception:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# -- 7. Document symbols ----------------------------------------------------
|
|
267
|
+
|
|
268
|
+
def lsp_symbols(file_path: str) -> list[dict]:
|
|
269
|
+
"""Document symbols for *file_path*.
|
|
270
|
+
|
|
271
|
+
Returns a list of ``{name, kind, range}`` dicts.
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
client = get_client()
|
|
275
|
+
result = client.send_request(
|
|
276
|
+
"textDocument/documentSymbol",
|
|
277
|
+
{"textDocument": {"uri": _file_uri(file_path)}},
|
|
278
|
+
)
|
|
279
|
+
if result is None:
|
|
280
|
+
return []
|
|
281
|
+
if not isinstance(result, list):
|
|
282
|
+
return []
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
"name": sym.get("name", ""),
|
|
286
|
+
"kind": _SYMBOL_KIND_MAP.get(sym.get("kind", 0), "Unknown"),
|
|
287
|
+
"range": sym.get("range", sym.get("location", {}).get("range", {})),
|
|
288
|
+
}
|
|
289
|
+
for sym in result
|
|
290
|
+
if isinstance(sym, dict)
|
|
291
|
+
]
|
|
292
|
+
except Exception:
|
|
293
|
+
return []
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# -- 8. Rename --------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
def lsp_rename(
|
|
299
|
+
file_path: str, line: int, character: int, new_name: str,
|
|
300
|
+
) -> dict:
|
|
301
|
+
"""Rename the symbol at the given position.
|
|
302
|
+
|
|
303
|
+
Returns a workspace-edit dict (``{changes, documentChanges}``).
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
client = get_client()
|
|
307
|
+
params = _position_params(file_path, line, character)
|
|
308
|
+
params["newName"] = new_name
|
|
309
|
+
result = client.send_request("textDocument/rename", params)
|
|
310
|
+
if result is None:
|
|
311
|
+
return {}
|
|
312
|
+
return result
|
|
313
|
+
except Exception:
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# -- 9. Code actions --------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
def lsp_code_actions(file_path: str, line: int, character: int) -> list[dict]:
|
|
320
|
+
"""Available code actions at the given position.
|
|
321
|
+
|
|
322
|
+
Returns a list of ``{title, kind}`` dicts.
|
|
323
|
+
"""
|
|
324
|
+
try:
|
|
325
|
+
client = get_client()
|
|
326
|
+
pos = {"line": line, "character": character}
|
|
327
|
+
result = client.send_request(
|
|
328
|
+
"textDocument/codeAction",
|
|
329
|
+
{
|
|
330
|
+
"textDocument": {"uri": _file_uri(file_path)},
|
|
331
|
+
"range": {"start": pos, "end": pos},
|
|
332
|
+
"context": {"diagnostics": []},
|
|
333
|
+
},
|
|
334
|
+
)
|
|
335
|
+
if result is None:
|
|
336
|
+
return []
|
|
337
|
+
if not isinstance(result, list):
|
|
338
|
+
return []
|
|
339
|
+
return [
|
|
340
|
+
{
|
|
341
|
+
"title": action.get("title", ""),
|
|
342
|
+
"kind": action.get("kind", ""),
|
|
343
|
+
}
|
|
344
|
+
for action in result
|
|
345
|
+
if isinstance(action, dict)
|
|
346
|
+
]
|
|
347
|
+
except Exception:
|
|
348
|
+
return []
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# -- 10. Status -------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
def lsp_status() -> dict:
|
|
354
|
+
"""Return the current LSP client status.
|
|
355
|
+
|
|
356
|
+
Returns ``{connected: bool, server_name: str | None, capabilities: dict}``.
|
|
357
|
+
"""
|
|
358
|
+
try:
|
|
359
|
+
if not _is_enabled():
|
|
360
|
+
return {"connected": False, "server_name": None, "capabilities": {}}
|
|
361
|
+
if _client is None:
|
|
362
|
+
return {"connected": False, "server_name": None, "capabilities": {}}
|
|
363
|
+
return {
|
|
364
|
+
"connected": _client.is_connected(),
|
|
365
|
+
"server_name": getattr(_client, "_server_name", None),
|
|
366
|
+
"capabilities": getattr(_client, "_capabilities", {}),
|
|
367
|
+
}
|
|
368
|
+
except Exception:
|
|
369
|
+
return {"connected": False, "server_name": None, "capabilities": {}}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# -- 11. Reload -------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
def lsp_reload() -> bool:
|
|
375
|
+
"""Restart the LSP client singleton.
|
|
376
|
+
|
|
377
|
+
Shuts down any existing client, creates a fresh one.
|
|
378
|
+
Returns ``True`` on success, ``False`` on failure or disabled.
|
|
379
|
+
"""
|
|
380
|
+
global _client
|
|
381
|
+
try:
|
|
382
|
+
if not _is_enabled():
|
|
383
|
+
return False
|
|
384
|
+
if _client is not None:
|
|
385
|
+
try:
|
|
386
|
+
_client.shutdown()
|
|
387
|
+
except Exception:
|
|
388
|
+
pass
|
|
389
|
+
_client = LSPClient()
|
|
390
|
+
return True
|
|
391
|
+
except Exception:
|
|
392
|
+
return False
|