gsd-pi 2.8.2 → 2.9.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 +2 -1
- package/dist/cli.js +5 -0
- package/dist/loader.js +1 -1
- package/dist/update-check.d.ts +24 -0
- package/dist/update-check.js +93 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.d.ts +4 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +80 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +5 -0
- package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/core/extensions/types.ts +4 -2
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +94 -2
- package/node_modules/@gsd/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/modes/rpc/rpc-types.ts +2 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +4 -2
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +80 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +5 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +4 -2
- package/packages/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/packages/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/packages/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/packages/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +94 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +2 -1
- package/src/resources/extensions/ask-user-questions.ts +42 -2
- package/src/resources/extensions/bg-shell/index.ts +34 -37
- package/src/resources/extensions/browser-tools/core.d.ts +205 -0
- package/src/resources/extensions/browser-tools/index.ts +2 -2
- package/src/resources/extensions/browser-tools/refs.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/session.ts +1 -1
- package/src/resources/extensions/context7/index.ts +2 -2
- package/src/resources/extensions/get-secrets-from-user.ts +3 -2
- package/src/resources/extensions/google-search/index.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +126 -12
- package/src/resources/extensions/gsd/commands.ts +218 -3
- package/src/resources/extensions/gsd/doctor.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +163 -13
- package/src/resources/extensions/gsd/guided-flow.ts +19 -9
- package/src/resources/extensions/gsd/index.ts +17 -7
- package/src/resources/extensions/gsd/preferences.ts +1 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +352 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/worktree.ts +20 -1
- package/src/resources/extensions/mac-tools/index.ts +1 -1
- package/src/resources/extensions/search-the-web/command-search-provider.ts +1 -1
- package/src/resources/extensions/search-the-web/format.ts +1 -1
- package/src/resources/extensions/search-the-web/index.ts +5 -5
- package/src/resources/extensions/search-the-web/native-search.ts +5 -6
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +7 -7
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +11 -11
- package/src/resources/extensions/search-the-web/tool-search.ts +10 -10
- package/src/resources/extensions/shared/interview-ui.ts +2 -2
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
import * as fsPromises from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { glob } from "glob";
|
|
4
|
+
import { isEnoent } from "./helpers.js";
|
|
5
|
+
import type {
|
|
6
|
+
CodeAction,
|
|
7
|
+
Command,
|
|
8
|
+
Diagnostic,
|
|
9
|
+
DiagnosticSeverity,
|
|
10
|
+
DocumentSymbol,
|
|
11
|
+
Location,
|
|
12
|
+
SymbolInformation,
|
|
13
|
+
SymbolKind,
|
|
14
|
+
TextEdit,
|
|
15
|
+
WorkspaceEdit,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Language Detection
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
const LANGUAGE_MAP: Record<string, string> = {
|
|
23
|
+
// TypeScript/JavaScript
|
|
24
|
+
".ts": "typescript",
|
|
25
|
+
".tsx": "typescriptreact",
|
|
26
|
+
".js": "javascript",
|
|
27
|
+
".jsx": "javascriptreact",
|
|
28
|
+
".mjs": "javascript",
|
|
29
|
+
".cjs": "javascript",
|
|
30
|
+
".mts": "typescript",
|
|
31
|
+
".cts": "typescript",
|
|
32
|
+
|
|
33
|
+
// Systems languages
|
|
34
|
+
".rs": "rust",
|
|
35
|
+
".go": "go",
|
|
36
|
+
".c": "c",
|
|
37
|
+
".h": "c",
|
|
38
|
+
".cpp": "cpp",
|
|
39
|
+
".cc": "cpp",
|
|
40
|
+
".cxx": "cpp",
|
|
41
|
+
".hpp": "cpp",
|
|
42
|
+
".hxx": "cpp",
|
|
43
|
+
".zig": "zig",
|
|
44
|
+
|
|
45
|
+
// Scripting languages
|
|
46
|
+
".py": "python",
|
|
47
|
+
".rb": "ruby",
|
|
48
|
+
".lua": "lua",
|
|
49
|
+
".sh": "shellscript",
|
|
50
|
+
".bash": "shellscript",
|
|
51
|
+
".zsh": "shellscript",
|
|
52
|
+
".fish": "fish",
|
|
53
|
+
".pl": "perl",
|
|
54
|
+
".php": "php",
|
|
55
|
+
|
|
56
|
+
// JVM languages
|
|
57
|
+
".java": "java",
|
|
58
|
+
".kt": "kotlin",
|
|
59
|
+
".kts": "kotlin",
|
|
60
|
+
".scala": "scala",
|
|
61
|
+
".groovy": "groovy",
|
|
62
|
+
".clj": "clojure",
|
|
63
|
+
|
|
64
|
+
// .NET languages
|
|
65
|
+
".cs": "csharp",
|
|
66
|
+
".fs": "fsharp",
|
|
67
|
+
".vb": "vb",
|
|
68
|
+
|
|
69
|
+
// Web
|
|
70
|
+
".html": "html",
|
|
71
|
+
".htm": "html",
|
|
72
|
+
".css": "css",
|
|
73
|
+
".scss": "scss",
|
|
74
|
+
".sass": "sass",
|
|
75
|
+
".less": "less",
|
|
76
|
+
".vue": "vue",
|
|
77
|
+
".svelte": "svelte",
|
|
78
|
+
|
|
79
|
+
// Data formats
|
|
80
|
+
".json": "json",
|
|
81
|
+
".jsonc": "jsonc",
|
|
82
|
+
".yaml": "yaml",
|
|
83
|
+
".yml": "yaml",
|
|
84
|
+
".toml": "toml",
|
|
85
|
+
".xml": "xml",
|
|
86
|
+
".ini": "ini",
|
|
87
|
+
|
|
88
|
+
// Documentation
|
|
89
|
+
".md": "markdown",
|
|
90
|
+
".markdown": "markdown",
|
|
91
|
+
".rst": "restructuredtext",
|
|
92
|
+
".adoc": "asciidoc",
|
|
93
|
+
".tex": "latex",
|
|
94
|
+
|
|
95
|
+
// Other
|
|
96
|
+
".sql": "sql",
|
|
97
|
+
".graphql": "graphql",
|
|
98
|
+
".gql": "graphql",
|
|
99
|
+
".proto": "protobuf",
|
|
100
|
+
".dockerfile": "dockerfile",
|
|
101
|
+
".tf": "terraform",
|
|
102
|
+
".hcl": "hcl",
|
|
103
|
+
".nix": "nix",
|
|
104
|
+
".ex": "elixir",
|
|
105
|
+
".exs": "elixir",
|
|
106
|
+
".erl": "erlang",
|
|
107
|
+
".hrl": "erlang",
|
|
108
|
+
".hs": "haskell",
|
|
109
|
+
".ml": "ocaml",
|
|
110
|
+
".mli": "ocaml",
|
|
111
|
+
".swift": "swift",
|
|
112
|
+
".r": "r",
|
|
113
|
+
".R": "r",
|
|
114
|
+
".jl": "julia",
|
|
115
|
+
".dart": "dart",
|
|
116
|
+
".elm": "elm",
|
|
117
|
+
".v": "v",
|
|
118
|
+
".nim": "nim",
|
|
119
|
+
".cr": "crystal",
|
|
120
|
+
".d": "d",
|
|
121
|
+
".pas": "pascal",
|
|
122
|
+
".pp": "pascal",
|
|
123
|
+
".lisp": "lisp",
|
|
124
|
+
".lsp": "lisp",
|
|
125
|
+
".rkt": "racket",
|
|
126
|
+
".scm": "scheme",
|
|
127
|
+
".ps1": "powershell",
|
|
128
|
+
".psm1": "powershell",
|
|
129
|
+
".bat": "bat",
|
|
130
|
+
".cmd": "bat",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Detect language ID from file path.
|
|
135
|
+
*/
|
|
136
|
+
export function detectLanguageId(filePath: string): string {
|
|
137
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
138
|
+
const basename = path.basename(filePath).toLowerCase();
|
|
139
|
+
|
|
140
|
+
if (basename === "dockerfile" || basename.startsWith("dockerfile.")) {
|
|
141
|
+
return "dockerfile";
|
|
142
|
+
}
|
|
143
|
+
if (basename === "makefile" || basename === "gnumakefile") {
|
|
144
|
+
return "makefile";
|
|
145
|
+
}
|
|
146
|
+
if (basename === "cmakelists.txt" || ext === ".cmake") {
|
|
147
|
+
return "cmake";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return LANGUAGE_MAP[ext] ?? "plaintext";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// URI Handling (Cross-Platform)
|
|
155
|
+
// =============================================================================
|
|
156
|
+
|
|
157
|
+
export function fileToUri(filePath: string): string {
|
|
158
|
+
const resolved = path.resolve(filePath);
|
|
159
|
+
|
|
160
|
+
if (process.platform === "win32") {
|
|
161
|
+
return `file:///${resolved.replace(/\\/g, "/")}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return `file://${resolved}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function uriToFile(uri: string): string {
|
|
168
|
+
if (!uri.startsWith("file://")) {
|
|
169
|
+
return uri;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let filePath = decodeURIComponent(uri.slice(7));
|
|
173
|
+
|
|
174
|
+
if (process.platform === "win32" && filePath.startsWith("/") && /^[A-Za-z]:/.test(filePath.slice(1))) {
|
|
175
|
+
filePath = filePath.slice(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return filePath;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// Diagnostic Formatting
|
|
183
|
+
// =============================================================================
|
|
184
|
+
|
|
185
|
+
const SEVERITY_NAMES: Record<DiagnosticSeverity, string> = {
|
|
186
|
+
1: "error",
|
|
187
|
+
2: "warning",
|
|
188
|
+
3: "info",
|
|
189
|
+
4: "hint",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export function severityToString(severity?: DiagnosticSeverity): string {
|
|
193
|
+
return SEVERITY_NAMES[severity ?? 1] ?? "unknown";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
|
|
197
|
+
return diagnostics.sort((a, b) => {
|
|
198
|
+
const aSeverity = a.severity ?? 1;
|
|
199
|
+
const bSeverity = b.severity ?? 1;
|
|
200
|
+
if (aSeverity !== bSeverity) return aSeverity - bSeverity;
|
|
201
|
+
const aLine = a.range.start.line;
|
|
202
|
+
const bLine = b.range.start.line;
|
|
203
|
+
if (aLine !== bLine) return aLine - bLine;
|
|
204
|
+
const aCol = a.range.start.character;
|
|
205
|
+
const bCol = b.range.start.character;
|
|
206
|
+
if (aCol !== bCol) return aCol - bCol;
|
|
207
|
+
return a.message.localeCompare(b.message);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function severityToIcon(severity?: DiagnosticSeverity): string {
|
|
212
|
+
switch (severity ?? 1) {
|
|
213
|
+
case 1:
|
|
214
|
+
return "[E]";
|
|
215
|
+
case 2:
|
|
216
|
+
return "[W]";
|
|
217
|
+
case 3:
|
|
218
|
+
return "[I]";
|
|
219
|
+
case 4:
|
|
220
|
+
return "[H]";
|
|
221
|
+
default:
|
|
222
|
+
return "[E]";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function stripDiagnosticNoise(message: string): string {
|
|
227
|
+
return message
|
|
228
|
+
.split("\n")
|
|
229
|
+
.filter(line => {
|
|
230
|
+
const trimmed = line.trim();
|
|
231
|
+
if (trimmed.startsWith("for further information visit")) return false;
|
|
232
|
+
if (/^https?:\/\//.test(trimmed)) return false;
|
|
233
|
+
return true;
|
|
234
|
+
})
|
|
235
|
+
.join("\n")
|
|
236
|
+
.trim();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function formatDiagnostic(diagnostic: Diagnostic, filePath: string): string {
|
|
240
|
+
const severity = severityToString(diagnostic.severity);
|
|
241
|
+
const line = diagnostic.range.start.line + 1;
|
|
242
|
+
const col = diagnostic.range.start.character + 1;
|
|
243
|
+
const source = diagnostic.source ? `[${diagnostic.source}] ` : "";
|
|
244
|
+
const code = diagnostic.code !== undefined ? ` (${diagnostic.code})` : "";
|
|
245
|
+
const message = stripDiagnosticNoise(diagnostic.message);
|
|
246
|
+
|
|
247
|
+
return `${filePath}:${line}:${col} [${severity}] ${source}${message}${code}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const DIAG_PATH_RE = /^(.+?):(\d+:\d+\s+.*)$/;
|
|
251
|
+
|
|
252
|
+
export function formatGroupedDiagnosticMessages(messages: string[]): string {
|
|
253
|
+
const diagnosticsByFile = new Map<string, string[]>();
|
|
254
|
+
const fileOrder: string[] = [];
|
|
255
|
+
const ungrouped: string[] = [];
|
|
256
|
+
|
|
257
|
+
for (const msg of messages) {
|
|
258
|
+
const match = DIAG_PATH_RE.exec(msg);
|
|
259
|
+
if (!match) {
|
|
260
|
+
ungrouped.push(msg);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const [, rawFilePath, rest] = match;
|
|
265
|
+
const filePath = rawFilePath.replace(/\\/g, "/");
|
|
266
|
+
if (!diagnosticsByFile.has(filePath)) {
|
|
267
|
+
diagnosticsByFile.set(filePath, []);
|
|
268
|
+
fileOrder.push(filePath);
|
|
269
|
+
}
|
|
270
|
+
diagnosticsByFile.get(filePath)?.push(rest);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (diagnosticsByFile.size === 0) {
|
|
274
|
+
return ungrouped.join("\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const filesByDirectory = new Map<string, string[]>();
|
|
278
|
+
for (const filePath of fileOrder) {
|
|
279
|
+
const directory = path.dirname(filePath).replace(/\\/g, "/");
|
|
280
|
+
if (!filesByDirectory.has(directory)) {
|
|
281
|
+
filesByDirectory.set(directory, []);
|
|
282
|
+
}
|
|
283
|
+
filesByDirectory.get(directory)?.push(filePath);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const lines: string[] = [];
|
|
287
|
+
for (const [directory, directoryFiles] of filesByDirectory) {
|
|
288
|
+
if (directory === ".") {
|
|
289
|
+
for (const filePath of directoryFiles) {
|
|
290
|
+
if (lines.length > 0) {
|
|
291
|
+
lines.push("");
|
|
292
|
+
}
|
|
293
|
+
lines.push(`# ${path.basename(filePath)}`);
|
|
294
|
+
for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) {
|
|
295
|
+
lines.push(` ${diagnostic}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (lines.length > 0) {
|
|
302
|
+
lines.push("");
|
|
303
|
+
}
|
|
304
|
+
lines.push(`# ${directory}`);
|
|
305
|
+
for (const filePath of directoryFiles) {
|
|
306
|
+
lines.push(`## └─ ${path.basename(filePath)}`);
|
|
307
|
+
for (const diagnostic of diagnosticsByFile.get(filePath) ?? []) {
|
|
308
|
+
lines.push(` ${diagnostic}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (ungrouped.length > 0) {
|
|
314
|
+
lines.push("");
|
|
315
|
+
for (const msg of ungrouped) {
|
|
316
|
+
lines.push(msg);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return lines.join("\n");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function formatDiagnosticsSummary(diagnostics: Diagnostic[]): string {
|
|
324
|
+
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
325
|
+
|
|
326
|
+
for (const d of diagnostics) {
|
|
327
|
+
const sev = severityToString(d.severity);
|
|
328
|
+
if (sev in counts) {
|
|
329
|
+
counts[sev as keyof typeof counts]++;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const parts: string[] = [];
|
|
334
|
+
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
|
|
335
|
+
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
|
|
336
|
+
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
|
|
337
|
+
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
|
|
338
|
+
|
|
339
|
+
return parts.length > 0 ? parts.join(", ") : "no issues";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// =============================================================================
|
|
343
|
+
// Location Formatting
|
|
344
|
+
// =============================================================================
|
|
345
|
+
|
|
346
|
+
export function formatLocation(location: Location, cwd: string): string {
|
|
347
|
+
const file = path.relative(cwd, uriToFile(location.uri));
|
|
348
|
+
const line = location.range.start.line + 1;
|
|
349
|
+
const col = location.range.start.character + 1;
|
|
350
|
+
return `${file}:${line}:${col}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function formatPosition(line: number, col: number): string {
|
|
354
|
+
return `${line}:${col}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// =============================================================================
|
|
358
|
+
// WorkspaceEdit Formatting
|
|
359
|
+
// =============================================================================
|
|
360
|
+
|
|
361
|
+
export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string[] {
|
|
362
|
+
const results: string[] = [];
|
|
363
|
+
|
|
364
|
+
if (edit.changes) {
|
|
365
|
+
for (const [uri, textEdits] of Object.entries(edit.changes)) {
|
|
366
|
+
const file = path.relative(cwd, uriToFile(uri));
|
|
367
|
+
results.push(`${file}: ${textEdits.length} edit${textEdits.length > 1 ? "s" : ""}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (edit.documentChanges) {
|
|
372
|
+
for (const change of edit.documentChanges) {
|
|
373
|
+
if ("edits" in change && change.textDocument) {
|
|
374
|
+
const file = path.relative(cwd, uriToFile(change.textDocument.uri));
|
|
375
|
+
results.push(`${file}: ${change.edits.length} edit${change.edits.length > 1 ? "s" : ""}`);
|
|
376
|
+
} else if ("kind" in change) {
|
|
377
|
+
switch (change.kind) {
|
|
378
|
+
case "create":
|
|
379
|
+
results.push(`CREATE: ${path.relative(cwd, uriToFile(change.uri))}`);
|
|
380
|
+
break;
|
|
381
|
+
case "rename":
|
|
382
|
+
results.push(
|
|
383
|
+
`RENAME: ${path.relative(cwd, uriToFile(change.oldUri))} -> ${path.relative(cwd, uriToFile(change.newUri))}`,
|
|
384
|
+
);
|
|
385
|
+
break;
|
|
386
|
+
case "delete":
|
|
387
|
+
results.push(`DELETE: ${path.relative(cwd, uriToFile(change.uri))}`);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return results;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function formatTextEdit(edit: TextEdit, maxLength = 50): string {
|
|
398
|
+
const range = `${edit.range.start.line + 1}:${edit.range.start.character + 1}`;
|
|
399
|
+
const preview =
|
|
400
|
+
edit.newText.length > maxLength
|
|
401
|
+
? `${edit.newText.slice(0, maxLength).replace(/\n/g, "\\n")}…`
|
|
402
|
+
: edit.newText.replace(/\n/g, "\\n");
|
|
403
|
+
return `line ${range} -> "${preview}"`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// =============================================================================
|
|
407
|
+
// Symbol Formatting
|
|
408
|
+
// =============================================================================
|
|
409
|
+
|
|
410
|
+
const SYMBOL_KIND_LABELS: Record<number, string> = {
|
|
411
|
+
1: "File",
|
|
412
|
+
2: "Module",
|
|
413
|
+
3: "Namespace",
|
|
414
|
+
4: "Package",
|
|
415
|
+
5: "Class",
|
|
416
|
+
6: "Method",
|
|
417
|
+
7: "Property",
|
|
418
|
+
8: "Field",
|
|
419
|
+
9: "Constructor",
|
|
420
|
+
10: "Enum",
|
|
421
|
+
11: "Interface",
|
|
422
|
+
12: "Function",
|
|
423
|
+
13: "Variable",
|
|
424
|
+
14: "Constant",
|
|
425
|
+
15: "String",
|
|
426
|
+
16: "Number",
|
|
427
|
+
17: "Boolean",
|
|
428
|
+
18: "Array",
|
|
429
|
+
19: "Object",
|
|
430
|
+
20: "Key",
|
|
431
|
+
21: "Null",
|
|
432
|
+
22: "EnumMember",
|
|
433
|
+
23: "Struct",
|
|
434
|
+
24: "Event",
|
|
435
|
+
25: "Operator",
|
|
436
|
+
26: "TypeParameter",
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
export function symbolKindToIcon(kind: SymbolKind): string {
|
|
440
|
+
return `[${SYMBOL_KIND_LABELS[kind] ?? "?"}]`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function symbolKindToName(kind: SymbolKind): string {
|
|
444
|
+
return SYMBOL_KIND_LABELS[kind] ?? "Unknown";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string[] {
|
|
448
|
+
const prefix = " ".repeat(indent);
|
|
449
|
+
const icon = symbolKindToIcon(symbol.kind);
|
|
450
|
+
const line = symbol.range.start.line + 1;
|
|
451
|
+
const detail = symbol.detail ? ` ${symbol.detail}` : "";
|
|
452
|
+
const results = [`${prefix}${icon} ${symbol.name}${detail} @ line ${line}`];
|
|
453
|
+
|
|
454
|
+
if (symbol.children) {
|
|
455
|
+
for (const child of symbol.children) {
|
|
456
|
+
results.push(...formatDocumentSymbol(child, indent + 1));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return results;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function formatSymbolInformation(symbol: SymbolInformation, cwd: string): string {
|
|
464
|
+
const icon = symbolKindToIcon(symbol.kind);
|
|
465
|
+
const location = formatLocation(symbol.location, cwd);
|
|
466
|
+
const container = symbol.containerName ? ` (${symbol.containerName})` : "";
|
|
467
|
+
return `${icon} ${symbol.name}${container} @ ${location}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function filterWorkspaceSymbols(symbols: SymbolInformation[], query: string): SymbolInformation[] {
|
|
471
|
+
const needle = query.trim().toLowerCase();
|
|
472
|
+
if (!needle) return symbols;
|
|
473
|
+
return symbols.filter(symbol => {
|
|
474
|
+
const fields = [symbol.name, symbol.containerName ?? "", uriToFile(symbol.location.uri)];
|
|
475
|
+
return fields.some(field => field.toLowerCase().includes(needle));
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function dedupeWorkspaceSymbols(symbols: SymbolInformation[]): SymbolInformation[] {
|
|
480
|
+
const seen = new Set<string>();
|
|
481
|
+
const unique: SymbolInformation[] = [];
|
|
482
|
+
for (const symbol of symbols) {
|
|
483
|
+
const key = [
|
|
484
|
+
symbol.name,
|
|
485
|
+
symbol.containerName ?? "",
|
|
486
|
+
symbol.kind,
|
|
487
|
+
symbol.location.uri,
|
|
488
|
+
symbol.location.range.start.line,
|
|
489
|
+
symbol.location.range.start.character,
|
|
490
|
+
].join(":");
|
|
491
|
+
if (seen.has(key)) continue;
|
|
492
|
+
seen.add(key);
|
|
493
|
+
unique.push(symbol);
|
|
494
|
+
}
|
|
495
|
+
return unique;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function formatCodeAction(action: CodeAction | Command, index: number): string {
|
|
499
|
+
const kind = "kind" in action && action.kind ? action.kind : "action";
|
|
500
|
+
const preferred = "isPreferred" in action && action.isPreferred ? " (preferred)" : "";
|
|
501
|
+
const disabled = "disabled" in action && action.disabled ? ` (disabled: ${action.disabled.reason})` : "";
|
|
502
|
+
return `${index}: [${kind}] ${action.title}${preferred}${disabled}`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export interface CodeActionApplyDependencies {
|
|
506
|
+
resolveCodeAction?: (action: CodeAction) => Promise<CodeAction>;
|
|
507
|
+
applyWorkspaceEdit: (edit: WorkspaceEdit) => Promise<string[]>;
|
|
508
|
+
executeCommand: (command: Command) => Promise<void>;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export interface AppliedCodeActionResult {
|
|
512
|
+
title: string;
|
|
513
|
+
edits: string[];
|
|
514
|
+
executedCommands: string[];
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function isCommandItem(action: CodeAction | Command): action is Command {
|
|
518
|
+
return typeof action.command === "string";
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export async function applyCodeAction(
|
|
522
|
+
action: CodeAction | Command,
|
|
523
|
+
dependencies: CodeActionApplyDependencies,
|
|
524
|
+
): Promise<AppliedCodeActionResult | null> {
|
|
525
|
+
if (isCommandItem(action)) {
|
|
526
|
+
await dependencies.executeCommand(action);
|
|
527
|
+
return { title: action.title, edits: [], executedCommands: [action.command] };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let resolvedAction = action;
|
|
531
|
+
if (!resolvedAction.edit && dependencies.resolveCodeAction) {
|
|
532
|
+
try {
|
|
533
|
+
resolvedAction = await dependencies.resolveCodeAction(resolvedAction);
|
|
534
|
+
} catch {
|
|
535
|
+
// Resolve is optional; continue with unresolved action.
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const edits = resolvedAction.edit ? await dependencies.applyWorkspaceEdit(resolvedAction.edit) : [];
|
|
540
|
+
const executedCommands: string[] = [];
|
|
541
|
+
if (resolvedAction.command) {
|
|
542
|
+
await dependencies.executeCommand(resolvedAction.command);
|
|
543
|
+
executedCommands.push(resolvedAction.command.command);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (edits.length === 0 && executedCommands.length === 0) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return { title: resolvedAction.title, edits, executedCommands };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const GLOB_PATTERN_CHARS = /[*?[{]/;
|
|
554
|
+
|
|
555
|
+
export function hasGlobPattern(value: string): boolean {
|
|
556
|
+
return GLOB_PATTERN_CHARS.test(value);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export async function collectGlobMatches(
|
|
560
|
+
pattern: string,
|
|
561
|
+
cwd: string,
|
|
562
|
+
maxMatches: number,
|
|
563
|
+
): Promise<{ matches: string[]; truncated: boolean }> {
|
|
564
|
+
const normalizedLimit = Number.isFinite(maxMatches) ? Math.max(1, Math.trunc(maxMatches)) : 1;
|
|
565
|
+
const allMatches = await glob(pattern, { cwd });
|
|
566
|
+
if (allMatches.length > normalizedLimit) {
|
|
567
|
+
return { matches: allMatches.slice(0, normalizedLimit), truncated: true };
|
|
568
|
+
}
|
|
569
|
+
return { matches: allMatches, truncated: false };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// =============================================================================
|
|
573
|
+
// Hover Content Extraction
|
|
574
|
+
// =============================================================================
|
|
575
|
+
|
|
576
|
+
export function extractHoverText(
|
|
577
|
+
contents: string | { kind: string; value: string } | { language: string; value: string } | unknown[],
|
|
578
|
+
): string {
|
|
579
|
+
if (typeof contents === "string") {
|
|
580
|
+
return contents;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (Array.isArray(contents)) {
|
|
584
|
+
return contents.map(c => extractHoverText(c as string | { kind: string; value: string })).join("\n\n");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (typeof contents === "object" && contents !== null) {
|
|
588
|
+
if ("value" in contents && typeof contents.value === "string") {
|
|
589
|
+
return contents.value;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return String(contents);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// =============================================================================
|
|
597
|
+
// General Utilities
|
|
598
|
+
// =============================================================================
|
|
599
|
+
|
|
600
|
+
function firstNonWhitespaceColumn(lineText: string): number {
|
|
601
|
+
const match = lineText.match(/\S/);
|
|
602
|
+
return match ? (match.index ?? 0) : 0;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function findSymbolMatchIndexes(lineText: string, symbol: string, caseInsensitive = false): number[] {
|
|
606
|
+
if (symbol.length === 0) return [];
|
|
607
|
+
const haystack = caseInsensitive ? lineText.toLowerCase() : lineText;
|
|
608
|
+
const needle = caseInsensitive ? symbol.toLowerCase() : symbol;
|
|
609
|
+
const indexes: number[] = [];
|
|
610
|
+
let fromIndex = 0;
|
|
611
|
+
while (fromIndex <= haystack.length - needle.length) {
|
|
612
|
+
const matchIndex = haystack.indexOf(needle, fromIndex);
|
|
613
|
+
if (matchIndex === -1) break;
|
|
614
|
+
indexes.push(matchIndex);
|
|
615
|
+
fromIndex = matchIndex + needle.length;
|
|
616
|
+
}
|
|
617
|
+
return indexes;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function normalizeOccurrence(occurrence?: number): number {
|
|
621
|
+
if (occurrence === undefined || !Number.isFinite(occurrence)) return 1;
|
|
622
|
+
return Math.max(1, Math.trunc(occurrence));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export async function resolveSymbolColumn(
|
|
626
|
+
filePath: string,
|
|
627
|
+
line: number,
|
|
628
|
+
symbol?: string,
|
|
629
|
+
occurrence?: number,
|
|
630
|
+
): Promise<number> {
|
|
631
|
+
const lineNumber = Math.max(1, line);
|
|
632
|
+
const matchOccurrence = normalizeOccurrence(occurrence);
|
|
633
|
+
try {
|
|
634
|
+
const fileText = await fsPromises.readFile(filePath, "utf-8");
|
|
635
|
+
const lines = fileText.split("\n");
|
|
636
|
+
const targetLine = lines[lineNumber - 1] ?? "";
|
|
637
|
+
if (!symbol) {
|
|
638
|
+
return firstNonWhitespaceColumn(targetLine);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const exactIndexes = findSymbolMatchIndexes(targetLine, symbol);
|
|
642
|
+
const fallbackIndexes = exactIndexes.length > 0 ? exactIndexes : findSymbolMatchIndexes(targetLine, symbol, true);
|
|
643
|
+
if (fallbackIndexes.length === 0) {
|
|
644
|
+
throw new Error(`Symbol "${symbol}" not found on line ${lineNumber}`);
|
|
645
|
+
}
|
|
646
|
+
if (matchOccurrence > fallbackIndexes.length) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Symbol "${symbol}" occurrence ${matchOccurrence} is out of bounds on line ${lineNumber} (found ${fallbackIndexes.length})`,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
return fallbackIndexes[matchOccurrence - 1];
|
|
652
|
+
} catch (error: unknown) {
|
|
653
|
+
if (isEnoent(error)) {
|
|
654
|
+
throw new Error(`File not found: ${filePath}`);
|
|
655
|
+
}
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export async function readLocationContext(filePath: string, line: number, contextLines = 1): Promise<string[]> {
|
|
661
|
+
const targetLine = Math.max(1, line);
|
|
662
|
+
const surrounding = Math.max(0, contextLines);
|
|
663
|
+
try {
|
|
664
|
+
const fileText = await fsPromises.readFile(filePath, "utf-8");
|
|
665
|
+
const lines = fileText.split("\n");
|
|
666
|
+
if (lines.length === 0) return [];
|
|
667
|
+
|
|
668
|
+
const startLine = Math.max(1, targetLine - surrounding);
|
|
669
|
+
const endLine = Math.min(lines.length, targetLine + surrounding);
|
|
670
|
+
const context: string[] = [];
|
|
671
|
+
for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
|
|
672
|
+
const content = lines[currentLine - 1] ?? "";
|
|
673
|
+
context.push(`${currentLine}: ${content}`);
|
|
674
|
+
}
|
|
675
|
+
return context;
|
|
676
|
+
} catch (error: unknown) {
|
|
677
|
+
if (isEnoent(error)) {
|
|
678
|
+
return [];
|
|
679
|
+
}
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
@@ -34,5 +34,6 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
34
34
|
{ name: "compact", description: "Manually compact the session context" },
|
|
35
35
|
{ name: "resume", description: "Resume a different session" },
|
|
36
36
|
{ name: "reload", description: "Reload extensions, skills, prompts, and themes" },
|
|
37
|
+
{ name: "thinking", description: "Set thinking level (off/minimal/low/medium/high/xhigh)" },
|
|
37
38
|
{ name: "quit", description: "Quit pi" },
|
|
38
39
|
];
|