pi-ui-extend 0.1.34 → 0.1.36
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 +20 -0
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +12 -2
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +32 -0
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-session-actions.js +2 -0
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +4 -0
- package/dist/app/input/input-controller.d.ts +5 -1
- package/dist/app/input/input-controller.js +122 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +21 -0
- package/dist/app/input/terminal-edit-shortcuts.js +92 -16
- package/dist/app/popup/popup-action-controller.d.ts +1 -0
- package/dist/app/popup/popup-action-controller.js +1 -0
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +7 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +4 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +1 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +5 -5
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -160,6 +160,26 @@ Before committing code changes, run:
|
|
|
160
160
|
npm run check
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
### Keeping the bundled extension's SDK pin in sync
|
|
164
|
+
|
|
165
|
+
Pix bundles the `pi-tools-suite` extension (in `external/pi-tools-suite`), which
|
|
166
|
+
runs inside the Pi host process. Its `@earendil-works/*` peerDependencies must
|
|
167
|
+
match the host Pi SDK version exactly, or npm can resolve a stale copy in the
|
|
168
|
+
suite's own `node_modules` and cause a double-load (e.g. `0.75.4` in the suite
|
|
169
|
+
vs `0.79.4` in the host).
|
|
170
|
+
|
|
171
|
+
When you bump the Pi SDK in the root `package.json`, re-pin the suite and
|
|
172
|
+
reinstall it:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run sync:sdk-pin # rewrite suite peerDeps to host version
|
|
176
|
+
cd external/pi-tools-suite && npm install --ignore-scripts # update suite lockfile/node_modules
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`npm run check` runs `npm run sync:sdk-pin:check` first, so a stale pin fails
|
|
180
|
+
the check fast. Use `npm run sync:sdk-pin:check` on its own for a drift-only
|
|
181
|
+
report (non-zero exit on drift).
|
|
182
|
+
|
|
163
183
|
## Configuration
|
|
164
184
|
|
|
165
185
|
Useful environment variables:
|
package/dist/app/app.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare class PiUiExtendApp {
|
|
|
66
66
|
start(): Promise<void>;
|
|
67
67
|
private checkPixUpdateOnStartup;
|
|
68
68
|
private bindCurrentSession;
|
|
69
|
+
private awaitCurrentSessionExtensions;
|
|
69
70
|
private activateRuntime;
|
|
70
71
|
private createExtensionEventBus;
|
|
71
72
|
private handleTerminalBellAttention;
|
package/dist/app/app.js
CHANGED
|
@@ -166,12 +166,13 @@ export class PiUiExtendApp {
|
|
|
166
166
|
maxProjectSessions: () => this.pixConfig.maxProjectSessions,
|
|
167
167
|
blinkController: this.blinkController,
|
|
168
168
|
runtime: () => this.runtime,
|
|
169
|
-
createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options)),
|
|
169
|
+
createRuntimeForNewSession: () => this.createRuntime(newTabRuntimeOptions(this.options), this.runtime === undefined ? {} : { reuseServicesFrom: this.runtime }),
|
|
170
170
|
createRuntimeForSession: (sessionPath) => this.createRuntime({
|
|
171
171
|
...this.options,
|
|
172
172
|
noSession: false,
|
|
173
173
|
sessionPath,
|
|
174
174
|
}),
|
|
175
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
175
176
|
activateRuntime: (runtime, options) => this.activateRuntime(runtime, options),
|
|
176
177
|
disposeRuntime: (runtime) => this.terminalController.disposeRuntime(runtime),
|
|
177
178
|
isRunning: () => this.running,
|
|
@@ -317,6 +318,7 @@ export class PiUiExtendApp {
|
|
|
317
318
|
isRunning: () => this.running,
|
|
318
319
|
getInput: () => this.input,
|
|
319
320
|
setInput: (value) => this.setInput(value),
|
|
321
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
320
322
|
resetSessionView: () => this.resetSessionView(),
|
|
321
323
|
loadSessionHistory: () => this.loadSessionHistory(),
|
|
322
324
|
afterSessionReplacement: (message) => this.afterSessionReplacement(message),
|
|
@@ -334,12 +336,14 @@ export class PiUiExtendApp {
|
|
|
334
336
|
});
|
|
335
337
|
this.todoWidgetController = new AppTodoWidgetController({
|
|
336
338
|
sessionFile: () => this.runtime?.session.sessionFile,
|
|
339
|
+
sessionId: () => this.runtime?.session.sessionId,
|
|
337
340
|
isRunning: () => this.running,
|
|
338
341
|
render: () => this.scheduleRender(),
|
|
339
342
|
});
|
|
340
343
|
this.workspaceActions = new AppWorkspaceActionsController({
|
|
341
344
|
entries: this.entries,
|
|
342
345
|
runtime: () => this.runtime,
|
|
346
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
343
347
|
findUserEntry: (entryId) => this.findUserEntry(entryId),
|
|
344
348
|
touchEntry: (entry) => this.touchEntry(entry),
|
|
345
349
|
resetSessionView: () => this.resetSessionView(),
|
|
@@ -470,6 +474,7 @@ export class PiUiExtendApp {
|
|
|
470
474
|
showMenu: (items, options) => this.popupMenus.menuController.show(items, options),
|
|
471
475
|
getModelMenuItems: (query) => this.menuItems.getModelMenuItems(query),
|
|
472
476
|
getThinkingMenuItems: (query) => this.menuItems.getThinkingMenuItems(query),
|
|
477
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
473
478
|
modelRef: (model) => this.menuItems.modelRef(model),
|
|
474
479
|
getFavoriteScopedModels: () => this.menuItems.getFavoriteScopedModels(),
|
|
475
480
|
setSessionStatus: (session) => this.setSessionStatus(session),
|
|
@@ -518,6 +523,7 @@ export class PiUiExtendApp {
|
|
|
518
523
|
showToast: (message, kind) => this.showToast(message, kind),
|
|
519
524
|
render: () => this.render(),
|
|
520
525
|
resetSessionView: () => this.resetSessionView(),
|
|
526
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
521
527
|
bindCurrentSession: () => this.bindCurrentSession(),
|
|
522
528
|
loadSessionHistory: () => this.loadSessionHistory(),
|
|
523
529
|
scrollToConversationEntry: (entryId) => this.scrollController.scrollToConversationEntry(entryId),
|
|
@@ -761,10 +767,11 @@ export class PiUiExtendApp {
|
|
|
761
767
|
});
|
|
762
768
|
this.slashCommands = this.commandController.slashCommands;
|
|
763
769
|
}
|
|
764
|
-
createRuntime(options) {
|
|
770
|
+
createRuntime(options, runtimeOptions = {}) {
|
|
765
771
|
return createPixRuntime(options, {
|
|
766
772
|
eventBus: this.createExtensionEventBus(),
|
|
767
773
|
config: this.pixConfig,
|
|
774
|
+
...runtimeOptions,
|
|
768
775
|
});
|
|
769
776
|
}
|
|
770
777
|
async loadStartupConfig() {
|
|
@@ -822,6 +829,9 @@ export class PiUiExtendApp {
|
|
|
822
829
|
async bindCurrentSession(options) {
|
|
823
830
|
await this.sessionLifecycle.bindCurrentSession(options);
|
|
824
831
|
}
|
|
832
|
+
async awaitCurrentSessionExtensions(runtime) {
|
|
833
|
+
await this.sessionLifecycle.awaitCurrentSessionExtensions(runtime);
|
|
834
|
+
}
|
|
825
835
|
async activateRuntime(runtime, options) {
|
|
826
836
|
this.runtime = runtime;
|
|
827
837
|
runtime.setRebindSession(async () => {
|
|
@@ -7,6 +7,7 @@ export type DirectPopupMenu = Exclude<ActivePopupMenu, "slash">;
|
|
|
7
7
|
export type CommandControllerHost = {
|
|
8
8
|
readonly options: AppOptions;
|
|
9
9
|
runtime(): AgentSessionRuntime | undefined;
|
|
10
|
+
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
10
11
|
requestHistory(): AppRequestHistory;
|
|
11
12
|
getInput(): string;
|
|
12
13
|
setInput(value: string): void;
|
|
@@ -14,6 +14,7 @@ export declare class ModelCommandActions {
|
|
|
14
14
|
runModelCommand(model: SessionModel): Promise<void>;
|
|
15
15
|
runThinkingCommand(level: ThinkingLevel): Promise<void>;
|
|
16
16
|
private addPersistentSystemEntry;
|
|
17
|
+
private reloadAfterModelChange;
|
|
17
18
|
private saveDefaultModel;
|
|
18
19
|
private saveDefaultThinking;
|
|
19
20
|
}
|
|
@@ -269,6 +269,17 @@ export class ModelCommandActions {
|
|
|
269
269
|
this.host.render();
|
|
270
270
|
await runtime.session.setModel(model);
|
|
271
271
|
this.host.addEntry({ id: createId("system"), kind: "system", text: `Selected model ${ref}` });
|
|
272
|
+
if (runtime.session.isStreaming) {
|
|
273
|
+
this.host.addEntry({
|
|
274
|
+
id: createId("system"),
|
|
275
|
+
kind: "system",
|
|
276
|
+
text: "Skipped reload because the agent is still running. Run /reload when idle to refresh model-specific tools.",
|
|
277
|
+
});
|
|
278
|
+
this.host.toast.warning("Model changed; reload skipped while the agent is running");
|
|
279
|
+
this.host.setSessionStatus(runtime.session);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
await this.reloadAfterModelChange(runtime.session, ref);
|
|
272
283
|
this.host.setSessionStatus(runtime.session);
|
|
273
284
|
}
|
|
274
285
|
async runThinkingCommand(level) {
|
|
@@ -285,6 +296,27 @@ export class ModelCommandActions {
|
|
|
285
296
|
appendPixSystemDisplayEntry(session, text);
|
|
286
297
|
this.host.addEntry({ id: createId("system"), kind: "system", text });
|
|
287
298
|
}
|
|
299
|
+
async reloadAfterModelChange(session, ref) {
|
|
300
|
+
this.host.setStatus(`reloading resources for ${ref}`);
|
|
301
|
+
this.host.render();
|
|
302
|
+
try {
|
|
303
|
+
await session.reload();
|
|
304
|
+
this.host.addEntry({
|
|
305
|
+
id: createId("system"),
|
|
306
|
+
kind: "system",
|
|
307
|
+
text: `Reloaded resources after model change to ${ref}`,
|
|
308
|
+
});
|
|
309
|
+
this.host.toast.success("Model changed and resources reloaded");
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
this.host.addEntry({
|
|
313
|
+
id: createId("error"),
|
|
314
|
+
kind: "error",
|
|
315
|
+
text: `Model changed to ${ref}, but reload failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
316
|
+
});
|
|
317
|
+
this.host.toast.error("Model changed, but reload failed");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
288
320
|
saveDefaultModel(modelRef) {
|
|
289
321
|
const saved = savePixDefaultModel(modelRef);
|
|
290
322
|
if (!saved)
|
|
@@ -50,6 +50,7 @@ export class NavigationCommandActions {
|
|
|
50
50
|
throw new Error("No user messages to fork from");
|
|
51
51
|
this.host.setStatus("forking session");
|
|
52
52
|
this.host.render();
|
|
53
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
53
54
|
const result = await runtime.fork(entryId);
|
|
54
55
|
if (result.cancelled) {
|
|
55
56
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Fork cancelled." });
|
|
@@ -74,6 +75,7 @@ export class NavigationCommandActions {
|
|
|
74
75
|
}
|
|
75
76
|
this.host.setStatus("cloning session");
|
|
76
77
|
this.host.render();
|
|
78
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
77
79
|
const result = await runtime.fork(leafId, { position: "at" });
|
|
78
80
|
if (result.cancelled) {
|
|
79
81
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Clone cancelled." });
|
|
@@ -228,6 +230,7 @@ export class NavigationCommandActions {
|
|
|
228
230
|
const resolvedSessionPath = resolve(runtime.cwd, sessionPath);
|
|
229
231
|
this.host.setStatus("switching session");
|
|
230
232
|
this.host.render();
|
|
233
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
231
234
|
const result = await runtime.switchSession(resolvedSessionPath);
|
|
232
235
|
if (result.cancelled) {
|
|
233
236
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Resume cancelled." });
|
|
@@ -260,6 +260,7 @@ export class SessionCommandActions {
|
|
|
260
260
|
this.host.setStatus("reloading");
|
|
261
261
|
this.host.render();
|
|
262
262
|
try {
|
|
263
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
263
264
|
await runtime.session.reload();
|
|
264
265
|
this.host.setSessionStatus(runtime.session);
|
|
265
266
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Reloaded keybindings, extensions, skills, prompts, themes" });
|
|
@@ -277,6 +278,7 @@ export class SessionCommandActions {
|
|
|
277
278
|
return;
|
|
278
279
|
this.host.setStatus("starting new session");
|
|
279
280
|
this.host.render();
|
|
281
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
280
282
|
const result = await runtime.newSession();
|
|
281
283
|
if (result.cancelled) {
|
|
282
284
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "New session cancelled." });
|
package/dist/app/constants.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export declare const REQUEST_HISTORY_VERSION = 1;
|
|
|
19
19
|
export declare const REQUEST_HISTORY_MAX_ENTRIES = 200;
|
|
20
20
|
export declare const REQUEST_HISTORY_MAX_BYTES: number;
|
|
21
21
|
export declare const REQUEST_HISTORY_MAX_ENTRY_BYTES: number;
|
|
22
|
-
export declare const ENABLE_TERMINAL_KEY_REPORTING = "\u001B[>7u\u001B[
|
|
22
|
+
export declare const ENABLE_TERMINAL_KEY_REPORTING = "\u001B[>7u\u001B[?u\u001B[c";
|
|
23
|
+
export declare const ENABLE_TERMINAL_MODIFY_OTHER_KEYS = "\u001B[>4;2m";
|
|
23
24
|
export declare const DISABLE_TERMINAL_KEY_REPORTING = "\u001B[<u\u001B[>4;0m";
|
|
24
25
|
export declare const ENABLE_BRACKETED_PASTE = "\u001B[?2004h";
|
|
25
26
|
export declare const DISABLE_BRACKETED_PASTE = "\u001B[?2004l";
|
package/dist/app/constants.js
CHANGED
|
@@ -52,7 +52,12 @@ export const REQUEST_HISTORY_VERSION = 1;
|
|
|
52
52
|
export const REQUEST_HISTORY_MAX_ENTRIES = 200;
|
|
53
53
|
export const REQUEST_HISTORY_MAX_BYTES = 128 * 1024;
|
|
54
54
|
export const REQUEST_HISTORY_MAX_ENTRY_BYTES = 16 * 1024;
|
|
55
|
-
|
|
55
|
+
// Match pi/@earendil-works/pi-tui keyboard setup: request Kitty keyboard
|
|
56
|
+
// protocol flags, query the terminal response, and use xterm modifyOtherKeys
|
|
57
|
+
// only as a response-driven fallback. Enabling both protocols blindly can make
|
|
58
|
+
// terminals disagree about modified Enter reporting.
|
|
59
|
+
export const ENABLE_TERMINAL_KEY_REPORTING = "\x1b[>7u\x1b[?u\x1b[c";
|
|
60
|
+
export const ENABLE_TERMINAL_MODIFY_OTHER_KEYS = "\x1b[>4;2m";
|
|
56
61
|
export const DISABLE_TERMINAL_KEY_REPORTING = "\x1b[<u\x1b[>4;0m";
|
|
57
62
|
export const ENABLE_BRACKETED_PASTE = "\x1b[?2004h";
|
|
58
63
|
export const DISABLE_BRACKETED_PASTE = "\x1b[?2004l";
|
|
@@ -4,6 +4,7 @@ export type AppExtensionActionsHost = {
|
|
|
4
4
|
isRunning(): boolean;
|
|
5
5
|
getInput(): string;
|
|
6
6
|
setInput(value: string): void;
|
|
7
|
+
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
7
8
|
resetSessionView(): void;
|
|
8
9
|
loadSessionHistory(): void;
|
|
9
10
|
afterSessionReplacement(message?: string): void;
|
|
@@ -8,12 +8,14 @@ export class AppExtensionActionsController {
|
|
|
8
8
|
return {
|
|
9
9
|
waitForIdle: () => this.waitForSessionIdle(runtime),
|
|
10
10
|
newSession: async (options) => {
|
|
11
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
11
12
|
const result = await runtime.newSession(options);
|
|
12
13
|
if (!result.cancelled)
|
|
13
14
|
this.host.afterSessionReplacement("Started a new session.");
|
|
14
15
|
return result;
|
|
15
16
|
},
|
|
16
17
|
fork: async (entryId, options) => {
|
|
18
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
17
19
|
const result = await runtime.fork(entryId, options);
|
|
18
20
|
if (!result.cancelled)
|
|
19
21
|
this.host.afterSessionReplacement("Forked to a new session.");
|
|
@@ -32,12 +34,14 @@ export class AppExtensionActionsController {
|
|
|
32
34
|
return result;
|
|
33
35
|
},
|
|
34
36
|
switchSession: async (sessionPath, options) => {
|
|
37
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
35
38
|
const result = await runtime.switchSession(sessionPath, options);
|
|
36
39
|
if (!result.cancelled)
|
|
37
40
|
this.host.afterSessionReplacement(`Switched session: ${sessionPath}`);
|
|
38
41
|
return result;
|
|
39
42
|
},
|
|
40
43
|
reload: async () => {
|
|
44
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
41
45
|
await runtime.session.reload();
|
|
42
46
|
this.host.setSessionStatus(runtime.session);
|
|
43
47
|
this.host.showToast("Reloaded resources", "success");
|
|
@@ -35,7 +35,7 @@ export declare class AppInputController {
|
|
|
35
35
|
private readonly pasteHandler;
|
|
36
36
|
constructor(host: InputControllerHost);
|
|
37
37
|
handleChunk(chunk: Buffer): void;
|
|
38
|
-
private
|
|
38
|
+
private consumeSharedEditorInput;
|
|
39
39
|
private drainInputBuffer;
|
|
40
40
|
private consumeBracketedPastePayload;
|
|
41
41
|
private getEscapeSequences;
|
|
@@ -45,6 +45,10 @@ export declare class AppInputController {
|
|
|
45
45
|
private consumeCommandArrowPageSequence;
|
|
46
46
|
private consumeCommandArrowPageMatch;
|
|
47
47
|
private consumeTerminalEditShortcutSequence;
|
|
48
|
+
private consumeIgnoredModifiedKeySequence;
|
|
49
|
+
private consumeModifiedArrowKeySequence;
|
|
50
|
+
private consumeClipboardImagePasteSequence;
|
|
51
|
+
private consumeShiftEnterSequence;
|
|
48
52
|
private consumeTerminalInterruptSequence;
|
|
49
53
|
private handleArrowUp;
|
|
50
54
|
private handleArrowDown;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InputPasteHandler } from "./input-paste-handler.js";
|
|
2
2
|
import { hasTerminalCommandModifier, isNativeCommandPressed, isNativeShiftPressed } from "./native-modifiers.js";
|
|
3
|
-
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, terminalEditShortcutForControlChar } from "./terminal-edit-shortcuts.js";
|
|
3
|
+
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, parseTerminalModifiedKeySequence, terminalKeyArrowDirection, terminalEditShortcutForControlChar, terminalKeyIsClipboardImagePaste, terminalKeyIsShiftEnter, terminalKeyShouldIgnore, } from "./terminal-edit-shortcuts.js";
|
|
4
|
+
const SHIFT_ENTER_ESCAPE_SEQUENCES = ["\x1b\r", "\x1b\n"];
|
|
4
5
|
export class AppInputController {
|
|
5
6
|
host;
|
|
6
7
|
inputBuffer = "";
|
|
@@ -16,7 +17,7 @@ export class AppInputController {
|
|
|
16
17
|
this.drainInputBuffer();
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
if (this.
|
|
20
|
+
if (this.consumeSharedEditorInput(data))
|
|
20
21
|
return;
|
|
21
22
|
const extensionInput = this.host.handleExtensionTerminalInput(data);
|
|
22
23
|
if (extensionInput.consume)
|
|
@@ -28,17 +29,41 @@ export class AppInputController {
|
|
|
28
29
|
this.inputBuffer += data;
|
|
29
30
|
this.drainInputBuffer();
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
consumeSharedEditorInput(data) {
|
|
32
33
|
if (this.host.extensionInputUsesEditor?.() !== true)
|
|
33
34
|
return false;
|
|
34
35
|
if (this.host.inputEditor.isInBracketedPaste)
|
|
35
36
|
return false;
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
if (data === "\n") {
|
|
38
|
+
this.insertInputNewline();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (data === "\r" && this.isShiftPressed()) {
|
|
42
|
+
this.insertInputNewline();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (SHIFT_ENTER_ESCAPE_SEQUENCES.includes(data)) {
|
|
46
|
+
this.insertInputNewline();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (data === "\x16") {
|
|
50
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const modifiedKey = parseTerminalModifiedKeySequence(data);
|
|
54
|
+
if (modifiedKey.kind !== "key")
|
|
39
55
|
return false;
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
if (terminalKeyShouldIgnore(modifiedKey.key))
|
|
57
|
+
return true;
|
|
58
|
+
if (terminalKeyIsShiftEnter(modifiedKey.key)) {
|
|
59
|
+
this.insertInputNewline();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (terminalKeyIsClipboardImagePaste(modifiedKey.key)) {
|
|
63
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
42
67
|
}
|
|
43
68
|
drainInputBuffer() {
|
|
44
69
|
while (this.inputBuffer.length > 0) {
|
|
@@ -72,11 +97,31 @@ export class AppInputController {
|
|
|
72
97
|
continue;
|
|
73
98
|
if (terminalInterruptSequence === "pending")
|
|
74
99
|
return;
|
|
100
|
+
const shiftEnterSequence = this.consumeShiftEnterSequence();
|
|
101
|
+
if (shiftEnterSequence === "consumed")
|
|
102
|
+
continue;
|
|
103
|
+
if (shiftEnterSequence === "pending")
|
|
104
|
+
return;
|
|
105
|
+
const clipboardImagePasteSequence = this.consumeClipboardImagePasteSequence();
|
|
106
|
+
if (clipboardImagePasteSequence === "consumed")
|
|
107
|
+
continue;
|
|
108
|
+
if (clipboardImagePasteSequence === "pending")
|
|
109
|
+
return;
|
|
75
110
|
const terminalEditShortcutSequence = this.consumeTerminalEditShortcutSequence();
|
|
76
111
|
if (terminalEditShortcutSequence === "consumed")
|
|
77
112
|
continue;
|
|
78
113
|
if (terminalEditShortcutSequence === "pending")
|
|
79
114
|
return;
|
|
115
|
+
const modifiedArrowKeySequence = this.consumeModifiedArrowKeySequence();
|
|
116
|
+
if (modifiedArrowKeySequence === "consumed")
|
|
117
|
+
continue;
|
|
118
|
+
if (modifiedArrowKeySequence === "pending")
|
|
119
|
+
return;
|
|
120
|
+
const ignoredModifiedKeySequence = this.consumeIgnoredModifiedKeySequence();
|
|
121
|
+
if (ignoredModifiedKeySequence === "consumed")
|
|
122
|
+
continue;
|
|
123
|
+
if (ignoredModifiedKeySequence === "pending")
|
|
124
|
+
return;
|
|
80
125
|
if (this.consumeEscapeSequence())
|
|
81
126
|
continue;
|
|
82
127
|
if (this.isPendingEscapeSequence())
|
|
@@ -106,13 +151,9 @@ export class AppInputController {
|
|
|
106
151
|
}
|
|
107
152
|
getEscapeSequences() {
|
|
108
153
|
return [
|
|
109
|
-
[
|
|
110
|
-
["\x1b[13;2~", () => this.insertInputNewline()],
|
|
111
|
-
["\x1b[27;2;13~", () => this.insertInputNewline()],
|
|
154
|
+
...SHIFT_ENTER_ESCAPE_SEQUENCES.map((sequence) => [sequence, () => this.insertInputNewline()]),
|
|
112
155
|
["\x1b[13u", () => this.host.handleEnter()],
|
|
113
156
|
["\x1b[13;1u", () => this.host.handleEnter()],
|
|
114
|
-
["\x1b\r", () => this.insertInputNewline()],
|
|
115
|
-
["\x1b\n", () => this.insertInputNewline()],
|
|
116
157
|
["\x1b[5~", () => this.host.scrollByPage(-1)],
|
|
117
158
|
["\x1b[6~", () => this.host.scrollByPage(1)],
|
|
118
159
|
["\x1b[A", () => this.handleArrowUp()],
|
|
@@ -139,8 +180,6 @@ export class AppInputController {
|
|
|
139
180
|
["\x1b[201~", () => this.pasteHandler.endBracketedPaste()],
|
|
140
181
|
["\x1b[1;2H", () => { this.host.inputEditor.moveToLineStartExtend(); this.host.render(); }],
|
|
141
182
|
["\x1b[1;2F", () => { this.host.inputEditor.moveToLineEndExtend(); this.host.render(); }],
|
|
142
|
-
["\x1b[118;5u", () => { void this.pasteHandler.handleClipboardImagePaste(); }],
|
|
143
|
-
["\x1b[27;5;118~", () => { void this.pasteHandler.handleClipboardImagePaste(); }],
|
|
144
183
|
["\x1b[122;9u", () => this.undoInput()],
|
|
145
184
|
["\x1b[27;9;122~", () => this.undoInput()],
|
|
146
185
|
["\x1b[90;10u", () => this.redoInput()],
|
|
@@ -232,6 +271,65 @@ export class AppInputController {
|
|
|
232
271
|
}
|
|
233
272
|
return "consumed";
|
|
234
273
|
}
|
|
274
|
+
consumeIgnoredModifiedKeySequence() {
|
|
275
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
276
|
+
if (result.kind === "pending")
|
|
277
|
+
return "pending";
|
|
278
|
+
if (result.kind === "none")
|
|
279
|
+
return "none";
|
|
280
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
281
|
+
return "none";
|
|
282
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
283
|
+
return "consumed";
|
|
284
|
+
}
|
|
285
|
+
consumeModifiedArrowKeySequence() {
|
|
286
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
287
|
+
if (result.kind === "pending")
|
|
288
|
+
return "pending";
|
|
289
|
+
if (result.kind === "none")
|
|
290
|
+
return "none";
|
|
291
|
+
const direction = terminalKeyArrowDirection(result.key);
|
|
292
|
+
if (!direction)
|
|
293
|
+
return "none";
|
|
294
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
295
|
+
if (terminalKeyShouldIgnore(result.key))
|
|
296
|
+
return "consumed";
|
|
297
|
+
if (direction === "up")
|
|
298
|
+
this.handleArrowUp();
|
|
299
|
+
else if (direction === "down")
|
|
300
|
+
this.handleArrowDown();
|
|
301
|
+
else if (direction === "right")
|
|
302
|
+
this.handleArrowRight();
|
|
303
|
+
else
|
|
304
|
+
this.handleArrowLeft();
|
|
305
|
+
return "consumed";
|
|
306
|
+
}
|
|
307
|
+
consumeClipboardImagePasteSequence() {
|
|
308
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
309
|
+
if (result.kind === "pending")
|
|
310
|
+
return "pending";
|
|
311
|
+
if (result.kind === "none")
|
|
312
|
+
return "none";
|
|
313
|
+
if (!terminalKeyIsClipboardImagePaste(result.key))
|
|
314
|
+
return "none";
|
|
315
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
316
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
317
|
+
void this.pasteHandler.handleClipboardImagePaste();
|
|
318
|
+
return "consumed";
|
|
319
|
+
}
|
|
320
|
+
consumeShiftEnterSequence() {
|
|
321
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
322
|
+
if (result.kind === "pending")
|
|
323
|
+
return "pending";
|
|
324
|
+
if (result.kind === "none")
|
|
325
|
+
return "none";
|
|
326
|
+
if (!terminalKeyIsShiftEnter(result.key))
|
|
327
|
+
return "none";
|
|
328
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
329
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
330
|
+
this.insertInputNewline();
|
|
331
|
+
return "consumed";
|
|
332
|
+
}
|
|
235
333
|
consumeTerminalInterruptSequence() {
|
|
236
334
|
const result = parseTerminalInterruptSequence(this.inputBuffer);
|
|
237
335
|
if (result.kind === "pending")
|
|
@@ -401,7 +499,15 @@ export class AppInputController {
|
|
|
401
499
|
this.host.toggleVoiceRecording();
|
|
402
500
|
return;
|
|
403
501
|
}
|
|
404
|
-
if (char === "\
|
|
502
|
+
if (char === "\n") {
|
|
503
|
+
if (this.host.inputEditor.isInBracketedPaste) {
|
|
504
|
+
this.pasteHandler.appendBracketedPasteText("\n");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
this.insertInputNewline();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (char === "\r") {
|
|
405
511
|
if (this.host.inputEditor.isInBracketedPaste) {
|
|
406
512
|
this.pasteHandler.appendBracketedPasteText("\n");
|
|
407
513
|
return;
|
|
@@ -92,8 +92,10 @@ export class InputPasteHandler {
|
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
94
|
handlePasteEnd(text) {
|
|
95
|
-
if (!text)
|
|
95
|
+
if (!text) {
|
|
96
|
+
void this.handleClipboardImagePaste();
|
|
96
97
|
return;
|
|
98
|
+
}
|
|
97
99
|
const filePath = this.plainPasteFilePath(text);
|
|
98
100
|
if (filePath) {
|
|
99
101
|
if (isImagePath(filePath) && Date.now() < this.suppressImagePathPasteUntil) {
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export type TerminalEditShortcut = "undo" | "redo";
|
|
2
|
+
export type ParsedTerminalModifiedKeyResult = {
|
|
3
|
+
readonly kind: "key";
|
|
4
|
+
readonly key: ParsedModifiedKey;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "pending";
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "none";
|
|
9
|
+
};
|
|
2
10
|
export type TerminalEditShortcutSequenceResult = {
|
|
3
11
|
readonly kind: "shortcut";
|
|
4
12
|
readonly shortcut: TerminalEditShortcut;
|
|
@@ -11,6 +19,14 @@ export type TerminalEditShortcutSequenceResult = {
|
|
|
11
19
|
} | {
|
|
12
20
|
readonly kind: "none";
|
|
13
21
|
};
|
|
22
|
+
interface ParsedModifiedKey {
|
|
23
|
+
readonly codepoint: number;
|
|
24
|
+
readonly baseLayoutKey: number | undefined;
|
|
25
|
+
readonly modifier: number;
|
|
26
|
+
readonly eventType: number | undefined;
|
|
27
|
+
readonly length: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function parseTerminalModifiedKeySequence(input: string): ParsedTerminalModifiedKeyResult;
|
|
14
30
|
export declare function parseTerminalEditShortcutSequence(input: string): TerminalEditShortcutSequenceResult;
|
|
15
31
|
export declare function parseTerminalInterruptSequence(input: string): {
|
|
16
32
|
readonly kind: "interrupt";
|
|
@@ -20,4 +36,9 @@ export declare function parseTerminalInterruptSequence(input: string): {
|
|
|
20
36
|
} | {
|
|
21
37
|
readonly kind: "none";
|
|
22
38
|
};
|
|
39
|
+
export declare function terminalKeyIsShiftEnter(key: ParsedModifiedKey): boolean;
|
|
40
|
+
export declare function terminalKeyIsClipboardImagePaste(key: ParsedModifiedKey): boolean;
|
|
41
|
+
export declare function terminalKeyShouldIgnore(key: ParsedModifiedKey): boolean;
|
|
42
|
+
export declare function terminalKeyArrowDirection(key: ParsedModifiedKey): "up" | "down" | "right" | "left" | undefined;
|
|
23
43
|
export declare function terminalEditShortcutForControlChar(char: string, shiftPressed: boolean): TerminalEditShortcut | undefined;
|
|
44
|
+
export {};
|