pi-lens 2.2.9 → 3.0.0
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/CHANGELOG.md +198 -0
- package/README.md +709 -519
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/file-time.test.ts +276 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/format-service.test.ts +339 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/__tests__/formatters.test.ts +401 -0
- package/clients/amain-types.js +164 -0
- package/clients/amain-types.ts +165 -0
- package/clients/architect-client.js +56 -12
- package/clients/architect-client.ts +81 -16
- package/clients/ast-grep-client.js +2 -2
- package/clients/ast-grep-client.ts +14 -39
- package/clients/ast-grep-parser.ts +1 -1
- package/clients/ast-grep-rule-manager.js +8 -0
- package/clients/ast-grep-rule-manager.ts +10 -1
- package/clients/ast-grep-types.js +9 -0
- package/clients/ast-grep-types.ts +106 -0
- package/clients/auto-loop.js +10 -0
- package/clients/auto-loop.ts +14 -1
- package/clients/biome-client.js +81 -19
- package/clients/biome-client.ts +103 -22
- package/clients/bus/bus.js +191 -0
- package/clients/bus/bus.ts +251 -0
- package/clients/bus/events.js +214 -0
- package/clients/bus/events.ts +279 -0
- package/clients/bus/index.js +8 -0
- package/clients/bus/index.ts +9 -0
- package/clients/bus/integration.js +158 -0
- package/clients/bus/integration.ts +214 -0
- package/clients/complexity-client.js +13 -7
- package/clients/complexity-client.ts +13 -7
- package/clients/config-validator.js +465 -0
- package/clients/config-validator.ts +558 -0
- package/clients/dependency-checker.js +4 -10
- package/clients/dependency-checker.ts +4 -10
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
- package/clients/dispatch/bus-dispatcher.js +177 -0
- package/clients/dispatch/bus-dispatcher.ts +251 -0
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.edge.test.ts +100 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.format.test.ts +58 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.inline.test.ts +93 -0
- package/clients/dispatch/dispatcher.js +19 -53
- package/clients/dispatch/dispatcher.ts +20 -67
- package/clients/dispatch/plan.js +9 -4
- package/clients/dispatch/plan.ts +9 -4
- package/clients/dispatch/runners/architect.js +21 -7
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/architect.test.ts +162 -0
- package/clients/dispatch/runners/architect.ts +22 -7
- package/clients/dispatch/runners/ast-grep-napi.js +462 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
- package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
- package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
- package/clients/dispatch/runners/ast-grep.js +62 -19
- package/clients/dispatch/runners/ast-grep.ts +70 -18
- package/clients/dispatch/runners/biome.js +29 -53
- package/clients/dispatch/runners/biome.ts +29 -63
- package/clients/dispatch/runners/config-validation.js +67 -0
- package/clients/dispatch/runners/config-validation.ts +82 -0
- package/clients/dispatch/runners/go-vet.js +4 -28
- package/clients/dispatch/runners/go-vet.ts +4 -32
- package/clients/dispatch/runners/index.js +30 -10
- package/clients/dispatch/runners/index.ts +30 -10
- package/clients/dispatch/runners/oxlint.js +141 -0
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/oxlint.test.ts +303 -0
- package/clients/dispatch/runners/oxlint.ts +175 -0
- package/clients/dispatch/runners/pyright.js +40 -70
- package/clients/dispatch/runners/pyright.test.js +16 -2
- package/clients/dispatch/runners/pyright.test.ts +14 -2
- package/clients/dispatch/runners/pyright.ts +48 -91
- package/clients/dispatch/runners/python-slop.js +97 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/python-slop.test.ts +298 -0
- package/clients/dispatch/runners/python-slop.ts +124 -0
- package/clients/dispatch/runners/ruff.js +18 -71
- package/clients/dispatch/runners/ruff.ts +19 -79
- package/clients/dispatch/runners/rust-clippy.js +28 -32
- package/clients/dispatch/runners/rust-clippy.ts +29 -31
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
- package/clients/dispatch/runners/shellcheck.js +147 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/shellcheck.test.ts +129 -0
- package/clients/dispatch/runners/shellcheck.ts +188 -0
- package/clients/dispatch/runners/similarity.js +230 -0
- package/clients/dispatch/runners/similarity.ts +339 -0
- package/clients/dispatch/runners/spellcheck.js +106 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/runners/spellcheck.test.ts +214 -0
- package/clients/dispatch/runners/spellcheck.ts +136 -0
- package/clients/dispatch/runners/tree-sitter.js +107 -0
- package/clients/dispatch/runners/tree-sitter.ts +135 -0
- package/clients/dispatch/runners/ts-lsp.js +104 -33
- package/clients/dispatch/runners/ts-lsp.ts +120 -38
- package/clients/dispatch/runners/ts-slop.js +113 -0
- package/clients/dispatch/runners/ts-slop.test.js +180 -0
- package/clients/dispatch/runners/ts-slop.test.ts +230 -0
- package/clients/dispatch/runners/ts-slop.ts +142 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
- package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
- package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
- package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
- package/clients/dispatch/runners/utils.js +2 -4
- package/clients/dispatch/runners/utils.ts +2 -4
- package/clients/dispatch/types.ts +1 -1
- package/clients/dispatch/utils/format-utils.js +49 -0
- package/clients/dispatch/utils/format-utils.ts +60 -0
- package/clients/dogfood.test.js +201 -0
- package/clients/dogfood.test.ts +269 -0
- package/clients/file-time.js +152 -0
- package/clients/file-time.ts +208 -0
- package/clients/file-utils.js +40 -0
- package/clients/file-utils.ts +44 -0
- package/clients/fix-scanners.js +10 -20
- package/clients/fix-scanners.ts +10 -22
- package/clients/format-service.js +172 -0
- package/clients/format-service.ts +254 -0
- package/clients/formatters.js +435 -0
- package/clients/formatters.ts +508 -0
- package/clients/go-client.js +5 -14
- package/clients/go-client.ts +5 -13
- package/clients/installer/index.js +356 -0
- package/clients/installer/index.ts +426 -0
- package/clients/jscpd-client.js +11 -9
- package/clients/jscpd-client.ts +12 -8
- package/clients/knip-client.js +3 -7
- package/clients/knip-client.ts +3 -6
- package/clients/lsp/__tests__/client.test.js +325 -0
- package/clients/lsp/__tests__/client.test.ts +434 -0
- package/clients/lsp/__tests__/config.test.js +166 -0
- package/clients/lsp/__tests__/config.test.ts +209 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/integration.test.ts +160 -0
- package/clients/lsp/__tests__/launch.test.js +260 -0
- package/clients/lsp/__tests__/launch.test.ts +329 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/server.test.ts +332 -0
- package/clients/lsp/__tests__/service.test.js +417 -0
- package/clients/lsp/__tests__/service.test.ts +499 -0
- package/clients/lsp/client.js +235 -0
- package/clients/lsp/client.ts +328 -0
- package/clients/lsp/config.js +115 -0
- package/clients/lsp/config.ts +149 -0
- package/clients/lsp/index.js +222 -0
- package/clients/lsp/index.ts +280 -0
- package/clients/lsp/installer/index.js +391 -0
- package/clients/lsp/interactive-install.js +210 -0
- package/clients/lsp/interactive-install.ts +251 -0
- package/clients/lsp/language.js +170 -0
- package/clients/lsp/language.ts +216 -0
- package/clients/lsp/launch.js +174 -0
- package/clients/lsp/launch.ts +240 -0
- package/clients/lsp/lsp/launch.js +116 -0
- package/clients/lsp/lsp/server.js +532 -0
- package/clients/lsp/lsp-index.js +10 -0
- package/clients/lsp/lsp-index.ts +11 -0
- package/clients/lsp/path-utils.js +48 -0
- package/clients/lsp/path-utils.ts +52 -0
- package/clients/lsp/server.js +615 -0
- package/clients/lsp/server.ts +800 -0
- package/clients/lsp/test-py-spawn/requirements.txt +1 -0
- package/clients/lsp/test-py-spawn/test.py +3 -0
- package/clients/lsp/test-py-svc/requirements.txt +1 -0
- package/clients/lsp/test-py-svc/test.py +3 -0
- package/clients/lsp/test-python-project/requirements.txt +1 -0
- package/clients/lsp/test-python-project/test.py +5 -0
- package/clients/metrics-history.js +2 -2
- package/clients/metrics-history.ts +2 -2
- package/clients/production-readiness.js +522 -0
- package/clients/production-readiness.ts +556 -0
- package/clients/project-index.js +255 -0
- package/clients/project-index.ts +383 -0
- package/clients/project-metadata.js +531 -0
- package/clients/project-metadata.ts +624 -0
- package/clients/ruff-client.js +56 -16
- package/clients/ruff-client.ts +72 -15
- package/clients/runner-tracker.js +152 -0
- package/clients/runner-tracker.ts +213 -0
- package/clients/rust-client.js +4 -11
- package/clients/rust-client.ts +5 -11
- package/clients/safe-spawn.js +96 -0
- package/clients/safe-spawn.ts +128 -0
- package/clients/scan-architectural-debt.js +3 -6
- package/clients/scan-architectural-debt.ts +3 -6
- package/clients/scan-utils.js +5 -20
- package/clients/scan-utils.ts +5 -29
- package/clients/secrets-scanner.js +3 -17
- package/clients/secrets-scanner.ts +4 -20
- package/clients/services/__tests__/effect-integration.test.js +86 -0
- package/clients/services/__tests__/effect-integration.test.ts +111 -0
- package/clients/services/effect-integration.js +194 -0
- package/clients/services/effect-integration.ts +268 -0
- package/clients/services/index.js +7 -0
- package/clients/services/index.ts +8 -0
- package/clients/services/runner-service.js +105 -0
- package/clients/services/runner-service.ts +179 -0
- package/clients/sg-runner.js +87 -13
- package/clients/sg-runner.ts +97 -13
- package/clients/state-matrix.js +160 -0
- package/clients/state-matrix.ts +202 -0
- package/clients/subprocess-client.js +10 -9
- package/clients/subprocess-client.ts +10 -8
- package/clients/test-runner-client.js +3 -7
- package/clients/test-runner-client.ts +3 -6
- package/clients/tool-availability.js +4 -10
- package/clients/tool-availability.ts +4 -9
- package/clients/tree-sitter-client.js +564 -0
- package/clients/tree-sitter-client.ts +797 -0
- package/clients/tree-sitter-query-loader.js +355 -0
- package/clients/tree-sitter-query-loader.ts +425 -0
- package/clients/type-coverage-client.js +3 -7
- package/clients/type-coverage-client.ts +3 -6
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.codefix.test.ts +186 -0
- package/clients/typescript-client.js +43 -0
- package/clients/typescript-client.ts +98 -0
- package/commands/booboo.js +799 -219
- package/commands/booboo.ts +1004 -225
- package/commands/clients/ast-grep-client.js +250 -0
- package/commands/clients/ast-grep-parser.js +86 -0
- package/commands/clients/ast-grep-rule-manager.js +91 -0
- package/commands/clients/ast-grep-types.js +9 -0
- package/commands/clients/biome-client.js +380 -0
- package/commands/clients/complexity-client.js +667 -0
- package/commands/clients/file-kinds.js +177 -0
- package/commands/clients/file-utils.js +40 -0
- package/commands/clients/jscpd-client.js +169 -0
- package/commands/clients/knip-client.js +211 -0
- package/commands/clients/ruff-client.js +297 -0
- package/commands/clients/safe-spawn.js +88 -0
- package/commands/clients/scan-utils.js +83 -0
- package/commands/clients/sg-runner.js +190 -0
- package/commands/clients/types.js +11 -0
- package/commands/clients/typescript-client.js +505 -0
- package/commands/fix-from-booboo.js +398 -0
- package/commands/fix-from-booboo.ts +485 -0
- package/commands/fix-simplified.js +618 -0
- package/commands/fix-simplified.ts +768 -0
- package/commands/rate.js +10 -14
- package/commands/rate.ts +9 -16
- package/default-architect.yaml +59 -15
- package/index.ts +342 -429
- package/package.json +16 -3
- package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
- package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
- package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
- package/rules/python-slop-rules/.sgconfig.yml +4 -0
- package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
- package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
- package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
- package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
- package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
- package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
- package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
- package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
- package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
- package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
- package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
- package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
- package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
- package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
- package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
- package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
- package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
- package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
- package/rules/ts-slop-rules/.sgconfig.yml +4 -0
- package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
- package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
- package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
- package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
- package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
- package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
- package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
- package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
- package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
- package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
- package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
- package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
- package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
- package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
- package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
- package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
- package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
- package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
- package/skills/ast-grep/SKILL.md +182 -0
- package/clients/dispatch/runners/secrets.js +0 -109
- package/commands/fix.js +0 -244
- package/commands/fix.ts +0 -373
- package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Configuration for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Allows users to define custom LSP servers via configuration.
|
|
5
|
+
*
|
|
6
|
+
* Config file: .pi-lens/lsp.json
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* {
|
|
10
|
+
* "servers": {
|
|
11
|
+
* "my-server": {
|
|
12
|
+
* "name": "My Custom LSP",
|
|
13
|
+
* "extensions": [".myext"],
|
|
14
|
+
* "command": "my-lsp-server",
|
|
15
|
+
* "args": ["--stdio"],
|
|
16
|
+
* "rootMarkers": ["package.json"]
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import fs from "fs/promises";
|
|
23
|
+
import path from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
import { LSP_SERVERS, type LSPServerInfo, createRootDetector } from "./server.js";
|
|
26
|
+
import { launchLSP } from "./launch.js";
|
|
27
|
+
|
|
28
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
|
|
30
|
+
// --- Types ---
|
|
31
|
+
|
|
32
|
+
export interface CustomServerConfig {
|
|
33
|
+
name: string;
|
|
34
|
+
extensions: string[];
|
|
35
|
+
command: string;
|
|
36
|
+
args?: string[];
|
|
37
|
+
rootMarkers?: string[];
|
|
38
|
+
env?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LSPConfig {
|
|
42
|
+
servers?: Record<string, CustomServerConfig>;
|
|
43
|
+
disabledServers?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Config Loading ---
|
|
47
|
+
|
|
48
|
+
const CONFIG_PATHS = [
|
|
49
|
+
".pi-lens/lsp.json",
|
|
50
|
+
".pi-lens.json",
|
|
51
|
+
"pi-lsp.json",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load LSP configuration from file
|
|
56
|
+
*/
|
|
57
|
+
export async function loadLSPConfig(cwd: string): Promise<LSPConfig> {
|
|
58
|
+
for (const configPath of CONFIG_PATHS) {
|
|
59
|
+
const fullPath = path.join(cwd, configPath);
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
62
|
+
const config = JSON.parse(content) as LSPConfig;
|
|
63
|
+
console.error(`[lsp-config] Loaded config from ${configPath}`);
|
|
64
|
+
return config;
|
|
65
|
+
} catch {
|
|
66
|
+
// File doesn't exist or is invalid, try next
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Custom Server Factory ---
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create LSPServerInfo from user configuration
|
|
76
|
+
*/
|
|
77
|
+
export function createCustomServer(config: CustomServerConfig, id: string): LSPServerInfo {
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
name: config.name,
|
|
81
|
+
extensions: config.extensions,
|
|
82
|
+
root: config.rootMarkers
|
|
83
|
+
? createRootDetector(config.rootMarkers)
|
|
84
|
+
: async () => process.cwd(),
|
|
85
|
+
async spawn(root) {
|
|
86
|
+
const proc = launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
87
|
+
cwd: root,
|
|
88
|
+
env: config.env ? { ...process.env, ...config.env } : process.env,
|
|
89
|
+
});
|
|
90
|
+
return { process: proc };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Registry Management ---
|
|
96
|
+
|
|
97
|
+
let customServers: LSPServerInfo[] = [];
|
|
98
|
+
let disabledServerIds: Set<string> = new Set();
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Initialize LSP configuration (call at session start)
|
|
102
|
+
*/
|
|
103
|
+
export async function initLSPConfig(cwd: string): Promise<void> {
|
|
104
|
+
const config = await loadLSPConfig(cwd);
|
|
105
|
+
|
|
106
|
+
// Clear previous custom servers
|
|
107
|
+
customServers = [];
|
|
108
|
+
disabledServerIds = new Set(config.disabledServers ?? []);
|
|
109
|
+
|
|
110
|
+
// Register custom servers from config
|
|
111
|
+
if (config.servers) {
|
|
112
|
+
for (const [id, serverConfig] of Object.entries(config.servers)) {
|
|
113
|
+
try {
|
|
114
|
+
const server = createCustomServer(serverConfig, id);
|
|
115
|
+
customServers.push(server);
|
|
116
|
+
console.error(`[lsp-config] Registered custom server: ${id} (${serverConfig.name})`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error(`[lsp-config] Failed to register server ${id}:`, err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get all available servers (built-in + custom, minus disabled)
|
|
126
|
+
*/
|
|
127
|
+
export function getAllServers(): LSPServerInfo[] {
|
|
128
|
+
const all = [...LSP_SERVERS, ...customServers];
|
|
129
|
+
return all.filter(s => !disabledServerIds.has(s.id));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if a server is disabled
|
|
134
|
+
*/
|
|
135
|
+
export function isServerDisabled(serverId: string): boolean {
|
|
136
|
+
return disabledServerIds.has(serverId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Override getServersForFile to include custom servers
|
|
140
|
+
|
|
141
|
+
import { getServersForFile as getBuiltinServersForFile } from "./server.js";
|
|
142
|
+
|
|
143
|
+
export function getServersForFileWithConfig(filePath: string): LSPServerInfo[] {
|
|
144
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
145
|
+
return getAllServers().filter((server) => server.extensions.includes(ext));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Re-export with config support
|
|
149
|
+
export { getAllServers as getServersForFile };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Service Layer for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple LSP clients per workspace with:
|
|
5
|
+
* - Auto-spawning based on file type
|
|
6
|
+
* - Effect-TS service composition
|
|
7
|
+
* - Bus event integration
|
|
8
|
+
* - Resource cleanup
|
|
9
|
+
*/
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
import { createLSPClient } from "./client.js";
|
|
12
|
+
import { getServersForFileWithConfig } from "./config.js";
|
|
13
|
+
import { getLanguageId } from "./language.js";
|
|
14
|
+
// --- Service ---
|
|
15
|
+
export class LSPService {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.state = {
|
|
18
|
+
clients: new Map(),
|
|
19
|
+
servers: new Map(),
|
|
20
|
+
broken: new Set(),
|
|
21
|
+
inFlight: new Map(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get or create LSP client for a file
|
|
26
|
+
* Prevents duplicate client creation via in-flight promise tracking
|
|
27
|
+
*/
|
|
28
|
+
async getClientForFile(filePath) {
|
|
29
|
+
const servers = getServersForFileWithConfig(filePath);
|
|
30
|
+
if (servers.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
// Try each matching server
|
|
33
|
+
for (const server of servers) {
|
|
34
|
+
const root = await server.root(filePath);
|
|
35
|
+
if (!root)
|
|
36
|
+
continue;
|
|
37
|
+
// Normalize root path for consistent cache key on Windows
|
|
38
|
+
const normalizedRoot = process.platform === "win32" ? root.toLowerCase() : root;
|
|
39
|
+
const key = `${server.id}:${normalizedRoot}`;
|
|
40
|
+
// Check cache first (fast path)
|
|
41
|
+
const existing = this.state.clients.get(key);
|
|
42
|
+
if (existing) {
|
|
43
|
+
return { client: existing, info: server };
|
|
44
|
+
}
|
|
45
|
+
// Check if broken
|
|
46
|
+
if (this.state.broken.has(key)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Check if there's already an in-flight spawn for this key
|
|
50
|
+
const inFlight = this.state.inFlight.get(key);
|
|
51
|
+
if (inFlight) {
|
|
52
|
+
// Wait for the existing spawn to complete
|
|
53
|
+
const result = await inFlight;
|
|
54
|
+
if (result)
|
|
55
|
+
return result;
|
|
56
|
+
continue; // This server failed, try next
|
|
57
|
+
}
|
|
58
|
+
// Create the spawn promise and store it
|
|
59
|
+
const spawnPromise = this.spawnClient(server, root, key);
|
|
60
|
+
this.state.inFlight.set(key, spawnPromise);
|
|
61
|
+
try {
|
|
62
|
+
const result = await spawnPromise;
|
|
63
|
+
if (result)
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
// Clean up in-flight tracking
|
|
68
|
+
this.state.inFlight.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Internal: spawn a client for a server/root combination
|
|
75
|
+
*/
|
|
76
|
+
async spawnClient(server, root, key) {
|
|
77
|
+
try {
|
|
78
|
+
const spawned = await server.spawn(root);
|
|
79
|
+
if (!spawned) {
|
|
80
|
+
this.state.broken.add(key);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const client = await createLSPClient({
|
|
84
|
+
serverId: server.id,
|
|
85
|
+
process: spawned.process,
|
|
86
|
+
root,
|
|
87
|
+
initialization: spawned.initialization,
|
|
88
|
+
});
|
|
89
|
+
this.state.clients.set(key, client);
|
|
90
|
+
return { client, info: server };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
if (errorMsg.includes("Timeout")) {
|
|
95
|
+
console.error(`[lsp] ${server.id} timed out during initialization (${errorMsg}). The server may be downloading or the project is large. Skipping.`);
|
|
96
|
+
}
|
|
97
|
+
else if (errorMsg.includes("stream was destroyed")) {
|
|
98
|
+
console.error(`[lsp] ${server.id} stream was destroyed. The server binary may be missing or crashed immediately. Try reinstalling: npm install -g ${server.id}-language-server`);
|
|
99
|
+
}
|
|
100
|
+
else if (errorMsg.includes("exited immediately")) {
|
|
101
|
+
console.error(`[lsp] ${server.id} ${errorMsg}. Try reinstalling: npm install -g ${server.id}-language-server`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.error(`[lsp] Failed to spawn ${server.id}:`, err);
|
|
105
|
+
}
|
|
106
|
+
this.state.broken.add(key);
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Open a file in LSP (sends textDocument/didOpen)
|
|
112
|
+
*/
|
|
113
|
+
async openFile(filePath, content) {
|
|
114
|
+
const spawned = await this.getClientForFile(filePath);
|
|
115
|
+
if (!spawned)
|
|
116
|
+
return;
|
|
117
|
+
const languageId = getLanguageId(filePath) ?? "plaintext";
|
|
118
|
+
await spawned.client.notify.open(filePath, content, languageId);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Update file content (sends textDocument/didChange)
|
|
122
|
+
*/
|
|
123
|
+
async updateFile(filePath, content) {
|
|
124
|
+
const spawned = await this.getClientForFile(filePath);
|
|
125
|
+
if (!spawned)
|
|
126
|
+
return;
|
|
127
|
+
await spawned.client.notify.change(filePath, content);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get diagnostics for a file
|
|
131
|
+
*/
|
|
132
|
+
async getDiagnostics(filePath) {
|
|
133
|
+
const spawned = await this.getClientForFile(filePath);
|
|
134
|
+
if (!spawned)
|
|
135
|
+
return [];
|
|
136
|
+
await spawned.client.waitForDiagnostics(filePath, 3000);
|
|
137
|
+
return spawned.client.getDiagnostics(filePath);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if LSP is available for a file
|
|
141
|
+
*/
|
|
142
|
+
async hasLSP(filePath) {
|
|
143
|
+
const servers = getServersForFileWithConfig(filePath);
|
|
144
|
+
if (servers.length === 0)
|
|
145
|
+
return false;
|
|
146
|
+
// Check if any server can provide a root
|
|
147
|
+
for (const server of servers) {
|
|
148
|
+
const root = await server.root(filePath);
|
|
149
|
+
if (root)
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Shutdown all LSP clients
|
|
156
|
+
*/
|
|
157
|
+
async shutdown() {
|
|
158
|
+
// Cancel any in-flight spawns
|
|
159
|
+
this.state.inFlight.clear();
|
|
160
|
+
for (const [key, client] of this.state.clients) {
|
|
161
|
+
try {
|
|
162
|
+
await client.shutdown();
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.error(`[lsp] Error shutting down ${key}:`, err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.state.clients.clear();
|
|
169
|
+
this.state.broken.clear();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get status of all active clients
|
|
173
|
+
*/
|
|
174
|
+
getStatus() {
|
|
175
|
+
return Array.from(this.state.clients.entries()).map(([key, _client]) => {
|
|
176
|
+
const [serverId, root] = key.split(":");
|
|
177
|
+
return { serverId, root, connected: true };
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// --- Effect Integration ---
|
|
182
|
+
/**
|
|
183
|
+
* Effect wrapper for LSP operations
|
|
184
|
+
*/
|
|
185
|
+
export function lspEffect(service) {
|
|
186
|
+
return {
|
|
187
|
+
openFile: (filePath, content) => Effect.tryPromise({
|
|
188
|
+
try: () => service.openFile(filePath, content),
|
|
189
|
+
catch: (err) => err,
|
|
190
|
+
}),
|
|
191
|
+
updateFile: (filePath, content) => Effect.tryPromise({
|
|
192
|
+
try: () => service.updateFile(filePath, content),
|
|
193
|
+
catch: (err) => err,
|
|
194
|
+
}),
|
|
195
|
+
getDiagnostics: (filePath) => Effect.tryPromise({
|
|
196
|
+
try: () => service.getDiagnostics(filePath),
|
|
197
|
+
catch: (err) => err,
|
|
198
|
+
}),
|
|
199
|
+
hasLSP: (filePath) => Effect.tryPromise({
|
|
200
|
+
try: () => service.hasLSP(filePath),
|
|
201
|
+
catch: (err) => err,
|
|
202
|
+
}),
|
|
203
|
+
shutdown: () => Effect.tryPromise({
|
|
204
|
+
try: () => service.shutdown(),
|
|
205
|
+
catch: (err) => err,
|
|
206
|
+
}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// --- Singleton Instance ---
|
|
210
|
+
let globalLSPService = null;
|
|
211
|
+
export function getLSPService() {
|
|
212
|
+
if (!globalLSPService) {
|
|
213
|
+
globalLSPService = new LSPService();
|
|
214
|
+
}
|
|
215
|
+
return globalLSPService;
|
|
216
|
+
}
|
|
217
|
+
export function resetLSPService() {
|
|
218
|
+
if (globalLSPService) {
|
|
219
|
+
globalLSPService.shutdown().catch(() => { });
|
|
220
|
+
}
|
|
221
|
+
globalLSPService = null;
|
|
222
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Service Layer for pi-lens
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple LSP clients per workspace with:
|
|
5
|
+
* - Auto-spawning based on file type
|
|
6
|
+
* - Effect-TS service composition
|
|
7
|
+
* - Bus event integration
|
|
8
|
+
* - Resource cleanup
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Effect } from "effect";
|
|
12
|
+
import type { LSPClientInfo } from "./client.js";
|
|
13
|
+
import { createLSPClient } from "./client.js";
|
|
14
|
+
import { getServersForFileWithConfig } from "./config.js";
|
|
15
|
+
import { getLanguageId } from "./language.js";
|
|
16
|
+
import type { LSPServerInfo } from "./server.js";
|
|
17
|
+
|
|
18
|
+
// --- Types ---
|
|
19
|
+
|
|
20
|
+
export interface LSPState {
|
|
21
|
+
clients: Map<string, LSPClientInfo>; // key: "serverId:root"
|
|
22
|
+
servers: Map<string, LSPServerInfo>;
|
|
23
|
+
broken: Set<string>; // servers that failed to initialize
|
|
24
|
+
inFlight: Map<string, Promise<SpawnedServer | undefined>>; // prevent duplicate spawns
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SpawnedServer {
|
|
28
|
+
client: LSPClientInfo;
|
|
29
|
+
info: LSPServerInfo;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Service ---
|
|
33
|
+
|
|
34
|
+
export class LSPService {
|
|
35
|
+
private state: LSPState;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.state = {
|
|
39
|
+
clients: new Map(),
|
|
40
|
+
servers: new Map(),
|
|
41
|
+
broken: new Set(),
|
|
42
|
+
inFlight: new Map(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get or create LSP client for a file
|
|
48
|
+
* Prevents duplicate client creation via in-flight promise tracking
|
|
49
|
+
*/
|
|
50
|
+
async getClientForFile(filePath: string): Promise<SpawnedServer | undefined> {
|
|
51
|
+
const servers = getServersForFileWithConfig(filePath);
|
|
52
|
+
if (servers.length === 0) return undefined;
|
|
53
|
+
|
|
54
|
+
// Try each matching server
|
|
55
|
+
for (const server of servers) {
|
|
56
|
+
const root = await server.root(filePath);
|
|
57
|
+
if (!root) continue;
|
|
58
|
+
|
|
59
|
+
// Normalize root path for consistent cache key on Windows
|
|
60
|
+
const normalizedRoot =
|
|
61
|
+
process.platform === "win32" ? root.toLowerCase() : root;
|
|
62
|
+
const key = `${server.id}:${normalizedRoot}`;
|
|
63
|
+
|
|
64
|
+
// Check cache first (fast path)
|
|
65
|
+
const existing = this.state.clients.get(key);
|
|
66
|
+
if (existing) {
|
|
67
|
+
return { client: existing, info: server };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if broken
|
|
71
|
+
if (this.state.broken.has(key)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if there's already an in-flight spawn for this key
|
|
76
|
+
const inFlight = this.state.inFlight.get(key);
|
|
77
|
+
if (inFlight) {
|
|
78
|
+
// Wait for the existing spawn to complete
|
|
79
|
+
const result = await inFlight;
|
|
80
|
+
if (result) return result;
|
|
81
|
+
continue; // This server failed, try next
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create the spawn promise and store it
|
|
85
|
+
const spawnPromise = this.spawnClient(server, root, key);
|
|
86
|
+
this.state.inFlight.set(key, spawnPromise);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = await spawnPromise;
|
|
90
|
+
if (result) return result;
|
|
91
|
+
} finally {
|
|
92
|
+
// Clean up in-flight tracking
|
|
93
|
+
this.state.inFlight.delete(key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Internal: spawn a client for a server/root combination
|
|
102
|
+
*/
|
|
103
|
+
private async spawnClient(
|
|
104
|
+
server: LSPServerInfo,
|
|
105
|
+
root: string,
|
|
106
|
+
key: string,
|
|
107
|
+
): Promise<SpawnedServer | undefined> {
|
|
108
|
+
try {
|
|
109
|
+
const spawned = await server.spawn(root);
|
|
110
|
+
if (!spawned) {
|
|
111
|
+
this.state.broken.add(key);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const client = await createLSPClient({
|
|
116
|
+
serverId: server.id,
|
|
117
|
+
process: spawned.process,
|
|
118
|
+
root,
|
|
119
|
+
initialization: spawned.initialization,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
this.state.clients.set(key, client);
|
|
123
|
+
return { client, info: server };
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
126
|
+
if (errorMsg.includes("Timeout")) {
|
|
127
|
+
console.error(
|
|
128
|
+
`[lsp] ${server.id} timed out during initialization (${errorMsg}). The server may be downloading or the project is large. Skipping.`,
|
|
129
|
+
);
|
|
130
|
+
} else if (errorMsg.includes("stream was destroyed")) {
|
|
131
|
+
console.error(
|
|
132
|
+
`[lsp] ${server.id} stream was destroyed. The server binary may be missing or crashed immediately. Try reinstalling: npm install -g ${server.id}-language-server`,
|
|
133
|
+
);
|
|
134
|
+
} else if (errorMsg.includes("exited immediately")) {
|
|
135
|
+
console.error(
|
|
136
|
+
`[lsp] ${server.id} ${errorMsg}. Try reinstalling: npm install -g ${server.id}-language-server`,
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
console.error(`[lsp] Failed to spawn ${server.id}:`, err);
|
|
140
|
+
}
|
|
141
|
+
this.state.broken.add(key);
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Open a file in LSP (sends textDocument/didOpen)
|
|
148
|
+
*/
|
|
149
|
+
async openFile(filePath: string, content: string): Promise<void> {
|
|
150
|
+
const spawned = await this.getClientForFile(filePath);
|
|
151
|
+
if (!spawned) return;
|
|
152
|
+
|
|
153
|
+
const languageId = getLanguageId(filePath) ?? "plaintext";
|
|
154
|
+
await spawned.client.notify.open(filePath, content, languageId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Update file content (sends textDocument/didChange)
|
|
159
|
+
*/
|
|
160
|
+
async updateFile(filePath: string, content: string): Promise<void> {
|
|
161
|
+
const spawned = await this.getClientForFile(filePath);
|
|
162
|
+
if (!spawned) return;
|
|
163
|
+
|
|
164
|
+
await spawned.client.notify.change(filePath, content);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get diagnostics for a file
|
|
169
|
+
*/
|
|
170
|
+
async getDiagnostics(
|
|
171
|
+
filePath: string,
|
|
172
|
+
): Promise<import("./client.js").LSPDiagnostic[]> {
|
|
173
|
+
const spawned = await this.getClientForFile(filePath);
|
|
174
|
+
if (!spawned) return [];
|
|
175
|
+
|
|
176
|
+
await spawned.client.waitForDiagnostics(filePath, 3000);
|
|
177
|
+
return spawned.client.getDiagnostics(filePath);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if LSP is available for a file
|
|
182
|
+
*/
|
|
183
|
+
async hasLSP(filePath: string): Promise<boolean> {
|
|
184
|
+
const servers = getServersForFileWithConfig(filePath);
|
|
185
|
+
if (servers.length === 0) return false;
|
|
186
|
+
|
|
187
|
+
// Check if any server can provide a root
|
|
188
|
+
for (const server of servers) {
|
|
189
|
+
const root = await server.root(filePath);
|
|
190
|
+
if (root) return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Shutdown all LSP clients
|
|
198
|
+
*/
|
|
199
|
+
async shutdown(): Promise<void> {
|
|
200
|
+
// Cancel any in-flight spawns
|
|
201
|
+
this.state.inFlight.clear();
|
|
202
|
+
|
|
203
|
+
for (const [key, client] of this.state.clients) {
|
|
204
|
+
try {
|
|
205
|
+
await client.shutdown();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error(`[lsp] Error shutting down ${key}:`, err);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
this.state.clients.clear();
|
|
211
|
+
this.state.broken.clear();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get status of all active clients
|
|
216
|
+
*/
|
|
217
|
+
getStatus(): Array<{ serverId: string; root: string; connected: boolean }> {
|
|
218
|
+
return Array.from(this.state.clients.entries()).map(([key, _client]) => {
|
|
219
|
+
const [serverId, root] = key.split(":");
|
|
220
|
+
return { serverId, root, connected: true };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- Effect Integration ---
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Effect wrapper for LSP operations
|
|
229
|
+
*/
|
|
230
|
+
export function lspEffect(service: LSPService) {
|
|
231
|
+
return {
|
|
232
|
+
openFile: (filePath: string, content: string) =>
|
|
233
|
+
Effect.tryPromise({
|
|
234
|
+
try: () => service.openFile(filePath, content),
|
|
235
|
+
catch: (err) => err as Error,
|
|
236
|
+
}),
|
|
237
|
+
|
|
238
|
+
updateFile: (filePath: string, content: string) =>
|
|
239
|
+
Effect.tryPromise({
|
|
240
|
+
try: () => service.updateFile(filePath, content),
|
|
241
|
+
catch: (err) => err as Error,
|
|
242
|
+
}),
|
|
243
|
+
|
|
244
|
+
getDiagnostics: (filePath: string) =>
|
|
245
|
+
Effect.tryPromise({
|
|
246
|
+
try: () => service.getDiagnostics(filePath),
|
|
247
|
+
catch: (err) => err as Error,
|
|
248
|
+
}),
|
|
249
|
+
|
|
250
|
+
hasLSP: (filePath: string) =>
|
|
251
|
+
Effect.tryPromise({
|
|
252
|
+
try: () => service.hasLSP(filePath),
|
|
253
|
+
catch: (err) => err as Error,
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
shutdown: () =>
|
|
257
|
+
Effect.tryPromise({
|
|
258
|
+
try: () => service.shutdown(),
|
|
259
|
+
catch: (err) => err as Error,
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// --- Singleton Instance ---
|
|
265
|
+
|
|
266
|
+
let globalLSPService: LSPService | null = null;
|
|
267
|
+
|
|
268
|
+
export function getLSPService(): LSPService {
|
|
269
|
+
if (!globalLSPService) {
|
|
270
|
+
globalLSPService = new LSPService();
|
|
271
|
+
}
|
|
272
|
+
return globalLSPService;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function resetLSPService(): void {
|
|
276
|
+
if (globalLSPService) {
|
|
277
|
+
globalLSPService.shutdown().catch(() => {});
|
|
278
|
+
}
|
|
279
|
+
globalLSPService = null;
|
|
280
|
+
}
|