@mrclrchtr/supi-code-intelligence 0.1.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/README.md +212 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
- package/package.json +47 -0
- package/src/actions/affected-action.ts +310 -0
- package/src/actions/brief-action.ts +242 -0
- package/src/actions/callees-action.ts +134 -0
- package/src/actions/callers-action.ts +215 -0
- package/src/actions/implementations-action.ts +190 -0
- package/src/actions/index-action.ts +187 -0
- package/src/actions/pattern-action.ts +232 -0
- package/src/architecture.ts +367 -0
- package/src/brief-focused.ts +383 -0
- package/src/brief.ts +228 -0
- package/src/code-intelligence.ts +122 -0
- package/src/git-context.ts +65 -0
- package/src/guidance.ts +39 -0
- package/src/index.ts +28 -0
- package/src/resolve-target.ts +104 -0
- package/src/search-helpers.ts +283 -0
- package/src/target-resolution.ts +368 -0
- package/src/tool-actions.ts +109 -0
- package/src/types.ts +57 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface GrepMatch {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const IGNORE_DIRS = new Set([
|
|
11
|
+
"node_modules",
|
|
12
|
+
".git",
|
|
13
|
+
"dist",
|
|
14
|
+
"build",
|
|
15
|
+
".next",
|
|
16
|
+
"coverage",
|
|
17
|
+
"tmp",
|
|
18
|
+
".pnpm",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
22
|
+
".ts",
|
|
23
|
+
".tsx",
|
|
24
|
+
".js",
|
|
25
|
+
".jsx",
|
|
26
|
+
".mjs",
|
|
27
|
+
".cjs",
|
|
28
|
+
".py",
|
|
29
|
+
".rs",
|
|
30
|
+
".go",
|
|
31
|
+
".java",
|
|
32
|
+
".kt",
|
|
33
|
+
".swift",
|
|
34
|
+
".rb",
|
|
35
|
+
".c",
|
|
36
|
+
".cpp",
|
|
37
|
+
".h",
|
|
38
|
+
".hpp",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/** Simple recursive text search in project source files. */
|
|
42
|
+
export function fallbackGrep(projectRoot: string, query: string): GrepMatch[] {
|
|
43
|
+
const results: GrepMatch[] = [];
|
|
44
|
+
walk(projectRoot, projectRoot, query, results);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function walk(dir: string, projectRoot: string, query: string, results: GrepMatch[]): void {
|
|
49
|
+
let entries: fs.Dirent[];
|
|
50
|
+
try {
|
|
51
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
if (!IGNORE_DIRS.has(entry.name)) {
|
|
59
|
+
walk(path.join(dir, entry.name), projectRoot, query, results);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!entry.isFile() || !SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const filePath = path.join(dir, entry.name);
|
|
69
|
+
searchFile(filePath, projectRoot, query, results);
|
|
70
|
+
if (results.length >= 20) return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function searchFile(
|
|
75
|
+
filePath: string,
|
|
76
|
+
projectRoot: string,
|
|
77
|
+
query: string,
|
|
78
|
+
results: GrepMatch[],
|
|
79
|
+
): void {
|
|
80
|
+
let content: string;
|
|
81
|
+
try {
|
|
82
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
83
|
+
} catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const lines = content.split("\n");
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
if (lines[i].includes(query)) {
|
|
90
|
+
results.push({
|
|
91
|
+
file: path.relative(projectRoot, filePath),
|
|
92
|
+
line: i + 1,
|
|
93
|
+
text: lines[i].trim(),
|
|
94
|
+
});
|
|
95
|
+
if (results.length >= 20) return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Shared session-scoped LSP service registry.
|
|
2
|
+
// Peer extensions can import `getSessionLspService` from the package root
|
|
3
|
+
// to reuse the active LSP runtime without starting duplicate servers.
|
|
4
|
+
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import type { LspManager } from "./manager/manager.ts";
|
|
7
|
+
import type {
|
|
8
|
+
Diagnostic,
|
|
9
|
+
DocumentSymbol,
|
|
10
|
+
Hover,
|
|
11
|
+
Location,
|
|
12
|
+
LocationLink,
|
|
13
|
+
Position,
|
|
14
|
+
ProjectServerInfo,
|
|
15
|
+
SymbolInformation,
|
|
16
|
+
WorkspaceSymbol,
|
|
17
|
+
} from "./types.ts";
|
|
18
|
+
|
|
19
|
+
export type SessionLspServiceState =
|
|
20
|
+
| { kind: "ready"; service: SessionLspService }
|
|
21
|
+
| { kind: "pending" }
|
|
22
|
+
| { kind: "disabled" }
|
|
23
|
+
| { kind: "unavailable"; reason: string };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Public wrapper around {@link LspManager} that exposes stable semantic operations.
|
|
27
|
+
* File path inputs may be absolute or session-cwd-relative; a leading `@` is stripped
|
|
28
|
+
* to match pi's built-in path-tool convention.
|
|
29
|
+
*/
|
|
30
|
+
export class SessionLspService {
|
|
31
|
+
constructor(private readonly manager: LspManager) {}
|
|
32
|
+
|
|
33
|
+
// ── Semantic lookups ────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
async hover(filePath: string, position: Position): Promise<Hover | null> {
|
|
36
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
37
|
+
const client = await this.manager.ensureFileOpen(resolvedPath);
|
|
38
|
+
if (!client) return null;
|
|
39
|
+
return client.hover(resolvedPath, position);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async definition(
|
|
43
|
+
filePath: string,
|
|
44
|
+
position: Position,
|
|
45
|
+
): Promise<Location | Location[] | LocationLink[] | null> {
|
|
46
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
47
|
+
const client = await this.manager.ensureFileOpen(resolvedPath);
|
|
48
|
+
if (!client) return null;
|
|
49
|
+
return client.definition(resolvedPath, position);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async references(filePath: string, position: Position): Promise<Location[] | null> {
|
|
53
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
54
|
+
const client = await this.manager.ensureFileOpen(resolvedPath);
|
|
55
|
+
if (!client) return null;
|
|
56
|
+
return client.references(resolvedPath, position);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async implementation(
|
|
60
|
+
filePath: string,
|
|
61
|
+
position: Position,
|
|
62
|
+
): Promise<Location | Location[] | LocationLink[] | null> {
|
|
63
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
64
|
+
const client = await this.manager.ensureFileOpen(resolvedPath);
|
|
65
|
+
if (!client) return null;
|
|
66
|
+
return client.implementation(resolvedPath, position);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async documentSymbols(filePath: string): Promise<DocumentSymbol[] | SymbolInformation[] | null> {
|
|
70
|
+
const resolvedPath = this.resolveFilePath(filePath);
|
|
71
|
+
const client = await this.manager.ensureFileOpen(resolvedPath);
|
|
72
|
+
if (!client) return null;
|
|
73
|
+
return client.documentSymbols(resolvedPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async workspaceSymbol(query: string): Promise<SymbolInformation[] | WorkspaceSymbol[] | null> {
|
|
77
|
+
return this.manager.workspaceSymbol(query);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Project / runtime awareness ─────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
getProjectServers(): ProjectServerInfo[] {
|
|
83
|
+
return this.manager.getKnownProjectServers([]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
isSupportedSourceFile(filePath: string): boolean {
|
|
87
|
+
return this.manager.isSupportedSourceFile(this.resolveFilePath(filePath));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Diagnostics ─────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
getOutstandingDiagnostics(
|
|
93
|
+
maxSeverity: number = 1,
|
|
94
|
+
): Array<{ file: string; diagnostics: Diagnostic[] }> {
|
|
95
|
+
return this.manager.getOutstandingDiagnostics(maxSeverity);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getOutstandingDiagnosticSummary(
|
|
99
|
+
maxSeverity: number = 1,
|
|
100
|
+
): import("./manager/manager-types.ts").OutstandingDiagnosticSummaryEntry[] {
|
|
101
|
+
return this.manager.getOutstandingDiagnosticSummary(maxSeverity);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Access the underlying manager for advanced use cases (discouraged). */
|
|
105
|
+
getManager(): LspManager {
|
|
106
|
+
return this.manager;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private resolveFilePath(filePath: string): string {
|
|
110
|
+
const normalizedPath = filePath.startsWith("@") ? filePath.slice(1) : filePath;
|
|
111
|
+
return path.resolve(this.manager.getCwd(), normalizedPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Registry ──────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
const REGISTRY_KEY = Symbol.for("@mrclrchtr/supi-lsp/session-registry");
|
|
118
|
+
|
|
119
|
+
function getRegistry(): Map<string, SessionLspServiceState> {
|
|
120
|
+
const globalScope = globalThis as typeof globalThis & Record<symbol, unknown>;
|
|
121
|
+
const existing = globalScope[REGISTRY_KEY];
|
|
122
|
+
if (existing instanceof Map) return existing as Map<string, SessionLspServiceState>;
|
|
123
|
+
|
|
124
|
+
const registry = new Map<string, SessionLspServiceState>();
|
|
125
|
+
globalScope[REGISTRY_KEY] = registry;
|
|
126
|
+
return registry;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function normalizeCwd(cwd: string): string {
|
|
130
|
+
return path.resolve(cwd);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const registry = getRegistry();
|
|
134
|
+
|
|
135
|
+
/** Publish the LSP service state for a session cwd. */
|
|
136
|
+
export function setSessionLspServiceState(cwd: string, state: SessionLspServiceState): void {
|
|
137
|
+
registry.set(normalizeCwd(cwd), state);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Acquire the LSP service state for a session cwd. */
|
|
141
|
+
export function getSessionLspService(cwd: string): SessionLspServiceState {
|
|
142
|
+
return (
|
|
143
|
+
registry.get(normalizeCwd(cwd)) ?? {
|
|
144
|
+
kind: "unavailable",
|
|
145
|
+
reason: "No LSP session initialized for this workspace",
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Remove the LSP service state for a session cwd. */
|
|
151
|
+
export function clearSessionLspService(cwd: string): void {
|
|
152
|
+
registry.delete(normalizeCwd(cwd));
|
|
153
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// LSP settings registration for the supi settings registry.
|
|
2
|
+
|
|
3
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
5
|
+
import { Container, Key, matchesKey, SettingsList, Text } from "@earendil-works/pi-tui";
|
|
6
|
+
import {
|
|
7
|
+
loadSupiConfig,
|
|
8
|
+
loadSupiConfigForScope,
|
|
9
|
+
registerConfigSettings,
|
|
10
|
+
} from "@mrclrchtr/supi-core";
|
|
11
|
+
import { loadConfig } from "./config.ts";
|
|
12
|
+
|
|
13
|
+
// ── Types ────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface LspSettings {
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
severity: number;
|
|
18
|
+
active: string[];
|
|
19
|
+
exclude: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LSP_DEFAULTS: LspSettings = {
|
|
23
|
+
enabled: true,
|
|
24
|
+
severity: 1,
|
|
25
|
+
active: [],
|
|
26
|
+
exclude: [],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── Config helpers ───────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export function loadLspSettings(cwd: string, homeDir?: string): LspSettings {
|
|
32
|
+
return loadSupiConfig("lsp", cwd, LSP_DEFAULTS, { homeDir });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return a user-facing message that indicates which config scope disabled LSP.
|
|
37
|
+
*/
|
|
38
|
+
export function getLspDisabledMessage(cwd: string, homeDir?: string): string {
|
|
39
|
+
const global = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "global", homeDir });
|
|
40
|
+
const project = loadSupiConfigForScope("lsp", cwd, LSP_DEFAULTS, { scope: "project", homeDir });
|
|
41
|
+
|
|
42
|
+
if (project.enabled === false) {
|
|
43
|
+
return "LSP is disabled in project settings (.pi/supi/config.json)";
|
|
44
|
+
}
|
|
45
|
+
if (global.enabled === false) {
|
|
46
|
+
return "LSP is disabled in global settings (~/.pi/agent/supi/config.json)";
|
|
47
|
+
}
|
|
48
|
+
return "LSP is disabled in settings";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function severityLabel(severity: number): string {
|
|
52
|
+
switch (severity) {
|
|
53
|
+
case 1:
|
|
54
|
+
return "errors";
|
|
55
|
+
case 2:
|
|
56
|
+
return "warnings";
|
|
57
|
+
case 3:
|
|
58
|
+
return "info";
|
|
59
|
+
case 4:
|
|
60
|
+
return "hints";
|
|
61
|
+
default:
|
|
62
|
+
return "errors";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Settings registration ────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
export function registerLspSettings(): void {
|
|
69
|
+
registerConfigSettings({
|
|
70
|
+
id: "lsp",
|
|
71
|
+
label: "LSP",
|
|
72
|
+
section: "lsp",
|
|
73
|
+
defaults: LSP_DEFAULTS,
|
|
74
|
+
buildItems: (settings, scope, cwd) => buildLspSettingItems(settings, scope, cwd),
|
|
75
|
+
// biome-ignore lint/complexity/useMaxParams: ConfigSettingsOptions interface callback
|
|
76
|
+
persistChange: (_scope, _cwd, settingId, value, helpers) => {
|
|
77
|
+
handlePersistChange(settingId, value, helpers);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handlePersistChange(
|
|
83
|
+
settingId: string,
|
|
84
|
+
value: string,
|
|
85
|
+
helpers: { set: (key: string, value: unknown) => void; unset: (key: string) => void },
|
|
86
|
+
): void {
|
|
87
|
+
switch (settingId) {
|
|
88
|
+
case "enabled":
|
|
89
|
+
helpers.set("enabled", value === "on");
|
|
90
|
+
break;
|
|
91
|
+
case "severity": {
|
|
92
|
+
const num = Number.parseInt(value.split(" ")[0] ?? "1", 10);
|
|
93
|
+
helpers.set("severity", Number.isNaN(num) ? 1 : num);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "active": {
|
|
97
|
+
const active = value
|
|
98
|
+
.split(",")
|
|
99
|
+
.map((s) => s.trim())
|
|
100
|
+
.filter((s) => s.length > 0);
|
|
101
|
+
if (active.length > 0) {
|
|
102
|
+
helpers.set("active", active);
|
|
103
|
+
} else {
|
|
104
|
+
helpers.unset("active");
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "exclude": {
|
|
109
|
+
const patterns = value
|
|
110
|
+
.split(",")
|
|
111
|
+
.map((s) => s.trim())
|
|
112
|
+
.filter((s) => s.length > 0);
|
|
113
|
+
if (patterns.length > 0) {
|
|
114
|
+
helpers.set("exclude", patterns);
|
|
115
|
+
} else {
|
|
116
|
+
helpers.unset("exclude");
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildLspSettingItems(
|
|
124
|
+
settings: LspSettings,
|
|
125
|
+
scope: "project" | "global",
|
|
126
|
+
cwd: string,
|
|
127
|
+
): SettingItem[] {
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
id: "enabled",
|
|
131
|
+
label: "Enable LSP",
|
|
132
|
+
description: "Enable or disable all LSP functionality",
|
|
133
|
+
currentValue: settings.enabled ? "on" : "off",
|
|
134
|
+
values: ["on", "off"],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "severity",
|
|
138
|
+
label: "Inline Severity",
|
|
139
|
+
description: "Minimum diagnostic severity to show inline (1=errors, 4=hints)",
|
|
140
|
+
currentValue: `${settings.severity} (${severityLabel(settings.severity)})`,
|
|
141
|
+
values: ["1 (errors)", "2 (warnings)", "3 (info)", "4 (hints)"],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "active",
|
|
145
|
+
label: "Active Servers",
|
|
146
|
+
description: "Press Enter to configure which language servers are active",
|
|
147
|
+
currentValue: settings.active.length > 0 ? settings.active.join(", ") : "all",
|
|
148
|
+
submenu: (_currentValue, done) => createServerSubmenu(scope, cwd, settings, done),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "exclude",
|
|
152
|
+
label: "Exclude Patterns",
|
|
153
|
+
description:
|
|
154
|
+
"Gitignore patterns to suppress LSP diagnostics (e.g. __tests__/, *.generated.ts)",
|
|
155
|
+
currentValue: settings.exclude.length > 0 ? settings.exclude.join(", ") : "none",
|
|
156
|
+
submenu: (_currentValue, done) => createExcludeSubmenu(scope, cwd, settings, done),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Server submenu ───────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
function createServerSubmenu(
|
|
164
|
+
_scope: "project" | "global",
|
|
165
|
+
cwd: string,
|
|
166
|
+
settings: LspSettings,
|
|
167
|
+
done: (selectedValue?: string) => void,
|
|
168
|
+
): {
|
|
169
|
+
render: (width: number) => string[];
|
|
170
|
+
invalidate: () => void;
|
|
171
|
+
handleInput: (data: string) => boolean;
|
|
172
|
+
} {
|
|
173
|
+
const config = loadConfig(cwd);
|
|
174
|
+
const allServers = Object.keys(config.servers);
|
|
175
|
+
const allEnabled = settings.active.length === 0;
|
|
176
|
+
const enabledServers = new Set(settings.active);
|
|
177
|
+
|
|
178
|
+
const items: SettingItem[] = allServers.map((name) => ({
|
|
179
|
+
id: name,
|
|
180
|
+
label: name,
|
|
181
|
+
currentValue: allEnabled || enabledServers.has(name) ? "enabled" : "disabled",
|
|
182
|
+
values: ["enabled", "disabled"],
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
let dirty = false;
|
|
186
|
+
|
|
187
|
+
const container = new Container();
|
|
188
|
+
const header = new Text("Active Servers — all enabled by default", 0, 0);
|
|
189
|
+
container.addChild(header);
|
|
190
|
+
|
|
191
|
+
const settingsList = new SettingsList(
|
|
192
|
+
items,
|
|
193
|
+
Math.min(items.length + 2, 15),
|
|
194
|
+
getSettingsListTheme(),
|
|
195
|
+
(id, newValue) => {
|
|
196
|
+
const idx = items.findIndex((i) => i.id === id);
|
|
197
|
+
if (idx >= 0 && items[idx].currentValue !== newValue) {
|
|
198
|
+
dirty = true;
|
|
199
|
+
items[idx].currentValue = newValue;
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
() => {
|
|
203
|
+
// Escape on inner SettingsList — no-op, handled by submenu wrapper
|
|
204
|
+
},
|
|
205
|
+
{ enableSearch: true },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
container.addChild(settingsList);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
render: (width: number) => container.render(width),
|
|
212
|
+
invalidate: () => container.invalidate(),
|
|
213
|
+
handleInput: (data: string) => {
|
|
214
|
+
if (matchesKey(data, Key.escape)) {
|
|
215
|
+
if (!dirty) {
|
|
216
|
+
done();
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
const enabled = items.filter((i) => i.currentValue === "enabled").map((i) => i.id);
|
|
220
|
+
done(enabled.join(", ") || undefined);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
settingsList.handleInput?.(data);
|
|
224
|
+
return true;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Exclude patterns submenu ─────────────────────────────────
|
|
230
|
+
|
|
231
|
+
function createExcludeSubmenu(
|
|
232
|
+
_scope: "project" | "global",
|
|
233
|
+
_cwd: string,
|
|
234
|
+
settings: LspSettings,
|
|
235
|
+
done: (selectedValue?: string) => void,
|
|
236
|
+
): {
|
|
237
|
+
render: (width: number) => string[];
|
|
238
|
+
invalidate: () => void;
|
|
239
|
+
handleInput: (data: string) => boolean;
|
|
240
|
+
} {
|
|
241
|
+
const items: SettingItem[] = settings.exclude.map((pattern) => ({
|
|
242
|
+
id: pattern,
|
|
243
|
+
label: pattern,
|
|
244
|
+
currentValue: "enabled",
|
|
245
|
+
values: ["enabled", "disabled"],
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
let dirty = false;
|
|
249
|
+
|
|
250
|
+
const container = new Container();
|
|
251
|
+
const header = new Text("Exclude Patterns — toggle off to remove", 0, 0);
|
|
252
|
+
container.addChild(header);
|
|
253
|
+
|
|
254
|
+
const footer = new Text("Add new patterns in .pi/supi/config.json under lsp.exclude", 0, 0);
|
|
255
|
+
container.addChild(footer);
|
|
256
|
+
|
|
257
|
+
const settingsList = new SettingsList(
|
|
258
|
+
items,
|
|
259
|
+
Math.min(items.length + 3, 15),
|
|
260
|
+
getSettingsListTheme(),
|
|
261
|
+
(id, newValue) => {
|
|
262
|
+
const idx = items.findIndex((i) => i.id === id);
|
|
263
|
+
if (idx >= 0 && items[idx].currentValue !== newValue) {
|
|
264
|
+
dirty = true;
|
|
265
|
+
items[idx].currentValue = newValue;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
() => {
|
|
269
|
+
// Escape on inner SettingsList — no-op, handled by submenu wrapper
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
container.addChild(settingsList);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
render: (width: number) => container.render(width),
|
|
277
|
+
invalidate: () => container.invalidate(),
|
|
278
|
+
handleInput: (data: string) => {
|
|
279
|
+
if (matchesKey(data, Key.escape)) {
|
|
280
|
+
if (!dirty) {
|
|
281
|
+
done();
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
const enabled = items.filter((i) => i.currentValue === "enabled").map((i) => i.id);
|
|
285
|
+
done(enabled.join(", ") || undefined);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
settingsList.handleInput?.(data);
|
|
289
|
+
return true;
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type {
|
|
3
|
+
ActiveCoverageSummaryEntry,
|
|
4
|
+
OutstandingDiagnosticSummaryEntry,
|
|
5
|
+
} from "./manager/manager-types.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Display form for a file path used both for human-readable LSP output and as
|
|
9
|
+
* the diagnostic key. In-tree paths return the project-relative form; out-of-
|
|
10
|
+
* tree paths preserve the absolute path so files in sibling worktrees or
|
|
11
|
+
* monorepo packages don't collapse to a basename — that collapse used to make
|
|
12
|
+
* unrelated files with the same name appear interchangeable in relevance
|
|
13
|
+
* matching, and it broke diagnostic correlation for tracked external paths.
|
|
14
|
+
*/
|
|
15
|
+
export function displayRelativeFilePath(filePath: string, cwd: string): string {
|
|
16
|
+
const absolutePath = path.resolve(cwd, filePath);
|
|
17
|
+
const relativePath = path.relative(cwd, absolutePath);
|
|
18
|
+
if (relativePath === "") return path.basename(absolutePath);
|
|
19
|
+
if (relativePath.startsWith(`..${path.sep}`) || relativePath === "..") {
|
|
20
|
+
return absolutePath;
|
|
21
|
+
}
|
|
22
|
+
return relativePath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatCoverageSummaryText(
|
|
26
|
+
entries: ActiveCoverageSummaryEntry[],
|
|
27
|
+
maxServers: number,
|
|
28
|
+
maxFiles: number,
|
|
29
|
+
): string | null {
|
|
30
|
+
if (entries.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
const visible = entries.slice(0, maxServers);
|
|
33
|
+
const parts = visible.map(
|
|
34
|
+
(entry) => `${entry.name} (${formatOpenFiles(entry.openFiles, maxFiles)})`,
|
|
35
|
+
);
|
|
36
|
+
const remaining = entries.length - visible.length;
|
|
37
|
+
const suffix =
|
|
38
|
+
remaining > 0 ? `; +${remaining} more ${remaining === 1 ? "server" : "servers"}` : "";
|
|
39
|
+
|
|
40
|
+
return `Active LSP coverage: ${parts.join("; ")}${suffix}.`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function formatOutstandingDiagnosticsSummaryText(
|
|
44
|
+
entries: OutstandingDiagnosticSummaryEntry[],
|
|
45
|
+
maxFiles: number,
|
|
46
|
+
): string | null {
|
|
47
|
+
if (entries.length === 0) return null;
|
|
48
|
+
|
|
49
|
+
const visible = entries.slice(0, maxFiles);
|
|
50
|
+
const parts = visible.map((entry) => `${entry.file} (${formatDiagnosticCounts(entry)})`);
|
|
51
|
+
const remaining = entries.length - visible.length;
|
|
52
|
+
const suffix = remaining > 0 ? `; +${remaining} more ${remaining === 1 ? "file" : "files"}` : "";
|
|
53
|
+
|
|
54
|
+
return `Outstanding LSP diagnostics: ${parts.join("; ")}${suffix}.`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeRelevantPaths(relevantPaths: string[]): string[] {
|
|
58
|
+
return Array.from(new Set(relevantPaths.map(normalizeRelevantPath).filter(Boolean)));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Match a file against caller-supplied relevance hints. Hints come from prompt
|
|
63
|
+
* tokens and recent tool paths, so they're heterogeneous: full relative paths,
|
|
64
|
+
* directory names, or bare filenames. Matching modes:
|
|
65
|
+
* - exact path match
|
|
66
|
+
* - candidate contains "/": treat as a directory prefix (`lsp/foo` ⊂ `lsp/foo/...`)
|
|
67
|
+
* - candidate has no "/" and no ".": treat as a directory name anywhere in the path
|
|
68
|
+
* - otherwise: treat as a filename and match the basename
|
|
69
|
+
*/
|
|
70
|
+
export function isPathRelevant(filePath: string, relevantPaths: string[], cwd: string): boolean {
|
|
71
|
+
const normalizedFilePath = normalizeRelevantPath(filePath);
|
|
72
|
+
if (shouldIgnoreLspPath(normalizedFilePath, cwd)) return false;
|
|
73
|
+
|
|
74
|
+
return relevantPaths.some((candidate) => {
|
|
75
|
+
if (normalizedFilePath === candidate) return true;
|
|
76
|
+
if (candidate.includes("/")) return normalizedFilePath.startsWith(`${candidate}/`);
|
|
77
|
+
if (!candidate.includes(".")) {
|
|
78
|
+
return (
|
|
79
|
+
normalizedFilePath.startsWith(`${candidate}/`) ||
|
|
80
|
+
normalizedFilePath.includes(`/${candidate}/`)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return path.basename(normalizedFilePath) === candidate;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
import { isFileExcludedByTsconfig } from "./tsconfig-scope.ts";
|
|
88
|
+
|
|
89
|
+
/** Check whether a file path is inside the project tree (within cwd, not node_modules/.pnpm/out-of-tree).
|
|
90
|
+
* Does NOT check tsconfig exclusion — use `shouldIgnoreLspPath` for diagnostics/guidance filtering. */
|
|
91
|
+
export function isInProjectTree(filePath: string, cwd: string): boolean {
|
|
92
|
+
const normalized = normalizeRelevantPath(filePath);
|
|
93
|
+
if (
|
|
94
|
+
normalized === "node_modules" ||
|
|
95
|
+
normalized.startsWith("node_modules/") ||
|
|
96
|
+
normalized.includes("/node_modules/") ||
|
|
97
|
+
normalized === ".pnpm" ||
|
|
98
|
+
normalized.startsWith(".pnpm/") ||
|
|
99
|
+
normalized.includes("/.pnpm/")
|
|
100
|
+
) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const absolutePath = path.resolve(cwd, filePath);
|
|
105
|
+
const relativePath = path.relative(cwd, absolutePath);
|
|
106
|
+
return !(relativePath.startsWith(`..${path.sep}`) || relativePath === "..");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Check whether a file path is inside the current project (not ignored and within cwd). */
|
|
110
|
+
export function isProjectSource(filePath: string, cwd: string): boolean {
|
|
111
|
+
return isInProjectTree(filePath, cwd);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function shouldIgnoreLspPath(filePath: string, cwd: string): boolean {
|
|
115
|
+
const normalized = normalizeRelevantPath(filePath);
|
|
116
|
+
if (
|
|
117
|
+
normalized === "node_modules" ||
|
|
118
|
+
normalized.startsWith("node_modules/") ||
|
|
119
|
+
normalized.includes("/node_modules/") ||
|
|
120
|
+
normalized === ".pnpm" ||
|
|
121
|
+
normalized.startsWith(".pnpm/") ||
|
|
122
|
+
normalized.includes("/.pnpm/") ||
|
|
123
|
+
isFileExcludedByTsconfig(normalized, cwd)
|
|
124
|
+
) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeRelevantPath(filePath: string): string {
|
|
132
|
+
return filePath.replaceAll("\\", "/").replace(/\/$/, "").trim();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatOpenFiles(openFiles: string[], maxFiles: number): string {
|
|
136
|
+
const visible = openFiles.slice(0, maxFiles);
|
|
137
|
+
const remaining = openFiles.length - visible.length;
|
|
138
|
+
const suffix = remaining > 0 ? `, +${remaining} more` : "";
|
|
139
|
+
return `${pluralize(openFiles.length, "open file")}: ${visible.join(", ")}${suffix}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatDiagnosticCounts(entry: OutstandingDiagnosticSummaryEntry): string {
|
|
143
|
+
const counts: string[] = [];
|
|
144
|
+
if (entry.errors > 0) counts.push(pluralize(entry.errors, "error"));
|
|
145
|
+
if (entry.warnings > 0) counts.push(pluralize(entry.warnings, "warning"));
|
|
146
|
+
if (entry.information > 0) counts.push(pluralize(entry.information, "info"));
|
|
147
|
+
if (entry.hints > 0) counts.push(pluralize(entry.hints, "hint"));
|
|
148
|
+
return counts.join(", ");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function pluralize(count: number, word: string): string {
|
|
152
|
+
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
153
|
+
}
|