dsh-hashline-edit 0.2.2 → 0.2.3
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 +57 -45
- package/cordis.patch.yml +3 -3
- package/lib/index.js +3175 -3943
- package/lib/types/dsh/contracts.d.ts +12 -3
- package/lib/types/dsh/fs.d.ts +3 -0
- package/lib/types/dsh/guidance.d.ts +5 -5
- package/lib/types/dsh/hide.d.ts +79 -0
- package/lib/types/dsh/host-compat.d.ts +7 -78
- package/lib/types/dsh/index.d.ts +21 -9
- package/lib/types/dsh/metrics/events.d.ts +1 -1
- package/lib/types/dsh/metrics/observer.d.ts +1 -1
- package/lib/types/dsh/sandbox.d.ts +2 -2
- package/lib/types/dsh/shell-hook.d.ts +1 -1
- package/lib/types/dsh/tools/apply-patch.d.ts +38 -0
- package/lib/types/dsh/tools/read.d.ts +3 -3
- package/lib/types/src/apply-patch/commit.d.ts +49 -0
- package/lib/types/src/apply-patch/grammar.d.ts +29 -0
- package/lib/types/src/apply-patch/lifecycle.d.ts +32 -0
- package/lib/types/src/apply-patch/plan.d.ts +50 -0
- package/lib/types/src/apply-patch/render.d.ts +51 -0
- package/lib/types/src/apply-patch/resolve.d.ts +129 -0
- package/lib/types/src/apply-patch/tool.d.ts +54 -0
- package/lib/types/src/apply-patch/types.d.ts +362 -0
- package/lib/types/src/domain-errors.d.ts +16 -2
- package/lib/types/src/edit-undo.d.ts +11 -2
- package/lib/types/src/file-content/index.d.ts +1 -1
- package/lib/types/src/file-content/preview.d.ts +1 -1
- package/lib/types/src/fs-lifecycle.d.ts +44 -0
- package/lib/types/src/hashline/alphabet.d.ts +4 -3
- package/lib/types/src/hashline/hash-identity.d.ts +12 -7
- package/lib/types/src/host-utils.d.ts +63 -0
- package/lib/types/src/mutation-engine/edit-request.d.ts +34 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +8 -1
- package/lib/types/src/mutation-engine/types.d.ts +40 -50
- package/lib/types/src/served-session/serve-render.d.ts +6 -0
- package/lib/types/src/undo-store.d.ts +17 -1
- package/package.json +18 -21
- package/prompts/apply-patch-guidelines.md +10 -0
- package/prompts/apply-patch.md +1 -0
- package/prompts/read-guidelines.md +4 -4
- package/prompts/read.md +1 -1
- package/prompts/undo-last-edit-guidelines.md +2 -2
- package/prompts/undo-last-edit-snippet.md +1 -1
- package/prompts/undo-last-edit.md +1 -1
- package/src/apply-patch/commit.ts +190 -0
- package/src/apply-patch/grammar.ts +620 -0
- package/src/apply-patch/index.ts +17 -0
- package/src/apply-patch/lifecycle.ts +195 -0
- package/src/apply-patch/plan.ts +800 -0
- package/src/apply-patch/render.ts +137 -0
- package/src/apply-patch/resolve.ts +470 -0
- package/src/apply-patch/tool.ts +257 -0
- package/src/apply-patch/types.ts +387 -0
- package/src/cut-repair.ts +77 -11
- package/src/domain-errors.ts +35 -3
- package/src/edit-undo.ts +379 -25
- package/src/file-content/index.ts +1 -1
- package/src/file-content/preview.ts +1 -1
- package/src/file-content/served-header.ts +1 -1
- package/src/fs-lifecycle.ts +201 -0
- package/src/hash-store.ts +18 -1
- package/src/hashline/alphabet.ts +7 -7
- package/src/hashline/hash-identity.ts +12 -7
- package/src/host-utils.ts +224 -0
- package/src/mutation-engine/edit-request.ts +39 -0
- package/src/mutation-engine/pipeline.ts +198 -27
- package/src/mutation-engine/types.ts +39 -57
- package/src/mutation-queue.ts +1 -1
- package/src/prompts.ts +3 -30
- package/src/served-session/serve-render.ts +2 -2
- package/src/undo-store.ts +74 -5
- package/lib/types/dsh/tools/edit.d.ts +0 -10
- package/lib/types/src/edit-tool.d.ts +0 -41
- package/lib/types/src/mutation-engine/engine.d.ts +0 -31
- package/lib/types/src/payload-contract.d.ts +0 -135
- package/prompts/edit-guidelines.md +0 -10
- package/prompts/edit-snippet.md +0 -1
- package/prompts/edit.md +0 -1
- package/src/bash-classifier.ts +0 -1626
- package/src/bash-conversion/decide.ts +0 -812
- package/src/bash-display-transform.ts +0 -50
- package/src/bash-multifile-config.ts +0 -239
- package/src/bash-view-config.ts +0 -158
- package/src/edit-normalize.ts +0 -8
- package/src/edit-render.ts +0 -161
- package/src/edit-tool.ts +0 -118
- package/src/edit.ts +0 -136
- package/src/integrations/pi-lens/command.ts +0 -712
- package/src/integrations/pi-lens/config.ts +0 -148
- package/src/integrations/pi-lens/io-bridge-adapter.ts +0 -234
- package/src/integrations/pi-lens/io-bridge.ts +0 -112
- package/src/integrations/pi-lens/settings-overlay.ts +0 -272
- package/src/lifecycle-hooks/index.ts +0 -1992
- package/src/lifecycle-hooks/types.ts +0 -64
- package/src/mutated-files.ts +0 -75
- package/src/mutation-engine/engine.ts +0 -239
- package/src/mutation-engine/index.ts +0 -17
- package/src/payload-contract.ts +0 -685
- package/src/preview-controller.ts +0 -84
- package/src/read.ts +0 -865
- package/src/search-hook.ts +0 -41
- package/src/store-config.ts +0 -73
- package/src/store-janitor-warning.ts +0 -169
- package/src/store-janitor.ts +0 -539
- package/src/store-kernel/settings.ts +0 -233
- package/src/store.ts +0 -154
- package/src/tui-presenter.ts +0 -134
- package/src/write-hook.ts +0 -130
|
@@ -1,712 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `/pi-better-edit` — the command surface for the bridge and the bash view switch.
|
|
3
|
-
*
|
|
4
|
-
* WHY this lives beside the extension boundary: it is the only place where user input, UI dialogs
|
|
5
|
-
* and the config file meet, so every branch here is either "answer with text" or "write one
|
|
6
|
-
* setting". Nothing in this module is imported by the editing path, and an absent UI degrades to
|
|
7
|
-
* the same text output a headless host can print.
|
|
8
|
-
*/
|
|
9
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
11
|
-
import {
|
|
12
|
-
isLensBridgeMode,
|
|
13
|
-
isLensBridgeScope,
|
|
14
|
-
LENS_BRIDGE_ENV_VAR,
|
|
15
|
-
LENS_CONFIG_REL,
|
|
16
|
-
readLensBridgeConfig,
|
|
17
|
-
resolveEffectiveLensModeWithSource,
|
|
18
|
-
saveLensBridgeConfig,
|
|
19
|
-
type LensBridgeMode,
|
|
20
|
-
type LensBridgeScope,
|
|
21
|
-
} from "./config.js";
|
|
22
|
-
import { configDirOverride, hashStorePath, STORE_CONFIG_DIR_ENV_VAR } from "../../hash-store.js";
|
|
23
|
-
import { stateRoot } from "../../config-dir.js";
|
|
24
|
-
import {
|
|
25
|
-
resolveStoreJanitorSettingsWithSources,
|
|
26
|
-
STORE_JANITOR_KEY_NAMES,
|
|
27
|
-
} from "../../store-kernel/settings.js";
|
|
28
|
-
import {
|
|
29
|
-
BASH_VIEW_ENV_VAR,
|
|
30
|
-
isBashViewMode,
|
|
31
|
-
isBashViewScope,
|
|
32
|
-
readBashViewConfig,
|
|
33
|
-
resolveBashViewModeWithSource,
|
|
34
|
-
saveBashViewConfig,
|
|
35
|
-
type BashViewMode,
|
|
36
|
-
} from "../../bash-view-config.js";
|
|
37
|
-
import {
|
|
38
|
-
READ_EXPANSION_ENV_VAR,
|
|
39
|
-
isReadExpansionMode,
|
|
40
|
-
isReadExpansionScope,
|
|
41
|
-
readReadExpansionConfig,
|
|
42
|
-
resolveReadExpansionModeWithSource,
|
|
43
|
-
saveReadExpansionConfig,
|
|
44
|
-
type ReadExpansionMode,
|
|
45
|
-
} from "../../read-expansion-config.js";
|
|
46
|
-
import {
|
|
47
|
-
BASH_MULTI_FILE_LIMITS,
|
|
48
|
-
isBashMultiFileMaxFiles,
|
|
49
|
-
isBashMultiFileMaxRows,
|
|
50
|
-
isBashMultiFileScope,
|
|
51
|
-
readBashMultiFileConfig,
|
|
52
|
-
resolveBashMultiFileSettingsWithSources,
|
|
53
|
-
saveBashMultiFileConfig,
|
|
54
|
-
} from "../../bash-multifile-config.js";
|
|
55
|
-
import {
|
|
56
|
-
CONFIG_REL,
|
|
57
|
-
configFileDisplayPath,
|
|
58
|
-
configFilePath,
|
|
59
|
-
displayPath,
|
|
60
|
-
type ConfigScope,
|
|
61
|
-
} from "../../json-config.js";
|
|
62
|
-
import { DomainError } from "../../domain-errors.js";
|
|
63
|
-
import { SettingsOverlay } from "./settings-overlay.js";
|
|
64
|
-
import {
|
|
65
|
-
DIAGNOSTICS_LOG_MAX_BYTES,
|
|
66
|
-
diagnosticsLogStatus,
|
|
67
|
-
reportDiagnostic,
|
|
68
|
-
} from "../../diagnostics.js";
|
|
69
|
-
const COMMAND_NAME = "pi-better-edit";
|
|
70
|
-
const IO_BRIDGE_SYMBOL_KEY = "pi-lens:io-bridge";
|
|
71
|
-
|
|
72
|
-
const USAGE = [
|
|
73
|
-
`${COMMAND_NAME} — configure the pi-lens bridge`,
|
|
74
|
-
`Usage: /${COMMAND_NAME} lens [auto|on|off|status] [project|global]`,
|
|
75
|
-
` /${COMMAND_NAME} lens pick a mode interactively`,
|
|
76
|
-
` /${COMMAND_NAME} lens auto global write the global default`,
|
|
77
|
-
` /${COMMAND_NAME} lens status show effective mode and config sources`,
|
|
78
|
-
].join("\n");
|
|
79
|
-
|
|
80
|
-
const BASH_VIEW_USAGE = [
|
|
81
|
-
`${COMMAND_NAME} — configure bash view interception`,
|
|
82
|
-
`Usage: /${COMMAND_NAME} bash-view [on|off|status] [project|global]`,
|
|
83
|
-
` /${COMMAND_NAME} bash-view pick a mode interactively`,
|
|
84
|
-
` /${COMMAND_NAME} bash-view off global write the global default`,
|
|
85
|
-
` /${COMMAND_NAME} bash-view status show effective mode and config sources`,
|
|
86
|
-
` /${COMMAND_NAME} bash-view multi-file status show the multi-file caps and sources`,
|
|
87
|
-
` /${COMMAND_NAME} bash-view multi-file max-files 8 project lower the file cap (2-10)`,
|
|
88
|
-
` /${COMMAND_NAME} bash-view multi-file max-rows 1000 global lower the row cap (1-2000)`,
|
|
89
|
-
`Alias: /${COMMAND_NAME} bash [on|off|status] [project|global]`,
|
|
90
|
-
].join("\n");
|
|
91
|
-
|
|
92
|
-
const READ_EXPANSION_USAGE = [
|
|
93
|
-
`${COMMAND_NAME} — configure read expansion`,
|
|
94
|
-
`Usage: /${COMMAND_NAME} read-expansion [on|off|status] [project|global]`,
|
|
95
|
-
` /${COMMAND_NAME} read-expansion pick a mode interactively`,
|
|
96
|
-
` /${COMMAND_NAME} read-expansion off global write the global default`,
|
|
97
|
-
` /${COMMAND_NAME} read-expansion status show effective mode and config sources`,
|
|
98
|
-
`Alias: /${COMMAND_NAME} read [on|off|status] [project|global]`,
|
|
99
|
-
].join("\n");
|
|
100
|
-
|
|
101
|
-
const SETTINGS_USAGE = [
|
|
102
|
-
`${COMMAND_NAME} — review every setting in one overlay`,
|
|
103
|
-
`Usage: /${COMMAND_NAME} settings`,
|
|
104
|
-
` /${COMMAND_NAME} settings review and change every setting`,
|
|
105
|
-
` /${COMMAND_NAME} bare invocation opens the same overlay`,
|
|
106
|
-
].join("\n");
|
|
107
|
-
|
|
108
|
-
const COMMAND_COMPLETIONS: readonly string[] = [
|
|
109
|
-
"lens",
|
|
110
|
-
"lens auto",
|
|
111
|
-
"lens on",
|
|
112
|
-
"lens off",
|
|
113
|
-
"lens status",
|
|
114
|
-
"bash-view",
|
|
115
|
-
"bash-view on",
|
|
116
|
-
"bash-view off",
|
|
117
|
-
"bash-view status",
|
|
118
|
-
"bash-view multi-file",
|
|
119
|
-
"bash-view multi-file status",
|
|
120
|
-
"bash-view multi-file max-files",
|
|
121
|
-
"bash-view multi-file max-rows",
|
|
122
|
-
"bash",
|
|
123
|
-
"bash on",
|
|
124
|
-
"bash off",
|
|
125
|
-
"bash status",
|
|
126
|
-
"read-expansion",
|
|
127
|
-
"read-expansion on",
|
|
128
|
-
"read-expansion off",
|
|
129
|
-
"read-expansion status",
|
|
130
|
-
"read",
|
|
131
|
-
"read on",
|
|
132
|
-
"read off",
|
|
133
|
-
"read status",
|
|
134
|
-
"settings",
|
|
135
|
-
];
|
|
136
|
-
type LensPickerChoice =
|
|
137
|
-
| {
|
|
138
|
-
readonly label: string;
|
|
139
|
-
readonly kind: "set-mode";
|
|
140
|
-
readonly mode: LensBridgeMode;
|
|
141
|
-
readonly scope: LensBridgeScope;
|
|
142
|
-
}
|
|
143
|
-
| { readonly label: string; readonly kind: "status" };
|
|
144
|
-
|
|
145
|
-
const LENS_PICKER_CHOICES: readonly LensPickerChoice[] = [
|
|
146
|
-
{ label: "Auto (Project)", kind: "set-mode", mode: "auto", scope: "project" },
|
|
147
|
-
{ label: "Auto (Global)", kind: "set-mode", mode: "auto", scope: "global" },
|
|
148
|
-
{ label: "On (Project)", kind: "set-mode", mode: "on", scope: "project" },
|
|
149
|
-
{ label: "On (Global)", kind: "set-mode", mode: "on", scope: "global" },
|
|
150
|
-
{ label: "Off (Project)", kind: "set-mode", mode: "off", scope: "project" },
|
|
151
|
-
{ label: "Off (Global)", kind: "set-mode", mode: "off", scope: "global" },
|
|
152
|
-
{ label: "Status", kind: "status" },
|
|
153
|
-
];
|
|
154
|
-
|
|
155
|
-
type BashViewPickerChoice =
|
|
156
|
-
| {
|
|
157
|
-
readonly label: string;
|
|
158
|
-
readonly kind: "set-mode";
|
|
159
|
-
readonly mode: BashViewMode;
|
|
160
|
-
readonly scope: ConfigScope;
|
|
161
|
-
}
|
|
162
|
-
| { readonly label: string; readonly kind: "status" };
|
|
163
|
-
|
|
164
|
-
const BASH_VIEW_PICKER_CHOICES: readonly BashViewPickerChoice[] = [
|
|
165
|
-
{ label: "On (Project)", kind: "set-mode", mode: "on", scope: "project" },
|
|
166
|
-
{ label: "On (Global)", kind: "set-mode", mode: "on", scope: "global" },
|
|
167
|
-
{ label: "Off (Project)", kind: "set-mode", mode: "off", scope: "project" },
|
|
168
|
-
{ label: "Off (Global)", kind: "set-mode", mode: "off", scope: "global" },
|
|
169
|
-
{ label: "Status", kind: "status" },
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
type ReadExpansionPickerChoice =
|
|
173
|
-
| {
|
|
174
|
-
readonly label: string;
|
|
175
|
-
readonly kind: "set-mode";
|
|
176
|
-
readonly mode: ReadExpansionMode;
|
|
177
|
-
readonly scope: ConfigScope;
|
|
178
|
-
}
|
|
179
|
-
| { readonly label: string; readonly kind: "status" };
|
|
180
|
-
|
|
181
|
-
const READ_EXPANSION_PICKER_CHOICES: readonly ReadExpansionPickerChoice[] = [
|
|
182
|
-
{ label: "On (Project)", kind: "set-mode", mode: "on", scope: "project" },
|
|
183
|
-
{ label: "On (Global)", kind: "set-mode", mode: "on", scope: "global" },
|
|
184
|
-
{ label: "Off (Project)", kind: "set-mode", mode: "off", scope: "project" },
|
|
185
|
-
{ label: "Off (Global)", kind: "set-mode", mode: "off", scope: "global" },
|
|
186
|
-
{ label: "Status", kind: "status" },
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
/** SAFETY: presence-only probe — the io bridge is mounted on globalThis by the extension that
|
|
190
|
-
* owns it, and this command reports whether that mount point exists without ever calling into it. */
|
|
191
|
-
function isIOBridgeMounted(): boolean {
|
|
192
|
-
const key: symbol = Symbol.for(IO_BRIDGE_SYMBOL_KEY);
|
|
193
|
-
return key in globalThis;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function errorText(error: unknown): string {
|
|
197
|
-
return error instanceof Error ? error.message : String(error);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** Notify when a dialog-capable UI exists, otherwise print — a headless host still sees the text. */
|
|
201
|
-
function notify(
|
|
202
|
-
ctx: ExtensionCommandContext,
|
|
203
|
-
message: string,
|
|
204
|
-
type: "info" | "warning" | "error" = "info",
|
|
205
|
-
): void {
|
|
206
|
-
if (ctx.hasUI) {
|
|
207
|
-
ctx.ui.notify(message, type);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// WHY: the only legitimate stdout write left in the extension — `hasUI === false` means print or
|
|
211
|
-
// WHY: json mode, where no renderer owns the terminal and stdout is the channel that reaches the
|
|
212
|
-
// WHY: caller. The TUI branch above returns before this line, so a live frame is never touched.
|
|
213
|
-
// SAFETY: the message carries the command's entire output and there is no other channel in these modes.
|
|
214
|
-
console.log(message);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function statusText(cwd: string): string {
|
|
218
|
-
const config = readLensBridgeConfig(cwd);
|
|
219
|
-
const effective = resolveEffectiveLensModeWithSource(cwd);
|
|
220
|
-
const envRaw = process.env[LENS_BRIDGE_ENV_VAR];
|
|
221
|
-
const envValue = envRaw === undefined || envRaw.trim().length === 0 ? "unset" : envRaw.trim();
|
|
222
|
-
const lensLines = [
|
|
223
|
-
`${COMMAND_NAME} lens bridge`,
|
|
224
|
-
` effective: ${effective.mode}`,
|
|
225
|
-
` effective source: ${effective.source}`,
|
|
226
|
-
` io bridge detected: ${isIOBridgeMounted() ? "yes" : "no"}`,
|
|
227
|
-
` project (${LENS_CONFIG_REL}): ${config.project ?? "unset"}`,
|
|
228
|
-
` global (${configFileDisplayPath("global", cwd)}): ${config.global ?? "unset"}`,
|
|
229
|
-
` env ${LENS_BRIDGE_ENV_VAR}: ${envValue}`,
|
|
230
|
-
" precedence: env > project > global > auto",
|
|
231
|
-
];
|
|
232
|
-
// WHY: a blank line between the blocks — they are five subjects: the bridge, the store, the bash
|
|
233
|
-
// WHY: view, its multi-file caps, and the diagnostics log — and each one keeps the shape the others
|
|
234
|
-
// WHY: taught the reader.
|
|
235
|
-
return [
|
|
236
|
-
lensLines.join("\n"),
|
|
237
|
-
"",
|
|
238
|
-
storeStatusText(cwd),
|
|
239
|
-
"",
|
|
240
|
-
bashViewStatusText(cwd),
|
|
241
|
-
"",
|
|
242
|
-
readExpansionStatusText(cwd),
|
|
243
|
-
"",
|
|
244
|
-
multiFileStatusText(cwd),
|
|
245
|
-
"",
|
|
246
|
-
diagnosticsStatusText(),
|
|
247
|
-
].join("\n");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* The store's half of the status output, in the lens block's own layout: the settings, then the
|
|
252
|
-
* override, then a precedence line.
|
|
253
|
-
*
|
|
254
|
-
* WHY each key carries its rung: the ladder resolves PER KEY, so "the project file is set" says
|
|
255
|
-
* nothing about which value the store will actually use, and the rung is what says which file to
|
|
256
|
-
* edit next.
|
|
257
|
-
*
|
|
258
|
-
* WHY the store path is printed with its override: `PI_BETTER_EDIT_CONFIG_DIR` is the ONLY thing that
|
|
259
|
-
* can move the store, and no config file key can (there is deliberately no `store.dir`), which is
|
|
260
|
-
* worth saying where a reader is looking for the setting that moved it.
|
|
261
|
-
*
|
|
262
|
-
* WHY a warning is repeated here: an unusable config file degrades to the defaults, and a display
|
|
263
|
-
* that showed those defaults with no reason would be the silent drop this ladder refuses to make.
|
|
264
|
-
*/
|
|
265
|
-
function storeStatusText(cwd: string): string {
|
|
266
|
-
const warnings: string[] = [];
|
|
267
|
-
const settings = resolveStoreJanitorSettingsWithSources(cwd, (reason) => warnings.push(reason));
|
|
268
|
-
// WHY: verbatim and never trimmed — this is the value the store will USE (see configDirOverride),
|
|
269
|
-
// WHY: so a display that trimmed it would name a directory the store does not open.
|
|
270
|
-
const override = configDirOverride();
|
|
271
|
-
return [
|
|
272
|
-
`${COMMAND_NAME} store`,
|
|
273
|
-
` store.${STORE_JANITOR_KEY_NAMES.mode}: ${settings.mode.value} (${settings.mode.source})`,
|
|
274
|
-
` store.${STORE_JANITOR_KEY_NAMES.intervalMs}: ${settings.intervalMs.value} (${settings.intervalMs.source})`,
|
|
275
|
-
` store.${STORE_JANITOR_KEY_NAMES.batchSize}: ${settings.batchSize.value} (${settings.batchSize.source})`,
|
|
276
|
-
` store path: ${hashStorePath()}`,
|
|
277
|
-
` settings: ${configFileDisplayPath("global", cwd)}`,
|
|
278
|
-
` state root: ${displayPath(stateRoot())}`,
|
|
279
|
-
` override ${STORE_CONFIG_DIR_ENV_VAR}: ${override ?? "unset"}`,
|
|
280
|
-
` project settings: ${displayPath(configFilePath("project", cwd))}`,
|
|
281
|
-
" precedence: env > project > global > default",
|
|
282
|
-
` store path is set by ${STORE_CONFIG_DIR_ENV_VAR} alone: no config file key can move it`,
|
|
283
|
-
...warnings.map((reason) => ` warning: ${reason}`),
|
|
284
|
-
].join("\n");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* The bash view half of the status output, in the lens block's own layout: the effective
|
|
289
|
-
* mode with its rung, then both file values, then the override, then a precedence line.
|
|
290
|
-
*
|
|
291
|
-
* WHY the file values are printed beside the effective mode: the ladder resolves one key,
|
|
292
|
-
* so the losing file value is the one an operator edits next — hiding it would send the
|
|
293
|
-
* reader to guess which file to open.
|
|
294
|
-
*
|
|
295
|
-
* WHY a warning is repeated here: an unusable config file degrades to the default, and a
|
|
296
|
-
* display that showed that default with no reason would be the silent drop this ladder
|
|
297
|
-
* refuses to make.
|
|
298
|
-
*/
|
|
299
|
-
function bashViewStatusText(cwd: string): string {
|
|
300
|
-
const warnings: string[] = [];
|
|
301
|
-
const effective = resolveBashViewModeWithSource(cwd, (reason) => warnings.push(reason));
|
|
302
|
-
const config = readBashViewConfig(cwd);
|
|
303
|
-
const envRaw = process.env[BASH_VIEW_ENV_VAR];
|
|
304
|
-
const envValue = envRaw === undefined || envRaw.trim().length === 0 ? "unset" : envRaw.trim();
|
|
305
|
-
return [
|
|
306
|
-
`${COMMAND_NAME} bash view`,
|
|
307
|
-
` effective: ${effective.value}`,
|
|
308
|
-
` effective source: ${effective.source}`,
|
|
309
|
-
` project (${CONFIG_REL}): ${config.project ?? "unset"}`,
|
|
310
|
-
` global (${configFileDisplayPath("global", cwd)}): ${config.global ?? "unset"}`,
|
|
311
|
-
` env ${BASH_VIEW_ENV_VAR}: ${envValue}`,
|
|
312
|
-
" precedence: env > project > global > on",
|
|
313
|
-
...warnings.map((reason) => ` warning: ${reason}`),
|
|
314
|
-
].join("\n");
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function readExpansionStatusText(cwd: string): string {
|
|
318
|
-
const warnings: string[] = [];
|
|
319
|
-
const effective = resolveReadExpansionModeWithSource(cwd, (reason) => warnings.push(reason));
|
|
320
|
-
const config = readReadExpansionConfig(cwd);
|
|
321
|
-
const envRaw = process.env[READ_EXPANSION_ENV_VAR];
|
|
322
|
-
const envValue = envRaw === undefined || envRaw.trim().length === 0 ? "unset" : envRaw.trim();
|
|
323
|
-
return [
|
|
324
|
-
`${COMMAND_NAME} read expansion`,
|
|
325
|
-
` effective: ${effective.value}`,
|
|
326
|
-
` effective source: ${effective.source}`,
|
|
327
|
-
` project (${CONFIG_REL}): ${config.project ?? "unset"}`,
|
|
328
|
-
` global (${configFileDisplayPath("global", cwd)}): ${config.global ?? "unset"}`,
|
|
329
|
-
` env ${READ_EXPANSION_ENV_VAR}: ${envValue}`,
|
|
330
|
-
" precedence: env > project > global > on",
|
|
331
|
-
...warnings.map((reason) => ` warning: ${reason}`),
|
|
332
|
-
].join("\n");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* The multi-file caps' half of the status output (ADR-0049 M6).
|
|
337
|
-
*
|
|
338
|
-
* WHY its own block rather than two more lines in the bash-view block: the two ladders have different
|
|
339
|
-
* precedence (`view` has an env override; the caps resolve project > global > default), and one
|
|
340
|
-
* precedence line per block is the only honest way to say so. WHY both file values are printed beside
|
|
341
|
-
* the effective ones: the losing value is the one an operator edits next.
|
|
342
|
-
*/
|
|
343
|
-
function multiFileStatusText(cwd: string): string {
|
|
344
|
-
const warnings: string[] = [];
|
|
345
|
-
const effective = resolveBashMultiFileSettingsWithSources(cwd, (reason) => warnings.push(reason));
|
|
346
|
-
const config = readBashMultiFileConfig(cwd);
|
|
347
|
-
const scopeLine = (
|
|
348
|
-
scope: ConfigScope,
|
|
349
|
-
patch: { readonly maxFiles?: number; readonly maxRows?: number } | undefined,
|
|
350
|
-
path: string,
|
|
351
|
-
): string =>
|
|
352
|
-
` ${scope} (${path}): maxFiles=${patch?.maxFiles ?? "unset"}, maxRows=${patch?.maxRows ?? "unset"}`;
|
|
353
|
-
return [
|
|
354
|
-
`${COMMAND_NAME} bash multi-file`,
|
|
355
|
-
` bash.multiFile.maxFiles: ${effective.maxFiles.value} (${effective.maxFiles.source})`,
|
|
356
|
-
` bash.multiFile.maxRows: ${effective.maxRows.value} (${effective.maxRows.source})`,
|
|
357
|
-
scopeLine("project", config.project, CONFIG_REL),
|
|
358
|
-
scopeLine("global", config.global, configFileDisplayPath("global", cwd)),
|
|
359
|
-
` allowed: maxFiles ${BASH_MULTI_FILE_LIMITS.maxFiles.min}-${BASH_MULTI_FILE_LIMITS.maxFiles.max}, maxRows ${BASH_MULTI_FILE_LIMITS.maxRows.min}-${BASH_MULTI_FILE_LIMITS.maxRows.max}`,
|
|
360
|
-
" precedence: project > global > default",
|
|
361
|
-
...warnings.map((reason) => ` warning: ${reason}`),
|
|
362
|
-
].join("\n");
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* The diagnostics half of the status output: where the log is, how big it is, and when it was
|
|
367
|
-
* last written.
|
|
368
|
-
*
|
|
369
|
-
* WHY the path is printed at all: every failure that used to be smeared over the UI as untracked
|
|
370
|
-
* stderr now lands in this file instead, so "where is the error I just saw?" has to be answerable
|
|
371
|
-
* from the command an operator already runs.
|
|
372
|
-
*/
|
|
373
|
-
function diagnosticsStatusText(): string {
|
|
374
|
-
const status = diagnosticsLogStatus();
|
|
375
|
-
return [
|
|
376
|
-
`${COMMAND_NAME} diagnostics`,
|
|
377
|
-
` log: ${status.path}`,
|
|
378
|
-
` size: ${status.bytes === 0 ? "none recorded" : `${status.bytes} bytes`}`,
|
|
379
|
-
...(status.modifiedAt === undefined
|
|
380
|
-
? []
|
|
381
|
-
: [` last write: ${status.modifiedAt.toISOString()}`]),
|
|
382
|
-
` rotation: ${DIAGNOSTICS_LOG_MAX_BYTES}-byte cap, one previous generation (.1)`,
|
|
383
|
-
` path is set by ${STORE_CONFIG_DIR_ENV_VAR} alone, beside the store`,
|
|
384
|
-
].join("\n");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function applyMode(mode: LensBridgeMode, scope: LensBridgeScope, cwd: string): string {
|
|
388
|
-
const configPath = saveLensBridgeConfig(mode, scope, cwd).path;
|
|
389
|
-
return `${COMMAND_NAME} lens bridge set to ${mode} (${scope}) — ${configPath}`;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function pickInteractively(ctx: ExtensionCommandContext): Promise<void> {
|
|
393
|
-
const labels = LENS_PICKER_CHOICES.map((choice) => choice.label);
|
|
394
|
-
const picked = await ctx.ui.select(`${COMMAND_NAME} lens bridge`, labels);
|
|
395
|
-
// WHY: an undefined result is the user cancelling the dialog, so nothing is written and no
|
|
396
|
-
// WHY: confirmation is printed — silence is the correct response to Esc.
|
|
397
|
-
if (picked === undefined) return;
|
|
398
|
-
const choice = LENS_PICKER_CHOICES.find((candidate) => candidate.label === picked);
|
|
399
|
-
if (choice === undefined) {
|
|
400
|
-
notify(ctx, `Unrecognized choice "${picked}".`, "warning");
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
if (choice.kind === "status") {
|
|
404
|
-
notify(ctx, statusText(ctx.cwd));
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
notify(ctx, applyMode(choice.mode, choice.scope, ctx.cwd));
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
async function handleLens(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
411
|
-
const tokens = args
|
|
412
|
-
.trim()
|
|
413
|
-
.split(/\s+/)
|
|
414
|
-
.filter((token) => token.length > 0);
|
|
415
|
-
if (tokens.length > 3) {
|
|
416
|
-
notify(ctx, `Too many arguments.\n${USAGE}`, "warning");
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (tokens[0] !== "lens") {
|
|
420
|
-
notify(ctx, USAGE);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const sub = tokens[1] ?? "interactive";
|
|
425
|
-
let scope: LensBridgeScope = "project";
|
|
426
|
-
const scopeToken = tokens[2];
|
|
427
|
-
if (scopeToken !== undefined) {
|
|
428
|
-
if (!isLensBridgeScope(scopeToken)) {
|
|
429
|
-
notify(ctx, `Unknown scope "${scopeToken}".\n${USAGE}`, "warning");
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
scope = scopeToken;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (sub === "status") {
|
|
436
|
-
notify(ctx, statusText(ctx.cwd));
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
if (isLensBridgeMode(sub)) {
|
|
440
|
-
notify(ctx, applyMode(sub, scope, ctx.cwd));
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (sub !== "interactive") {
|
|
444
|
-
notify(ctx, `Unknown option "${sub}".\n${USAGE}`, "warning");
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (!ctx.hasUI) {
|
|
448
|
-
notify(ctx, statusText(ctx.cwd));
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
await pickInteractively(ctx);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function applyBashViewMode(mode: BashViewMode, scope: ConfigScope, cwd: string): string {
|
|
455
|
-
const configPath = saveBashViewConfig(mode, scope, cwd).path;
|
|
456
|
-
return `${COMMAND_NAME} bash view set to ${mode} (${scope}) — ${configPath}`;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
async function pickBashViewInteractively(ctx: ExtensionCommandContext): Promise<void> {
|
|
460
|
-
const labels = BASH_VIEW_PICKER_CHOICES.map((choice) => choice.label);
|
|
461
|
-
const picked = await ctx.ui.select(`${COMMAND_NAME} bash view`, labels);
|
|
462
|
-
// WHY: an undefined result is the user cancelling the dialog, so nothing is written and no
|
|
463
|
-
// WHY: confirmation is printed — silence is the correct response to Esc.
|
|
464
|
-
if (picked === undefined) return;
|
|
465
|
-
const choice = BASH_VIEW_PICKER_CHOICES.find((candidate) => candidate.label === picked);
|
|
466
|
-
if (choice === undefined) {
|
|
467
|
-
notify(ctx, `Unrecognized choice "${picked}".`, "warning");
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
if (choice.kind === "status") {
|
|
471
|
-
notify(ctx, statusText(ctx.cwd));
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
notify(ctx, applyBashViewMode(choice.mode, choice.scope, ctx.cwd));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* `/pi-better-edit bash-view multi-file …` — the caps' command surface (ADR-0049 M6).
|
|
479
|
-
*
|
|
480
|
-
* WHY numbers live here and not in the settings overlay: the overlay is an enum-only `SettingsList`
|
|
481
|
-
* (ADR-0038), so arbitrary numeric editing belongs to the subcommand — exactly as M6 says. A value
|
|
482
|
-
* outside the allowed range is refused with the range named, and nothing is written; the caps can be
|
|
483
|
-
* LOWERED below the safety bound, never raised past it.
|
|
484
|
-
*/
|
|
485
|
-
function handleMultiFile(tokens: string[], ctx: ExtensionCommandContext): void {
|
|
486
|
-
const sub = tokens[1] ?? "status";
|
|
487
|
-
if (sub === "status") {
|
|
488
|
-
if (tokens.length > 2) {
|
|
489
|
-
notify(ctx, `Too many arguments.\n${BASH_VIEW_USAGE}`, "warning");
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
notify(ctx, multiFileStatusText(ctx.cwd));
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
const key = sub === "max-files" ? "maxFiles" : sub === "max-rows" ? "maxRows" : undefined;
|
|
496
|
-
if (key === undefined) {
|
|
497
|
-
notify(ctx, `Unknown multi-file option "${sub}".\n${BASH_VIEW_USAGE}`, "warning");
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
if (tokens.length > 4) {
|
|
501
|
-
notify(ctx, `Too many arguments.\n${BASH_VIEW_USAGE}`, "warning");
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
const rawValue = tokens[2];
|
|
505
|
-
const limit = BASH_MULTI_FILE_LIMITS[key];
|
|
506
|
-
const accepted = key === "maxFiles" ? isBashMultiFileMaxFiles : isBashMultiFileMaxRows;
|
|
507
|
-
const value = rawValue === undefined ? Number.NaN : Number(rawValue);
|
|
508
|
-
if (!accepted(value)) {
|
|
509
|
-
notify(
|
|
510
|
-
ctx,
|
|
511
|
-
`bash.multiFile.${key} must be an integer in ${limit.min}-${limit.max}.\n${BASH_VIEW_USAGE}`,
|
|
512
|
-
"warning",
|
|
513
|
-
);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
let scope: ConfigScope = "project";
|
|
517
|
-
const scopeToken = tokens[3];
|
|
518
|
-
if (scopeToken !== undefined) {
|
|
519
|
-
if (!isBashMultiFileScope(scopeToken)) {
|
|
520
|
-
notify(ctx, `Unknown scope "${scopeToken}".\n${BASH_VIEW_USAGE}`, "warning");
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
scope = scopeToken;
|
|
524
|
-
}
|
|
525
|
-
const patch = key === "maxFiles" ? { maxFiles: value } : { maxRows: value };
|
|
526
|
-
const path = saveBashMultiFileConfig(patch, scope, ctx.cwd).path;
|
|
527
|
-
notify(ctx, `${COMMAND_NAME} bash multi-file ${key} set to ${value} (${scope}) — ${path}`);
|
|
528
|
-
}
|
|
529
|
-
async function handleBashView(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
530
|
-
const tokens = args
|
|
531
|
-
.trim()
|
|
532
|
-
.split(/\s+/)
|
|
533
|
-
.filter((token) => token.length > 0);
|
|
534
|
-
// WHY: the multi-file route is dispatched BEFORE the 3-token ceiling — its own surface takes four
|
|
535
|
-
// WHY: tokens (`bash-view multi-file max-files 8 global`), and it validates them itself.
|
|
536
|
-
if (tokens[1] === "multi-file") {
|
|
537
|
-
handleMultiFile(tokens.slice(1), ctx);
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
if (tokens.length > 3) {
|
|
541
|
-
notify(ctx, `Too many arguments.\n${BASH_VIEW_USAGE}`, "warning");
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
if (tokens[0] !== "bash-view" && tokens[0] !== "bash") {
|
|
545
|
-
notify(ctx, BASH_VIEW_USAGE);
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const sub = tokens[1] ?? "interactive";
|
|
550
|
-
let scope: ConfigScope = "project";
|
|
551
|
-
const scopeToken = tokens[2];
|
|
552
|
-
if (scopeToken !== undefined) {
|
|
553
|
-
if (!isBashViewScope(scopeToken)) {
|
|
554
|
-
notify(ctx, `Unknown scope "${scopeToken}".\n${BASH_VIEW_USAGE}`, "warning");
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
scope = scopeToken;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (sub === "status") {
|
|
561
|
-
notify(ctx, statusText(ctx.cwd));
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
if (isBashViewMode(sub)) {
|
|
565
|
-
notify(ctx, applyBashViewMode(sub, scope, ctx.cwd));
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (sub !== "interactive") {
|
|
569
|
-
notify(ctx, `Unknown option "${sub}".\n${BASH_VIEW_USAGE}`, "warning");
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (!ctx.hasUI) {
|
|
573
|
-
notify(ctx, statusText(ctx.cwd));
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
await pickBashViewInteractively(ctx);
|
|
577
|
-
}
|
|
578
|
-
function applyReadExpansionMode(mode: ReadExpansionMode, scope: ConfigScope, cwd: string): string {
|
|
579
|
-
const configPath = saveReadExpansionConfig(mode, scope, cwd).path;
|
|
580
|
-
return `${COMMAND_NAME} read expansion set to ${mode} (${scope}) — ${configPath}`;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function pickReadExpansionInteractively(ctx: ExtensionCommandContext): Promise<void> {
|
|
584
|
-
const labels = READ_EXPANSION_PICKER_CHOICES.map((choice) => choice.label);
|
|
585
|
-
const picked = await ctx.ui.select(`${COMMAND_NAME} read expansion`, labels);
|
|
586
|
-
if (picked === undefined) return;
|
|
587
|
-
const choice = READ_EXPANSION_PICKER_CHOICES.find((candidate) => candidate.label === picked);
|
|
588
|
-
if (choice === undefined) {
|
|
589
|
-
notify(ctx, `Unrecognized choice "${picked}".`, "warning");
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
if (choice.kind === "status") {
|
|
593
|
-
notify(ctx, readExpansionStatusText(ctx.cwd));
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
notify(ctx, applyReadExpansionMode(choice.mode, choice.scope, ctx.cwd));
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async function handleReadExpansion(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
600
|
-
const tokens = args
|
|
601
|
-
.trim()
|
|
602
|
-
.split(/\s+/)
|
|
603
|
-
.filter((token) => token.length > 0);
|
|
604
|
-
if (tokens.length > 3) {
|
|
605
|
-
notify(ctx, `Too many arguments.\n${READ_EXPANSION_USAGE}`, "warning");
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
if (tokens[0] !== "read-expansion" && tokens[0] !== "read") {
|
|
609
|
-
notify(ctx, READ_EXPANSION_USAGE);
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const sub = tokens[1] ?? "interactive";
|
|
614
|
-
let scope: ConfigScope = "project";
|
|
615
|
-
const scopeToken = tokens[2];
|
|
616
|
-
if (scopeToken !== undefined) {
|
|
617
|
-
if (!isReadExpansionScope(scopeToken)) {
|
|
618
|
-
notify(ctx, `Unknown scope "${scopeToken}".\n${READ_EXPANSION_USAGE}`, "warning");
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
scope = scopeToken;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (sub === "status") {
|
|
625
|
-
notify(ctx, readExpansionStatusText(ctx.cwd));
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
if (isReadExpansionMode(sub)) {
|
|
629
|
-
notify(ctx, applyReadExpansionMode(sub, scope, ctx.cwd));
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (sub !== "interactive") {
|
|
633
|
-
notify(ctx, `Unknown option "${sub}".\n${READ_EXPANSION_USAGE}`, "warning");
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
if (!ctx.hasUI) {
|
|
637
|
-
notify(ctx, readExpansionStatusText(ctx.cwd));
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
await pickReadExpansionInteractively(ctx);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* WHY the overlay owns its scope switch: headed hosts pick the write scope inside
|
|
645
|
-
* the dialog, so the command takes no scope argument; headless hosts read the
|
|
646
|
-
* same combined status text the other routes print.
|
|
647
|
-
*/
|
|
648
|
-
async function handleSettings(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
649
|
-
const tokens = args
|
|
650
|
-
.trim()
|
|
651
|
-
.split(/\s+/)
|
|
652
|
-
.filter((token) => token.length > 0);
|
|
653
|
-
if (tokens.length > 1) {
|
|
654
|
-
notify(ctx, `Too many arguments.\n${SETTINGS_USAGE}`, "warning");
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (!ctx.hasUI) {
|
|
658
|
-
notify(ctx, statusText(ctx.cwd));
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
await ctx.ui.custom((_tui, _theme, _keybindings, done) => new SettingsOverlay(ctx.cwd, done), {
|
|
662
|
-
overlay: true,
|
|
663
|
-
overlayOptions: { width: 68 },
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Register `/pi-better-edit`. Registration is the extension's only side effect here; every
|
|
668
|
-
* handler path reports through notify/console and never rejects into the host.
|
|
669
|
-
*/
|
|
670
|
-
export function registerLensCommand(pi: ExtensionAPI): void {
|
|
671
|
-
pi.registerCommand(COMMAND_NAME, {
|
|
672
|
-
description:
|
|
673
|
-
"Configure pi-better-edit. Usage: /pi-better-edit lens [auto|on|off|status] [project|global]",
|
|
674
|
-
getArgumentCompletions: (prefix: string): AutocompleteItem[] =>
|
|
675
|
-
COMMAND_COMPLETIONS.filter((candidate) => candidate.startsWith(prefix.trim())).map(
|
|
676
|
-
(candidate) => ({
|
|
677
|
-
label: candidate,
|
|
678
|
-
value: candidate,
|
|
679
|
-
}),
|
|
680
|
-
),
|
|
681
|
-
handler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {
|
|
682
|
-
try {
|
|
683
|
-
const head = args
|
|
684
|
-
.trim()
|
|
685
|
-
.split(/\s+/)
|
|
686
|
-
.filter((token) => token.length > 0)[0];
|
|
687
|
-
if (head === "bash-view" || head === "bash") {
|
|
688
|
-
await handleBashView(args, ctx);
|
|
689
|
-
} else if (head === "read-expansion" || head === "read") {
|
|
690
|
-
await handleReadExpansion(args, ctx);
|
|
691
|
-
} else if (head === undefined || head === "settings") {
|
|
692
|
-
await handleSettings(args, ctx);
|
|
693
|
-
} else {
|
|
694
|
-
await handleLens(args, ctx);
|
|
695
|
-
}
|
|
696
|
-
} catch (error: unknown) {
|
|
697
|
-
// SAFETY: a command handler must never reject into the host's event loop — the failure is
|
|
698
|
-
// SAFETY: reported to the user and the command ends.
|
|
699
|
-
if (error instanceof DomainError) {
|
|
700
|
-
// WHY: a DomainError's `message` is the `[AUDIENCE] [CODE] …` envelope built for the model
|
|
701
|
-
// WHY: and host channels, not a sentence for the operator, so the envelope goes to the log
|
|
702
|
-
// WHY: (where a diagnosis can still reach it) and the user gets a plain failure notice. A
|
|
703
|
-
// WHY: failure that is not a DomainError keeps its own text: that part is user-actionable.
|
|
704
|
-
reportDiagnostic("lens-command", `${COMMAND_NAME}: ${errorText(error)}`, error);
|
|
705
|
-
notify(ctx, `${COMMAND_NAME}: the command failed unexpectedly.`, "error");
|
|
706
|
-
} else {
|
|
707
|
-
notify(ctx, `${COMMAND_NAME}: ${errorText(error)}`, "error");
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
},
|
|
711
|
-
});
|
|
712
|
-
}
|