agent-sh 0.12.24 → 0.12.26
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 +61 -9
- package/dist/agent/system-prompt.js +2 -2
- package/dist/extensions/shell-context.d.ts +2 -1
- package/dist/extensions/shell-context.js +5 -4
- package/dist/index.js +30 -54
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +13 -8
- package/dist/shell/input-handler.js +75 -27
- package/dist/shell/tui-input-view.d.ts +5 -0
- package/dist/shell/tui-input-view.js +137 -96
- package/dist/utils/terminal-buffer.d.ts +6 -9
- package/dist/utils/terminal-buffer.js +21 -53
- package/examples/extensions/claude-code-bridge/README.md +14 -14
- package/examples/extensions/claude-code-bridge/index.ts +19 -25
- package/examples/extensions/opencode-bridge/README.md +59 -0
- package/examples/extensions/opencode-bridge/index.ts +601 -0
- package/examples/extensions/opencode-bridge/package.json +11 -0
- package/examples/extensions/pi-bridge/README.md +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,10 +19,12 @@ So I built agent-sh. Under the hood it's a normal shell on top of node-pty — y
|
|
|
19
19
|
~ $ > draft a commit message # agent reads your diff and shell history
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
agent-sh is built to be agent-agnostic. You can [bring your own coding agent](#bring-your-own-agent) or use the built-in agent `ash` — a lightweight, extensible agent if you'd like to build extensions on top of it.
|
|
23
23
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
|
+
### Installation
|
|
27
|
+
|
|
26
28
|
Install from npm:
|
|
27
29
|
|
|
28
30
|
```bash
|
|
@@ -41,7 +43,36 @@ npm run build # produces dist/
|
|
|
41
43
|
npm link # exposes `agent-sh` globally
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
Requires Node.js 18+. Currently supports **bash** and **zsh**; other shells (fish, nushell, etc.) are not yet wired up.
|
|
47
|
+
|
|
48
|
+
**Windows:** the interactive shell layer is bash/zsh-only. Run agent-sh inside **WSL** for the full experience. Native Windows (cmd.exe / PowerShell) is not supported as the host shell, though headless / library / ACP-bridge usage may work — file an issue if you hit a gap.
|
|
49
|
+
|
|
50
|
+
Tip — add a shell alias:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
alias ash="agent-sh"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Once installed, pick a backend below.
|
|
57
|
+
|
|
58
|
+
### Option A: Bring your own coding agent
|
|
59
|
+
|
|
60
|
+
If you already use a coding agent, host it inside agent-sh — same terminal, same `>` entry point, same shell-context wiring. Three bridges ship in the box:
|
|
61
|
+
|
|
62
|
+
- **pi** — [pi-mono](https://github.com/badlogic/pi-mono) coding agent
|
|
63
|
+
- **claude-code** — official [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)
|
|
64
|
+
- **opencode** — [opencode](https://opencode.ai/) via `@opencode-ai/sdk`
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
agent-sh install pi-bridge
|
|
68
|
+
agent-sh --backend pi
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [Bring your own agent](#bring-your-own-agent) below for full details and the other backends.
|
|
72
|
+
|
|
73
|
+
### Option B: Use the built-in agent (ash)
|
|
74
|
+
|
|
75
|
+
`ash` is agent-sh's own lightweight agent. It works with any OpenAI-compatible API — pick one of the zero-config paths below, no settings file needed. agent-sh auto-activates a built-in provider when it sees a known key.
|
|
45
76
|
|
|
46
77
|
**Hosted models via OpenRouter** (300+ models, one key):
|
|
47
78
|
|
|
@@ -77,15 +108,36 @@ Once running, switch models at any time with `/model <name>` (tab-completes; sel
|
|
|
77
108
|
|
|
78
109
|
For richer configuration (multiple providers, extensions), run `agent-sh init` to scaffold `~/.agent-sh/settings.json` with copy-pasteable examples. See the [Usage Guide](docs/usage.md) for the full list of supported providers.
|
|
79
110
|
|
|
80
|
-
|
|
111
|
+
`ash` is designed to be extended. Extensions can add tools, content transforms (e.g. render LaTeX or Mermaid), themes, slash commands, or new input modes — see [Extensions](docs/extensions.md) for the full surface.
|
|
81
112
|
|
|
82
|
-
|
|
83
|
-
alias ash="agent-sh"
|
|
84
|
-
```
|
|
113
|
+
## Bring your own agent
|
|
85
114
|
|
|
86
|
-
|
|
115
|
+
The built-in agent (`ash`) is the default, but agent-sh can host a different coding agent as its backend — same terminal, same `>` entry point, same shell-context wiring. Three bridges ship in the box:
|
|
87
116
|
|
|
88
|
-
**
|
|
117
|
+
- **[pi-bridge](examples/extensions/pi-bridge/)** — runs [pi](https://github.com/badlogic/pi-mono) (`@mariozechner/pi-coding-agent`) in-process. Pi brings its own models, tools, and `~/.pi/agent/settings.json`.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
agent-sh install pi-bridge
|
|
121
|
+
agent-sh --backend pi
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **[claude-code-bridge](examples/extensions/claude-code-bridge/)** — runs claude-code (the official [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)) in-process. Uses claude-code's own `Read`/`Edit`/`Write`/`Bash`/`Glob`/`Grep` tools.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
agent-sh install claude-code-bridge
|
|
128
|
+
agent-sh --backend claude-code
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- **[opencode-bridge](examples/extensions/opencode-bridge/)** — runs [opencode](https://opencode.ai/) in-process via `@opencode-ai/sdk`. Uses opencode's tools, models, and `opencode auth login` credentials.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
agent-sh install opencode-bridge
|
|
135
|
+
agent-sh --backend opencode
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
All three bridges receive agent-sh's per-query shell context (`<shell_events>`) and follow the PTY-tracked cwd, so the hosted agent sees what you ran and where you are. Switching at runtime with `/backend <name>` persists the choice across sessions automatically; the `--backend` flag above is per-session only.
|
|
139
|
+
|
|
140
|
+
**Caveat:** pi, claude-code, and opencode each manage their own tool surfaces, so agent-sh extensions that register tools (or skills, instructions, etc.) for the built-in `ash` agent generally won't be visible to a hosted backend. Frontend extensions (themes, content transforms, slash commands, the TUI renderer) keep working — only the agent-side capabilities differ. Use the bridges when you want that agent's toolset; stay on `ash` when you want agent-sh's extension ecosystem.
|
|
89
141
|
|
|
90
142
|
## Key Features
|
|
91
143
|
|
|
@@ -95,7 +147,7 @@ Requires Node.js 18+. Currently supports **bash** and **zsh**; other shells (fis
|
|
|
95
147
|
|
|
96
148
|
**Context that just works.** Every query includes your cwd, recent commands, and their output. Run a failing test, type `> fix this`, and agent-sh knows exactly what happened. Context management works like shell history — continuous, persistent across restarts, no sessions to manage. See [Context Management](docs/context-management.md).
|
|
97
149
|
|
|
98
|
-
**Any LLM, any backend.** agent-sh works with any OpenAI-compatible API out of the box. Define multiple providers in settings and switch models at runtime with `/model <name>`. Or swap in a completely different agent —
|
|
150
|
+
**Any LLM, any backend.** agent-sh works with any OpenAI-compatible API out of the box. Define multiple providers in settings and switch models at runtime with `/model <name>`. Or swap in a completely different agent — bundled bridges run [pi](examples/extensions/pi-bridge/), [claude-code](examples/extensions/claude-code-bridge/), or [opencode](examples/extensions/opencode-bridge/) as a drop-in backend (see [Bring your own agent](#bring-your-own-agent)).
|
|
99
151
|
|
|
100
152
|
**Extensible by design.** The entire system is built on a typed event bus. Extensions can add custom input modes, content transforms (render LaTeX as images, Mermaid as diagrams), themes, slash commands, or replace the agent backend entirely. The built-in TUI renderer is itself just an extension.
|
|
101
153
|
|
|
@@ -108,9 +108,9 @@ Extensions may register additional tools — follow their instructions.
|
|
|
108
108
|
- Always check command exit codes for errors
|
|
109
109
|
|
|
110
110
|
# Context Envelopes
|
|
111
|
-
- \`<query_context>\` (
|
|
111
|
+
- \`<query_context>\` (contains \`<cwd>\` always, and \`<shell_events>\` when there were user shell commands since the last turn): the user's situation when they sent this turn — \`<cwd>\` anchors where they are right now, \`<shell_events>\` grounds "fix this" / "what just happened" requests. Trust the most recent \`<cwd>\` over any cwd referenced in earlier history.
|
|
112
112
|
- \`<dynamic_context>\`: current system state — in-flight work, mode markers, warnings.
|
|
113
|
-
|
|
113
|
+
\`<dynamic_context>\` may be absent on any turn.
|
|
114
114
|
|
|
115
115
|
# Preference Learning
|
|
116
116
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tracks PTY commands and cwd, spills long outputs, contributes the
|
|
3
|
-
*
|
|
3
|
+
* per-query `<cwd>` (always) and `<shell_events>` (when there are fresh
|
|
4
|
+
* user-shell exchanges) signals. Frontends without a PTY skip this
|
|
4
5
|
* built-in and the agent runs cwd-aware via core's process.cwd() default.
|
|
5
6
|
*/
|
|
6
7
|
import type { ExtensionContext } from "../types.js";
|
|
@@ -43,15 +43,16 @@ export default function activate(ctx) {
|
|
|
43
43
|
bus.on("shell:agent-exec-done", () => { agentShellActive = false; });
|
|
44
44
|
// Override core's process.cwd() default with the PTY-tracked value.
|
|
45
45
|
ctx.advise("cwd", () => currentCwd);
|
|
46
|
-
ctx.registerContextProducer("shell-
|
|
46
|
+
ctx.registerContextProducer("shell-context", () => {
|
|
47
|
+
const cwdTag = `<cwd>${currentCwd}</cwd>`;
|
|
47
48
|
const fresh = exchanges.filter((ex) => ex.id > lastSeq && ex.source !== "agent");
|
|
48
49
|
if (fresh.length === 0)
|
|
49
|
-
return
|
|
50
|
+
return cwdTag;
|
|
50
51
|
lastSeq = exchanges[exchanges.length - 1].id;
|
|
51
52
|
const text = fresh.map(formatExchangeTruncated).filter(Boolean).join("\n");
|
|
52
53
|
if (!text)
|
|
53
|
-
return
|
|
54
|
-
return
|
|
54
|
+
return cwdTag;
|
|
55
|
+
return `${cwdTag}\n<shell_events>\n${text}\n</shell_events>`;
|
|
55
56
|
}, { mode: "per-query" });
|
|
56
57
|
ctx.define("shell:context-recent", (n = 25) => {
|
|
57
58
|
const recent = exchanges.slice(-n);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { activateShell } from "./shell/index.js";
|
|
4
|
+
import { activateShell, registerShellHandlers } from "./shell/index.js";
|
|
5
5
|
import { createCore } from "./core.js";
|
|
6
6
|
import { palette as p } from "./utils/palette.js";
|
|
7
7
|
import { loadBuiltinExtensions } from "./extensions/index.js";
|
|
@@ -236,49 +236,11 @@ async function main() {
|
|
|
236
236
|
}
|
|
237
237
|
process.exit(0);
|
|
238
238
|
};
|
|
239
|
-
// ── Extension context (must precede shell activation) ────────
|
|
240
|
-
if (process.env.DEBUG) {
|
|
241
|
-
console.error('[agent-sh] Setting up extensions...');
|
|
242
|
-
}
|
|
243
239
|
const extCtx = core.extensionContext({ quit: cleanup });
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
249
|
-
shell = activateShell(extCtx, {
|
|
250
|
-
cols,
|
|
251
|
-
rows,
|
|
252
|
-
shellPath: config.shell || process.env.SHELL || "/bin/bash",
|
|
253
|
-
cwd: process.cwd(),
|
|
254
|
-
onShowAgentInfo: () => {
|
|
255
|
-
if (agentInfo) {
|
|
256
|
-
return { info: `${p.dim}${agentInfo.name}${agentInfo.model ? ` (${agentInfo.model})` : ""}${p.reset}` };
|
|
257
|
-
}
|
|
258
|
-
return { info: "" };
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
if (process.env.DEBUG) {
|
|
262
|
-
console.error('[agent-sh] Shell created');
|
|
263
|
-
}
|
|
264
|
-
// ── Input mode ───────────────────────────────────────────────
|
|
265
|
-
bus.emit("input-mode:register", {
|
|
266
|
-
id: "agent",
|
|
267
|
-
trigger: ">",
|
|
268
|
-
label: "agent",
|
|
269
|
-
promptIcon: "❯",
|
|
270
|
-
indicator: "●",
|
|
271
|
-
onSubmit(query, b) {
|
|
272
|
-
b.emit("agent:submit", { query });
|
|
273
|
-
},
|
|
274
|
-
returnToSelf: true,
|
|
275
|
-
});
|
|
276
|
-
// Load built-in extensions (individually disableable via settings.disabledBuiltins)
|
|
240
|
+
// Before loadExtensions: extensions look up shell handlers at activation.
|
|
241
|
+
registerShellHandlers(extCtx);
|
|
242
|
+
// Load before spawning the shell so PS1 lands below the banner.
|
|
277
243
|
await loadBuiltinExtensions(extCtx, getSettings().disabledBuiltins);
|
|
278
|
-
// Load user extensions (may register alternative agent backends)
|
|
279
|
-
if (process.env.DEBUG) {
|
|
280
|
-
console.error('[agent-sh] Loading extensions...');
|
|
281
|
-
}
|
|
282
244
|
const loadExtensionsTimeoutMs = 10000;
|
|
283
245
|
let loadedExtensions = [];
|
|
284
246
|
await Promise.race([
|
|
@@ -287,17 +249,9 @@ async function main() {
|
|
|
287
249
|
]).catch((err) => {
|
|
288
250
|
console.error(`Warning: ${err.message}`);
|
|
289
251
|
});
|
|
290
|
-
if (process.env.DEBUG) {
|
|
291
|
-
console.error('[agent-sh] Extensions loaded');
|
|
292
|
-
}
|
|
293
|
-
// Names ride along so backend extensions can build banner sections.
|
|
294
252
|
core.bus.emit("core:extensions-loaded", { names: loadedExtensions });
|
|
295
|
-
// ── Activate agent backend ────────────────────────────────────
|
|
296
|
-
// Extensions had their chance to register via agent:register-backend.
|
|
297
|
-
// If none did, the built-in AgentLoop gets wired to bus events.
|
|
298
253
|
const { names: backendNames } = core.bus.emitPipe("config:get-backends", { names: [], active: null });
|
|
299
254
|
if (backendNames.length === 0) {
|
|
300
|
-
shell?.kill();
|
|
301
255
|
console.error("\nagent-sh: no agent backend available.\n\n" +
|
|
302
256
|
" Export OPENROUTER_API_KEY or OPENAI_API_KEY for zero-config launch, or\n" +
|
|
303
257
|
" pass --api-key on the command line, or\n" +
|
|
@@ -306,7 +260,6 @@ async function main() {
|
|
|
306
260
|
process.exit(1);
|
|
307
261
|
}
|
|
308
262
|
if (config.backend && !backendNames.includes(config.backend)) {
|
|
309
|
-
shell?.kill();
|
|
310
263
|
const bridge = suggestBridgeFor(config.backend);
|
|
311
264
|
const hint = bridge
|
|
312
265
|
? ` Try: agent-sh install ${bridge}\n`
|
|
@@ -316,9 +269,6 @@ async function main() {
|
|
|
316
269
|
hint);
|
|
317
270
|
process.exit(1);
|
|
318
271
|
}
|
|
319
|
-
// No await: banner must out-race the shell's PS1 arriving via PTY.
|
|
320
|
-
core.activateBackend(config.backend);
|
|
321
|
-
// ── Startup banner ───────────────────────────────────────────
|
|
322
272
|
const settings = getSettings();
|
|
323
273
|
if (settings.startupBanner !== false) {
|
|
324
274
|
const termW = process.stdout.columns || 80;
|
|
@@ -346,6 +296,32 @@ async function main() {
|
|
|
346
296
|
"\n " + hint + "\n" +
|
|
347
297
|
borderLine + "\n\n");
|
|
348
298
|
}
|
|
299
|
+
// 100ms sidesteps macOS SIGTTOU during fg-pgrp handoff.
|
|
300
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
301
|
+
shell = activateShell(extCtx, {
|
|
302
|
+
cols,
|
|
303
|
+
rows,
|
|
304
|
+
shellPath: config.shell || process.env.SHELL || "/bin/bash",
|
|
305
|
+
cwd: process.cwd(),
|
|
306
|
+
onShowAgentInfo: () => {
|
|
307
|
+
if (agentInfo) {
|
|
308
|
+
return { info: `${p.dim}${agentInfo.name}${agentInfo.model ? ` (${agentInfo.model})` : ""}${p.reset}` };
|
|
309
|
+
}
|
|
310
|
+
return { info: "" };
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
bus.emit("input-mode:register", {
|
|
314
|
+
id: "agent",
|
|
315
|
+
trigger: ">",
|
|
316
|
+
label: "agent",
|
|
317
|
+
promptIcon: "❯",
|
|
318
|
+
indicator: "●",
|
|
319
|
+
onSubmit(query, b) {
|
|
320
|
+
b.emit("agent:submit", { query });
|
|
321
|
+
},
|
|
322
|
+
returnToSelf: true,
|
|
323
|
+
});
|
|
324
|
+
core.activateBackend(config.backend);
|
|
349
325
|
// ── Terminal lifecycle ────────────────────────────────────────
|
|
350
326
|
process.on("SIGTERM", cleanup);
|
|
351
327
|
process.on("SIGHUP", cleanup);
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface ShellHandle {
|
|
|
27
27
|
/** Forward terminal size changes to the PTY. */
|
|
28
28
|
resize(cols: number, rows: number): void;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Register shell-owned handlers extensions can `ctx.call`. Must run before
|
|
32
|
+
* `loadExtensions`; the handlers only need the bus, not the PTY.
|
|
33
|
+
*/
|
|
34
|
+
export declare function registerShellHandlers(ctx: ExtensionContext): void;
|
|
30
35
|
/**
|
|
31
36
|
* Construct the Shell, wire resize forwarding, and register cleanup with the
|
|
32
37
|
* provided ExtensionContext. Returns a handle the caller (typically
|
package/dist/shell/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { Shell } from "./shell.js";
|
|
2
2
|
import { StdoutSurface } from "../utils/compositor.js";
|
|
3
3
|
import { TerminalBuffer } from "../utils/terminal-buffer.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register shell-owned handlers extensions can `ctx.call`. Must run before
|
|
6
|
+
* `loadExtensions`; the handlers only need the bus, not the PTY.
|
|
7
|
+
*/
|
|
8
|
+
export function registerShellHandlers(ctx) {
|
|
9
|
+
let terminalBufferSingleton;
|
|
10
|
+
ctx.define("terminal-buffer", () => {
|
|
11
|
+
if (terminalBufferSingleton !== undefined)
|
|
12
|
+
return terminalBufferSingleton;
|
|
13
|
+
terminalBufferSingleton = TerminalBuffer.createWired(ctx.bus);
|
|
14
|
+
return terminalBufferSingleton;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Construct the Shell, wire resize forwarding, and register cleanup with the
|
|
6
19
|
* provided ExtensionContext. Returns a handle the caller (typically
|
|
@@ -13,14 +26,6 @@ export function activateShell(ctx, opts) {
|
|
|
13
26
|
ctx.compositor.setDefault("agent", stdoutSurface);
|
|
14
27
|
ctx.compositor.setDefault("query", stdoutSurface);
|
|
15
28
|
ctx.compositor.setDefault("status", stdoutSurface);
|
|
16
|
-
// Lazy because @xterm/headless is optional; null when not installed.
|
|
17
|
-
let terminalBufferSingleton;
|
|
18
|
-
ctx.define("terminal-buffer", () => {
|
|
19
|
-
if (terminalBufferSingleton !== undefined)
|
|
20
|
-
return terminalBufferSingleton;
|
|
21
|
-
terminalBufferSingleton = TerminalBuffer.createWired(ctx.bus);
|
|
22
|
-
return terminalBufferSingleton;
|
|
23
|
-
});
|
|
24
29
|
const shell = new Shell({
|
|
25
30
|
bus: ctx.bus,
|
|
26
31
|
handlers: { define: ctx.define, call: ctx.call },
|
|
@@ -229,9 +229,15 @@ export class InputHandler {
|
|
|
229
229
|
return false;
|
|
230
230
|
}
|
|
231
231
|
renderModeInput() {
|
|
232
|
-
this.view.
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
this.view.beginFrame();
|
|
233
|
+
try {
|
|
234
|
+
this.view.clearAutocomplete();
|
|
235
|
+
this.drawPrompt();
|
|
236
|
+
this.updateAutocomplete();
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
this.view.endFrame();
|
|
240
|
+
}
|
|
235
241
|
}
|
|
236
242
|
updateAutocomplete() {
|
|
237
243
|
const buf = this.editor.text;
|
|
@@ -278,13 +284,19 @@ export class InputHandler {
|
|
|
278
284
|
else {
|
|
279
285
|
this.editor.setText(selected.name);
|
|
280
286
|
}
|
|
281
|
-
this.view.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
this.
|
|
287
|
+
this.view.beginFrame();
|
|
288
|
+
try {
|
|
289
|
+
this.view.clearAutocomplete();
|
|
290
|
+
this.autocompleteActive = false;
|
|
291
|
+
this.autocompleteItems = [];
|
|
292
|
+
this.autocompleteIndex = 0;
|
|
293
|
+
this.drawPrompt();
|
|
294
|
+
if (isFileAc)
|
|
295
|
+
this.updateAutocomplete();
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
this.view.endFrame();
|
|
299
|
+
}
|
|
288
300
|
}
|
|
289
301
|
dismissAutocomplete() {
|
|
290
302
|
this.view.clearAutocomplete();
|
|
@@ -314,11 +326,17 @@ export class InputHandler {
|
|
|
314
326
|
case "changed": {
|
|
315
327
|
const switchMode = this.modes.get(this.editor.text);
|
|
316
328
|
if (this.editor.text.length === 1 && switchMode && switchMode !== this.activeMode) {
|
|
317
|
-
this.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
this.view.beginFrame();
|
|
330
|
+
try {
|
|
331
|
+
this.dismissAutocomplete();
|
|
332
|
+
this.view.clearPromptArea();
|
|
333
|
+
this.activeMode = switchMode;
|
|
334
|
+
this.editor.clear();
|
|
335
|
+
this.drawPrompt(false);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
this.view.endFrame();
|
|
339
|
+
}
|
|
322
340
|
break;
|
|
323
341
|
}
|
|
324
342
|
this.historyIndex = -1;
|
|
@@ -371,8 +389,14 @@ export class InputHandler {
|
|
|
371
389
|
}
|
|
372
390
|
case "cancel":
|
|
373
391
|
if (this.autocompleteActive) {
|
|
374
|
-
this.
|
|
375
|
-
|
|
392
|
+
this.view.beginFrame();
|
|
393
|
+
try {
|
|
394
|
+
this.dismissAutocomplete();
|
|
395
|
+
this.drawPrompt();
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
this.view.endFrame();
|
|
399
|
+
}
|
|
376
400
|
}
|
|
377
401
|
else {
|
|
378
402
|
this.exitMode();
|
|
@@ -393,9 +417,15 @@ export class InputHandler {
|
|
|
393
417
|
this.autocompleteIndex === 0
|
|
394
418
|
? this.autocompleteItems.length - 1
|
|
395
419
|
: this.autocompleteIndex - 1;
|
|
396
|
-
this.view.
|
|
397
|
-
|
|
398
|
-
|
|
420
|
+
this.view.beginFrame();
|
|
421
|
+
try {
|
|
422
|
+
this.view.clearAutocomplete();
|
|
423
|
+
this.drawPrompt();
|
|
424
|
+
this.view.drawAutocomplete({ items: this.autocompleteItems, selected: this.autocompleteIndex });
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
this.view.endFrame();
|
|
428
|
+
}
|
|
399
429
|
}
|
|
400
430
|
else if (this.history.length > 0) {
|
|
401
431
|
if (this.historyIndex === -1) {
|
|
@@ -406,8 +436,14 @@ export class InputHandler {
|
|
|
406
436
|
this.historyIndex--;
|
|
407
437
|
}
|
|
408
438
|
this.editor.setText(this.history[this.historyIndex]);
|
|
409
|
-
this.view.
|
|
410
|
-
|
|
439
|
+
this.view.beginFrame();
|
|
440
|
+
try {
|
|
441
|
+
this.view.clearAutocomplete();
|
|
442
|
+
this.drawPrompt();
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
this.view.endFrame();
|
|
446
|
+
}
|
|
411
447
|
}
|
|
412
448
|
break;
|
|
413
449
|
case "arrow-down":
|
|
@@ -416,9 +452,15 @@ export class InputHandler {
|
|
|
416
452
|
this.autocompleteIndex === this.autocompleteItems.length - 1
|
|
417
453
|
? 0
|
|
418
454
|
: this.autocompleteIndex + 1;
|
|
419
|
-
this.view.
|
|
420
|
-
|
|
421
|
-
|
|
455
|
+
this.view.beginFrame();
|
|
456
|
+
try {
|
|
457
|
+
this.view.clearAutocomplete();
|
|
458
|
+
this.drawPrompt();
|
|
459
|
+
this.view.drawAutocomplete({ items: this.autocompleteItems, selected: this.autocompleteIndex });
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
this.view.endFrame();
|
|
463
|
+
}
|
|
422
464
|
}
|
|
423
465
|
else if (this.historyIndex !== -1) {
|
|
424
466
|
if (this.historyIndex < this.history.length - 1) {
|
|
@@ -429,8 +471,14 @@ export class InputHandler {
|
|
|
429
471
|
this.historyIndex = -1;
|
|
430
472
|
this.editor.setText(this.savedBuffer);
|
|
431
473
|
}
|
|
432
|
-
this.view.
|
|
433
|
-
|
|
474
|
+
this.view.beginFrame();
|
|
475
|
+
try {
|
|
476
|
+
this.view.clearAutocomplete();
|
|
477
|
+
this.drawPrompt();
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
this.view.endFrame();
|
|
481
|
+
}
|
|
434
482
|
}
|
|
435
483
|
break;
|
|
436
484
|
}
|
|
@@ -26,7 +26,12 @@ export declare class TuiInputView {
|
|
|
26
26
|
private cursorTermCol;
|
|
27
27
|
private autocompleteLines;
|
|
28
28
|
private readonly surface;
|
|
29
|
+
private frameBuf;
|
|
29
30
|
constructor(surface?: RenderSurface);
|
|
31
|
+
beginFrame(): void;
|
|
32
|
+
endFrame(): void;
|
|
33
|
+
private emit;
|
|
34
|
+
private autoFrame;
|
|
30
35
|
resetCursor(): void;
|
|
31
36
|
enableModeKeys(): void;
|
|
32
37
|
disableModeKeys(): void;
|