@agi-fans/dsh-tui 0.1.1 → 0.2.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 -2
- package/lib/autocomplete.js +0 -2
- package/lib/autocomplete.js.map +1 -1
- package/lib/{command-queue.d.ts → command-export.d.ts} +2 -2
- package/lib/command-export.js +39 -0
- package/lib/command-export.js.map +1 -0
- package/lib/{command-transcript.d.ts → command-steer.d.ts} +2 -2
- package/lib/command-steer.js +18 -0
- package/lib/command-steer.js.map +1 -0
- package/lib/definition.d.ts +14 -0
- package/lib/event-views.d.ts +22 -8
- package/lib/event-views.js +204 -45
- package/lib/event-views.js.map +1 -1
- package/lib/hotkeys.d.ts +2 -5
- package/lib/hotkeys.js +4 -14
- package/lib/hotkeys.js.map +1 -1
- package/lib/provider-local.d.ts +4 -1
- package/lib/provider-local.js +170 -40
- package/lib/provider-local.js.map +1 -1
- package/lib/renderer.js +11 -1
- package/lib/renderer.js.map +1 -1
- package/lib/runner.js +28 -0
- package/lib/runner.js.map +1 -1
- package/lib/session-controller.d.ts +27 -3
- package/lib/session-controller.js +199 -6
- package/lib/session-controller.js.map +1 -1
- package/lib/welcome-tips.js +1 -1
- package/lib/welcome-tips.js.map +1 -1
- package/package.json +7 -7
- package/lib/command-queue.js +0 -62
- package/lib/command-queue.js.map +0 -1
- package/lib/command-transcript.js +0 -85
- package/lib/command-transcript.js.map +0 -1
- package/lib/pwd.d.ts +0 -13
- package/lib/pwd.js +0 -14
- package/lib/pwd.js.map +0 -1
package/lib/hotkeys.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Keyboard-shortcut catalog
|
|
2
|
+
* Keyboard-shortcut catalog embedded in `/help`.
|
|
3
3
|
* @module @agi-fans/dsh-tui
|
|
4
4
|
*/
|
|
5
5
|
import { DEFAULT_KEYBINDINGS } from "./keybindings-config.js";
|
|
6
|
-
import { renderMarkdown } from "./markdown.js";
|
|
7
6
|
function displayKey(key) {
|
|
8
7
|
return key.split('+').map((part) => {
|
|
9
8
|
const normalized = part.toLowerCase();
|
|
@@ -71,6 +70,7 @@ function sections(bindings) {
|
|
|
71
70
|
{
|
|
72
71
|
title: 'Session',
|
|
73
72
|
rows: [
|
|
73
|
+
{ keys: 'Esc twice', action: 'Rewind to an earlier conversation turn' },
|
|
74
74
|
{ keys: 'Ctrl+C twice', action: 'Interrupt or clear, then exit' },
|
|
75
75
|
{ keys: 'Ctrl+Z', action: 'Suspend to the background' },
|
|
76
76
|
{ keys: 'Alt+L', action: 'Reset the terminal display' },
|
|
@@ -80,7 +80,7 @@ function sections(bindings) {
|
|
|
80
80
|
{ keys: '@ / ./ / ~/', action: 'Complete file paths' },
|
|
81
81
|
{ keys: 'Tab', action: 'Accept command or path completion' },
|
|
82
82
|
{ keys: '/copy', action: 'Open the copy picker' },
|
|
83
|
-
{ keys: '/
|
|
83
|
+
{ keys: '/help', action: 'Show commands and keyboard shortcuts' },
|
|
84
84
|
],
|
|
85
85
|
},
|
|
86
86
|
];
|
|
@@ -92,7 +92,7 @@ function tableCell(value) {
|
|
|
92
92
|
export function hotkeyCount(bindings = DEFAULT_KEYBINDINGS) {
|
|
93
93
|
return sections(bindings).reduce((total, section) => total + section.rows.length, 0);
|
|
94
94
|
}
|
|
95
|
-
/** Markdown tables
|
|
95
|
+
/** Markdown tables embedded in the `/help` transcript panel. */
|
|
96
96
|
export function formatHotkeysText(bindings = DEFAULT_KEYBINDINGS) {
|
|
97
97
|
return sections(bindings).flatMap((section, index) => [
|
|
98
98
|
...(index === 0 ? [] : ['']),
|
|
@@ -102,14 +102,4 @@ export function formatHotkeysText(bindings = DEFAULT_KEYBINDINGS) {
|
|
|
102
102
|
...section.rows.map(row => `| \`${tableCell(row.keys)}\` | ${tableCell(row.action)} |`),
|
|
103
103
|
]).join('\n');
|
|
104
104
|
}
|
|
105
|
-
/** OMP-style command heading followed by grouped native Markdown tables. */
|
|
106
|
-
export function renderHotkeysPanel(bindings, theme, width) {
|
|
107
|
-
const inset = width > 2 ? 1 : 0;
|
|
108
|
-
const contentWidth = Math.max(1, width - inset * 2);
|
|
109
|
-
const prefix = ' '.repeat(inset);
|
|
110
|
-
const title = theme.bold(theme.fg('accent', 'Keyboard Shortcuts'))
|
|
111
|
-
+ theme.fg('muted', ` · ${hotkeyCount(bindings)} bindings`);
|
|
112
|
-
const body = renderMarkdown(formatHotkeysText(bindings), theme, contentWidth);
|
|
113
|
-
return [prefix + title, '', ...body.map(line => prefix + line)];
|
|
114
|
-
}
|
|
115
105
|
//# sourceMappingURL=hotkeys.js.map
|
package/lib/hotkeys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hotkeys.js","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAkB,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"hotkeys.js","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAkB,MAAM,yBAAyB,CAAA;AAe7E,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QACrC,IAAI,UAAU,KAAK,MAAM;YAAE,OAAO,MAAM,CAAA;QACxC,IAAI,UAAU,KAAK,KAAK;YAAE,OAAO,KAAK,CAAA;QACtC,IAAI,UAAU,KAAK,OAAO;YAAE,OAAO,OAAO,CAAA;QAC1C,IAAI,UAAU,KAAK,OAAO;YAAE,OAAO,OAAO,CAAA;QAC1C,IAAI,UAAU,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAA;QACzC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACxF,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAED,SAAS,aAAa,CAAC,QAAwB,EAAE,MAAiB;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,MAAM,CAAC;SACvC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;IAClC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;AACxD,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB;IACxC,OAAO;QACL;YACE,KAAK,EAAE,YAAY;YACnB,IAAI,EAAE;gBACJ,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,2DAA2D,EAAE;gBAC3F,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBAClE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,6BAA6B,EAAE;gBAC/D,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,oBAAoB,EAAE;gBAC1D,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,qBAAqB,EAAE;gBAC5D,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,6BAA6B,EAAE;gBACxE,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,8BAA8B,EAAE;aAC9E;SACF;QACD;YACE,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE;gBACJ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE;gBAC7C,EAAE,IAAI,EAAE,kCAAkC,EAAE,MAAM,EAAE,mBAAmB,EAAE;gBACzE,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,EAAE,0BAA0B,EAAE;gBACtE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE;gBACjD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,iCAAiC,EAAE;gBAC7D,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBAC3D,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE;gBAC/C,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE;gBAChD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE;gBAChD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,gDAAgD,EAAE;gBAC5E,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,8BAA8B,EAAE;gBAC5F,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,yBAAyB,EAAE;gBACnF,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,uBAAuB,EAAE;gBAC/E,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,uCAAuC,EAAE;aACtG;SACF;QACD;YACE,KAAK,EAAE,YAAY;YACnB,IAAI,EAAE;gBACJ,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE;gBAClD,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,gBAAgB,EAAE;gBAC3D,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,uBAAuB,EAAE;gBACxD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,4CAA4C,EAAE;aACzE;SACF;QACD;YACE,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE;gBACJ,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,wCAAwC,EAAE;gBACvE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBACjE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,2BAA2B,EAAE;gBACvD,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,4BAA4B,EAAE;gBACvD,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,uBAAuB,EAAE;gBACnD,EAAE,IAAI,EAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBACnF,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBACtD,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,qBAAqB,EAAE;gBACtD,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,mCAAmC,EAAE;gBAC5D,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE;gBACjD,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,sCAAsC,EAAE;aAClE;SACF;KACF,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AAClE,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,WAAW,CAAC,WAA2B,mBAAmB;IACxE,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;AACtF,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,iBAAiB,CAAC,WAA2B,mBAAmB;IAC9E,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;QACpD,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,KAAK,OAAO,CAAC,KAAK,IAAI;QACtB,uBAAuB;QACvB,WAAW;QACX,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;KACxF,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
|
package/lib/provider-local.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns the tty: raw-mode key input (editing, history, slash/tab
|
|
5
5
|
* autocomplete, /settings overlay, /copy picker, Ctrl-R history search, PgUp/PgDn
|
|
6
6
|
* and mouse-wheel transcript scroll, click-to-caret, Ctrl-O tool
|
|
7
|
-
* expand, bracketed paste, double Ctrl-C exit, Ctrl-D quit),
|
|
7
|
+
* expand, bracketed paste, double-Escape conversation rewind, double Ctrl-C exit, Ctrl-D quit),
|
|
8
8
|
* SIGWINCH reflow, and the differential renderer. In non-tty mode
|
|
9
9
|
* (pipes, CI) it degrades to line-based input with plain append-only
|
|
10
10
|
* printing of settled blocks.
|
|
@@ -102,7 +102,10 @@ export declare class LocalTui implements TuiService {
|
|
|
102
102
|
/** Compatibility helper for provider-level text editing tests and embedders. */
|
|
103
103
|
readline(): Promise<string | null>;
|
|
104
104
|
restoreInput(submission: TuiSubmission): void;
|
|
105
|
+
resolveQueueEdit(submission: TuiSubmission | null): void;
|
|
105
106
|
onInterrupt(listener: () => void): () => void;
|
|
107
|
+
onQueueEdit(listener: () => void): () => void;
|
|
108
|
+
onRewind(listener: () => void): () => void;
|
|
106
109
|
dispose(): void;
|
|
107
110
|
/** Re-render the current frame (resize reflow). */
|
|
108
111
|
refresh(): void;
|
package/lib/provider-local.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns the tty: raw-mode key input (editing, history, slash/tab
|
|
5
5
|
* autocomplete, /settings overlay, /copy picker, Ctrl-R history search, PgUp/PgDn
|
|
6
6
|
* and mouse-wheel transcript scroll, click-to-caret, Ctrl-O tool
|
|
7
|
-
* expand, bracketed paste, double Ctrl-C exit, Ctrl-D quit),
|
|
7
|
+
* expand, bracketed paste, double-Escape conversation rewind, double Ctrl-C exit, Ctrl-D quit),
|
|
8
8
|
* SIGWINCH reflow, and the differential renderer. In non-tty mode
|
|
9
9
|
* (pipes, CI) it degrades to line-based input with plain append-only
|
|
10
10
|
* printing of settled blocks.
|
|
@@ -22,12 +22,11 @@ import { buildCopyTargets, extractCopyTarget, parseCopyKind } from "./copy-targe
|
|
|
22
22
|
import { applyHistorySearchEvent, createHistorySearch, hitTestHistorySearch, } from "./history-search.js";
|
|
23
23
|
import { InputEditor, lineEnd, lineStart } from "./editor.js";
|
|
24
24
|
import { applySettingsEvent, createSettings, hitTestSettings, selectSetting, tuiSettingItems, } from "./settings-list.js";
|
|
25
|
-
import { applyEvent, blockLines, initialTranscript, renderView, TRANSCRIPT_FAST_SCROLL, TRANSCRIPT_WHEEL_SCROLL, } from "./event-views.js";
|
|
25
|
+
import { applyEvent, blockLines, initialTranscript, replayEvents, renderView, TRANSCRIPT_FAST_SCROLL, TRANSCRIPT_WHEEL_SCROLL, } from "./event-views.js";
|
|
26
26
|
import { flushPending, MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseKeys } from "./keys.js";
|
|
27
27
|
import { LineRenderer } from "./renderer.js";
|
|
28
28
|
import { hitTestEditor } from "./box.js";
|
|
29
29
|
import { createTheme, detectTrueColor, parseThemeName } from "./theme.js";
|
|
30
|
-
import { formatWorkspaceText } from "./pwd.js";
|
|
31
30
|
import { TUI_SETTINGS_NAMESPACE, TuiSettingsSchema } from "./tui-settings.js";
|
|
32
31
|
import { defaultStatusBarConfig, resolveStatusBarConfig } from "./status-config.js";
|
|
33
32
|
import { HistoryStore } from "./history-store.js";
|
|
@@ -37,10 +36,12 @@ import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
|
37
36
|
import { movePromptSelection, filteredPromptOptions, selectedPromptAnswer, selectedFilteredPromptAnswer, togglePromptSelection, } from "./prompt-selector.js";
|
|
38
37
|
import { resolveProjectContext } from "./project-context.js";
|
|
39
38
|
import { pickWelcomeTips } from "./welcome-tips.js";
|
|
39
|
+
import { formatHotkeysText, hotkeyCount } from "./hotkeys.js";
|
|
40
40
|
import { imageMarker, imagePathCandidates, probeImageDimensions, readImageFile, readImageFromClipboard, readMacClipboardFiles, } from "./image-paste.js";
|
|
41
41
|
const APP_NAME = 'omdsh';
|
|
42
42
|
const APP_VERSION = '0.1.0';
|
|
43
43
|
const DOUBLE_CTRL_C_MS = 500;
|
|
44
|
+
const DOUBLE_ESCAPE_MS = 500;
|
|
44
45
|
function shortenPath(cwd) {
|
|
45
46
|
const home = homedir();
|
|
46
47
|
if (cwd === home)
|
|
@@ -73,11 +74,17 @@ export class LocalTui {
|
|
|
73
74
|
#settings = null;
|
|
74
75
|
#copySelector = null;
|
|
75
76
|
#pending = null;
|
|
76
|
-
#
|
|
77
|
+
#queuedSubmissions = [];
|
|
78
|
+
/** Newer queue entries temporarily detached while Up browses backward. */
|
|
79
|
+
#queueEditNewer = null;
|
|
80
|
+
#queueEditPending = false;
|
|
77
81
|
#quitRequested = false;
|
|
78
82
|
#resumeHintRequested = false;
|
|
79
83
|
#lastSigintTime = 0;
|
|
84
|
+
#lastEscapeTime = 0;
|
|
80
85
|
#interrupts = new Set();
|
|
86
|
+
#queueEdits = new Set();
|
|
87
|
+
#rewinds = new Set();
|
|
81
88
|
#disposed = false;
|
|
82
89
|
#pendingKeys = '';
|
|
83
90
|
#escapeTimer = null;
|
|
@@ -202,6 +209,8 @@ export class LocalTui {
|
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
211
|
setStatus(status) {
|
|
212
|
+
if (this.#state.status === 'compacting')
|
|
213
|
+
return;
|
|
205
214
|
this.#state = { ...this.#state, status };
|
|
206
215
|
this.#syncTick();
|
|
207
216
|
if (this.#tty)
|
|
@@ -282,10 +291,8 @@ export class LocalTui {
|
|
|
282
291
|
});
|
|
283
292
|
}
|
|
284
293
|
replaceSession(events, presentations) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
state = applyEvent(state, event, presentations?.get(event.seq));
|
|
288
|
-
this.#state = { ...state, status: 'idle' };
|
|
294
|
+
const state = replayEvents(events, presentations);
|
|
295
|
+
this.#state = { ...state, status: 'idle', compactCommandId: undefined };
|
|
289
296
|
this.#plainPrinted = 0;
|
|
290
297
|
this.#followTail();
|
|
291
298
|
if (this.#tty)
|
|
@@ -337,9 +344,11 @@ export class LocalTui {
|
|
|
337
344
|
return this.#readlinePlain();
|
|
338
345
|
// Lines submitted while a turn was still running were queued instead of
|
|
339
346
|
// dropped; serve the oldest before waiting for fresh input.
|
|
340
|
-
const queued = this.#
|
|
341
|
-
if (queued !== undefined)
|
|
347
|
+
const queued = this.#queuedSubmissions.shift();
|
|
348
|
+
if (queued !== undefined) {
|
|
349
|
+
this.#render();
|
|
342
350
|
return Promise.resolve(queued);
|
|
351
|
+
}
|
|
343
352
|
return new Promise((resolve) => {
|
|
344
353
|
this.#pending = { resolve };
|
|
345
354
|
});
|
|
@@ -363,10 +372,49 @@ export class LocalTui {
|
|
|
363
372
|
if (this.#tty)
|
|
364
373
|
this.#render();
|
|
365
374
|
}
|
|
375
|
+
resolveQueueEdit(submission) {
|
|
376
|
+
if (!this.#queueEditPending)
|
|
377
|
+
return;
|
|
378
|
+
this.#queueEditPending = false;
|
|
379
|
+
if (submission === null) {
|
|
380
|
+
if (this.#editor.text === '' && this.#images.length === 0 && this.#queueEditNewer?.length === 0) {
|
|
381
|
+
this.#queueEditNewer = null;
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (this.#queueEditNewer === null)
|
|
386
|
+
this.#queueEditNewer = [];
|
|
387
|
+
if (this.#editor.text !== '' || this.#images.length > 0) {
|
|
388
|
+
this.#queueEditNewer.unshift(this.#currentSubmission());
|
|
389
|
+
}
|
|
390
|
+
this.#replaceInput(submission);
|
|
391
|
+
}
|
|
392
|
+
#currentSubmission() {
|
|
393
|
+
return {
|
|
394
|
+
text: this.#editor.text,
|
|
395
|
+
images: this.#images.map(image => ({ ...image })),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
#replaceInput(submission) {
|
|
399
|
+
this.#images = submission.images.map(image => ({ ...image }));
|
|
400
|
+
this.#editor.setText(submission.text);
|
|
401
|
+
this.#historyIndex = 0;
|
|
402
|
+
this.#refreshAutocomplete();
|
|
403
|
+
if (this.#tty)
|
|
404
|
+
this.#render();
|
|
405
|
+
}
|
|
366
406
|
onInterrupt(listener) {
|
|
367
407
|
this.#interrupts.add(listener);
|
|
368
408
|
return () => { this.#interrupts.delete(listener); };
|
|
369
409
|
}
|
|
410
|
+
onQueueEdit(listener) {
|
|
411
|
+
this.#queueEdits.add(listener);
|
|
412
|
+
return () => { this.#queueEdits.delete(listener); };
|
|
413
|
+
}
|
|
414
|
+
onRewind(listener) {
|
|
415
|
+
this.#rewinds.add(listener);
|
|
416
|
+
return () => { this.#rewinds.delete(listener); };
|
|
417
|
+
}
|
|
370
418
|
dispose() {
|
|
371
419
|
if (this.#disposed)
|
|
372
420
|
return;
|
|
@@ -471,7 +519,7 @@ export class LocalTui {
|
|
|
471
519
|
this.#term.output.write(out);
|
|
472
520
|
}
|
|
473
521
|
#busy() {
|
|
474
|
-
if (this.#state.status
|
|
522
|
+
if (this.#state.status !== 'idle')
|
|
475
523
|
return true;
|
|
476
524
|
return this.#state.blocks.some((block) => block.kind === 'tool' && block.status === 'running');
|
|
477
525
|
}
|
|
@@ -506,6 +554,9 @@ export class LocalTui {
|
|
|
506
554
|
input: this.#editor.text,
|
|
507
555
|
inputCursor: this.#editor.cursor,
|
|
508
556
|
inputImages: this.#images.length,
|
|
557
|
+
queuedSubmissions: this.#queueEditNewer === null
|
|
558
|
+
? this.#queuedSubmissions
|
|
559
|
+
: [...this.#queuedSubmissions, ...this.#queueEditNewer],
|
|
509
560
|
colors: this.#colors,
|
|
510
561
|
pwd: this.#pwd,
|
|
511
562
|
...(this.#branch !== undefined ? { branch: this.#branch } : {}),
|
|
@@ -761,6 +812,22 @@ export class LocalTui {
|
|
|
761
812
|
this.#editor.setText(text, Math.min(this.#editor.cursor, text.length));
|
|
762
813
|
}
|
|
763
814
|
#dispatch(event) {
|
|
815
|
+
if (this.#state.status === 'compacting') {
|
|
816
|
+
if (event.type === 'mouse' && event.wheel !== null) {
|
|
817
|
+
this.#scrollBy(event.wheel < 0 ? -TRANSCRIPT_WHEEL_SCROLL : TRANSCRIPT_WHEEL_SCROLL, true);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (event.type === 'key' && event.id === 'pageUp') {
|
|
821
|
+
this.#scrollBy(-this.#pageSize());
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (event.type === 'key' && event.id === 'pageDown') {
|
|
825
|
+
this.#scrollBy(this.#pageSize());
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (event.type !== 'key' || event.id !== 'ctrl+c')
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
764
831
|
// Clipboard image inspection is asynchronous. Preserve the exact key
|
|
765
832
|
// order so a fast Ctrl+V, Enter submits the finished image draft rather
|
|
766
833
|
// than an empty prompt.
|
|
@@ -789,9 +856,12 @@ export class LocalTui {
|
|
|
789
856
|
return;
|
|
790
857
|
}
|
|
791
858
|
if (event.type === 'mouse') {
|
|
859
|
+
this.#lastEscapeTime = 0;
|
|
792
860
|
this.#handleMouse(event);
|
|
793
861
|
return;
|
|
794
862
|
}
|
|
863
|
+
if (event.type !== 'key' || event.id !== 'escape')
|
|
864
|
+
this.#lastEscapeTime = 0;
|
|
795
865
|
if (this.#handlePrompt(event))
|
|
796
866
|
return;
|
|
797
867
|
if (event.type === 'key') {
|
|
@@ -830,7 +900,7 @@ export class LocalTui {
|
|
|
830
900
|
return;
|
|
831
901
|
}
|
|
832
902
|
this.#lastSigintTime = now;
|
|
833
|
-
if (this.#state.status
|
|
903
|
+
if (this.#state.status !== 'idle') {
|
|
834
904
|
for (const listener of this.#interrupts)
|
|
835
905
|
listener();
|
|
836
906
|
}
|
|
@@ -863,8 +933,29 @@ export class LocalTui {
|
|
|
863
933
|
this.#applySearch(event);
|
|
864
934
|
return;
|
|
865
935
|
}
|
|
866
|
-
if (this.#handleAutocomplete(event))
|
|
936
|
+
if (this.#handleAutocomplete(event)) {
|
|
937
|
+
if (event.type === 'key' && event.id === 'escape')
|
|
938
|
+
this.#lastEscapeTime = 0;
|
|
867
939
|
return;
|
|
940
|
+
}
|
|
941
|
+
if (event.type === 'key' && event.id === 'escape') {
|
|
942
|
+
if (this.#state.status !== 'idle' || this.#pending === null
|
|
943
|
+
|| this.#editor.text !== '' || this.#images.length > 0) {
|
|
944
|
+
this.#lastEscapeTime = 0;
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
const now = Date.now();
|
|
948
|
+
if (now - this.#lastEscapeTime < DOUBLE_ESCAPE_MS) {
|
|
949
|
+
this.#lastEscapeTime = 0;
|
|
950
|
+
for (const listener of this.#rewinds)
|
|
951
|
+
listener();
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
this.#lastEscapeTime = now;
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
868
959
|
if (event.type === 'key' && (event.id === 'backspace' || event.id === 'delete')
|
|
869
960
|
&& this.#removeImageAtCursor(event.id))
|
|
870
961
|
return;
|
|
@@ -1428,6 +1519,8 @@ export class LocalTui {
|
|
|
1428
1519
|
return;
|
|
1429
1520
|
}
|
|
1430
1521
|
if (command.kind === 'historyPrev') {
|
|
1522
|
+
if (this.#restoreLatestQueuedSubmission())
|
|
1523
|
+
return;
|
|
1431
1524
|
this.#historyPrev();
|
|
1432
1525
|
return;
|
|
1433
1526
|
}
|
|
@@ -1445,6 +1538,8 @@ export class LocalTui {
|
|
|
1445
1538
|
if (command.kind === 'clear') {
|
|
1446
1539
|
this.#editor.clear();
|
|
1447
1540
|
this.#images = [];
|
|
1541
|
+
this.#queueEditNewer = null;
|
|
1542
|
+
this.#queueEditPending = false;
|
|
1448
1543
|
this.#historyIndex = 0;
|
|
1449
1544
|
this.#ac = null;
|
|
1450
1545
|
this.#search = null;
|
|
@@ -1482,6 +1577,41 @@ export class LocalTui {
|
|
|
1482
1577
|
this.#refreshAutocomplete();
|
|
1483
1578
|
this.#render();
|
|
1484
1579
|
}
|
|
1580
|
+
#restoreLatestQueuedSubmission() {
|
|
1581
|
+
if (this.#queueEditPending)
|
|
1582
|
+
return true;
|
|
1583
|
+
if (this.#queueEditNewer !== null) {
|
|
1584
|
+
const previous = this.#queuedSubmissions.pop();
|
|
1585
|
+
if (previous !== undefined) {
|
|
1586
|
+
this.#queueEditNewer.unshift(this.#currentSubmission());
|
|
1587
|
+
this.#replaceInput(previous);
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
1591
|
+
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
1592
|
+
return true;
|
|
1593
|
+
this.#queueEditPending = true;
|
|
1594
|
+
for (const listener of this.#queueEdits)
|
|
1595
|
+
listener();
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
if (this.#editor.text !== '' || this.#images.length > 0 || this.#historyIndex !== 0)
|
|
1599
|
+
return false;
|
|
1600
|
+
const submission = this.#queuedSubmissions.pop();
|
|
1601
|
+
if (submission !== undefined) {
|
|
1602
|
+
this.#queueEditNewer = [];
|
|
1603
|
+
this.#replaceInput(submission);
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
const hasDurableFollowup = this.#state.nextTurnInbox.some(message => message.source.kind === 'user');
|
|
1607
|
+
if (!hasDurableFollowup || this.#queueEdits.size === 0)
|
|
1608
|
+
return false;
|
|
1609
|
+
this.#queueEditNewer = [];
|
|
1610
|
+
this.#queueEditPending = true;
|
|
1611
|
+
for (const listener of this.#queueEdits)
|
|
1612
|
+
listener();
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1485
1615
|
#historyNext() {
|
|
1486
1616
|
if (this.#images.length > 0)
|
|
1487
1617
|
return;
|
|
@@ -1512,6 +1642,7 @@ export class LocalTui {
|
|
|
1512
1642
|
}
|
|
1513
1643
|
const images = this.#images.map(image => ({ ...image }));
|
|
1514
1644
|
const submittedText = images.length > 0 ? text.trim() : text;
|
|
1645
|
+
const queueEditNewer = this.#queueEditNewer;
|
|
1515
1646
|
const historyText = images.reduce((value, image, index) => value.replaceAll(imageMarker(index, image), ''), submittedText)
|
|
1516
1647
|
.replace(/[ \t]{2,}/gu, ' ')
|
|
1517
1648
|
.trim();
|
|
@@ -1521,6 +1652,8 @@ export class LocalTui {
|
|
|
1521
1652
|
}
|
|
1522
1653
|
this.#historyIndex = 0;
|
|
1523
1654
|
this.#draft = '';
|
|
1655
|
+
this.#queueEditNewer = null;
|
|
1656
|
+
this.#queueEditPending = false;
|
|
1524
1657
|
this.#editor.setText('');
|
|
1525
1658
|
this.#images = [];
|
|
1526
1659
|
this.#ac = null;
|
|
@@ -1530,18 +1663,26 @@ export class LocalTui {
|
|
|
1530
1663
|
this.#followTail();
|
|
1531
1664
|
const slash = images.length === 0 ? parseSlashInput(submittedText) : null;
|
|
1532
1665
|
if (slash !== null) {
|
|
1666
|
+
if (queueEditNewer !== null)
|
|
1667
|
+
this.#queuedSubmissions.push(...queueEditNewer);
|
|
1533
1668
|
this.#runSlash(slash.name, slash.args);
|
|
1534
1669
|
return;
|
|
1535
1670
|
}
|
|
1536
|
-
this.#render();
|
|
1537
1671
|
const pending = this.#pending;
|
|
1538
1672
|
if (pending !== null) {
|
|
1539
1673
|
this.#pending = null;
|
|
1540
1674
|
pending.resolve({ text: submittedText, images });
|
|
1675
|
+
if (queueEditNewer !== null)
|
|
1676
|
+
this.#queuedSubmissions.push(...queueEditNewer);
|
|
1541
1677
|
}
|
|
1542
|
-
else if (submittedText !== '' || images.length > 0) {
|
|
1543
|
-
|
|
1678
|
+
else if (submittedText !== '' || images.length > 0 || queueEditNewer !== null) {
|
|
1679
|
+
if (submittedText !== '' || images.length > 0) {
|
|
1680
|
+
this.#queuedSubmissions.push({ text: submittedText, images });
|
|
1681
|
+
}
|
|
1682
|
+
if (queueEditNewer !== null)
|
|
1683
|
+
this.#queuedSubmissions.push(...queueEditNewer);
|
|
1544
1684
|
}
|
|
1685
|
+
this.#render();
|
|
1545
1686
|
}
|
|
1546
1687
|
#runSlash(name, args = '') {
|
|
1547
1688
|
if (name === '') {
|
|
@@ -1569,11 +1710,6 @@ export class LocalTui {
|
|
|
1569
1710
|
this.#runSettings(args);
|
|
1570
1711
|
return;
|
|
1571
1712
|
}
|
|
1572
|
-
if (command.name === 'hotkeys') {
|
|
1573
|
-
this.#hotkeyCatalog();
|
|
1574
|
-
this.#render();
|
|
1575
|
-
return;
|
|
1576
|
-
}
|
|
1577
1713
|
if (command.name === 'copy') {
|
|
1578
1714
|
void this.#runCopy(args);
|
|
1579
1715
|
return;
|
|
@@ -1583,17 +1719,7 @@ export class LocalTui {
|
|
|
1583
1719
|
this.#render();
|
|
1584
1720
|
return;
|
|
1585
1721
|
}
|
|
1586
|
-
if (command.name === 'pwd') {
|
|
1587
|
-
this.#notice(formatWorkspaceText({
|
|
1588
|
-
cwd: process.cwd(),
|
|
1589
|
-
model: this.#model,
|
|
1590
|
-
...(this.#branch !== undefined ? { branch: this.#branch } : {}),
|
|
1591
|
-
}));
|
|
1592
|
-
this.#render();
|
|
1593
|
-
return;
|
|
1594
|
-
}
|
|
1595
1722
|
if (!BUILTIN_SLASH_COMMANDS.some((entry) => entry.name === command.name)) {
|
|
1596
|
-
this.#render();
|
|
1597
1723
|
const raw = '/' + name + (args === '' ? '' : ' ' + args);
|
|
1598
1724
|
const pending = this.#pending;
|
|
1599
1725
|
if (pending !== null) {
|
|
@@ -1601,13 +1727,24 @@ export class LocalTui {
|
|
|
1601
1727
|
pending.resolve({ text: raw, images: [] });
|
|
1602
1728
|
}
|
|
1603
1729
|
else {
|
|
1604
|
-
this.#
|
|
1730
|
+
this.#queuedSubmissions.push({ text: raw, images: [] });
|
|
1605
1731
|
}
|
|
1732
|
+
this.#render();
|
|
1606
1733
|
return;
|
|
1607
1734
|
}
|
|
1608
1735
|
this.#state = {
|
|
1609
1736
|
...this.#state,
|
|
1610
|
-
blocks: [...this.#state.blocks, {
|
|
1737
|
+
blocks: [...this.#state.blocks, {
|
|
1738
|
+
kind: 'commandOutput',
|
|
1739
|
+
command: 'help',
|
|
1740
|
+
text: [
|
|
1741
|
+
formatHelpText(this.#commands()),
|
|
1742
|
+
'',
|
|
1743
|
+
`**Keyboard Shortcuts · ${hotkeyCount(this.#keybindings)} bindings**`,
|
|
1744
|
+
'',
|
|
1745
|
+
formatHotkeysText(this.#keybindings),
|
|
1746
|
+
].join('\n'),
|
|
1747
|
+
}],
|
|
1611
1748
|
};
|
|
1612
1749
|
this.#focusLatestBlock();
|
|
1613
1750
|
this.#render();
|
|
@@ -1684,13 +1821,6 @@ export class LocalTui {
|
|
|
1684
1821
|
};
|
|
1685
1822
|
this.#focusLatestBlock();
|
|
1686
1823
|
}
|
|
1687
|
-
#hotkeyCatalog() {
|
|
1688
|
-
this.#state = {
|
|
1689
|
-
...this.#state,
|
|
1690
|
-
blocks: [...this.#state.blocks, { kind: 'hotkeyCatalog', bindings: this.#keybindings }],
|
|
1691
|
-
};
|
|
1692
|
-
this.#focusLatestBlock();
|
|
1693
|
-
}
|
|
1694
1824
|
#focusLatestBlock() {
|
|
1695
1825
|
this.#follow = false;
|
|
1696
1826
|
this.#focusBlock = Math.max(0, this.#state.blocks.length - 1);
|