@zhijiewang/openharness 0.10.1 → 0.11.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 +79 -13
- package/dist/Tool.d.ts.map +1 -1
- package/dist/Tool.js +7 -1
- package/dist/Tool.js.map +1 -1
- package/dist/Tool.test.js +8 -2
- package/dist/Tool.test.js.map +1 -1
- package/dist/agents/roles.d.ts +25 -0
- package/dist/agents/roles.d.ts.map +1 -0
- package/dist/agents/roles.js +116 -0
- package/dist/agents/roles.js.map +1 -0
- package/dist/agents/roles.test.d.ts +2 -0
- package/dist/agents/roles.test.d.ts.map +1 -0
- package/dist/agents/roles.test.js +38 -0
- package/dist/agents/roles.test.js.map +1 -0
- package/dist/commands/commands-new.test.d.ts +5 -0
- package/dist/commands/commands-new.test.d.ts.map +1 -0
- package/dist/commands/commands-new.test.js +132 -0
- package/dist/commands/commands-new.test.js.map +1 -0
- package/dist/commands/commands.test.js +31 -0
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +199 -6
- package/dist/commands/index.js.map +1 -1
- package/dist/components/REPL.js +1 -1
- package/dist/components/REPL.js.map +1 -1
- package/dist/git/git.test.js +33 -1
- package/dist/git/git.test.js.map +1 -1
- package/dist/git/index.d.ts +12 -0
- package/dist/git/index.d.ts.map +1 -1
- package/dist/git/index.js +47 -2
- package/dist/git/index.js.map +1 -1
- package/dist/harness/checkpoints.d.ts +36 -0
- package/dist/harness/checkpoints.d.ts.map +1 -0
- package/dist/harness/checkpoints.js +156 -0
- package/dist/harness/checkpoints.js.map +1 -0
- package/dist/harness/config.d.ts +3 -0
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +35 -2
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/config.test.js +21 -1
- package/dist/harness/config.test.js.map +1 -1
- package/dist/harness/hooks-env.test.d.ts +5 -0
- package/dist/harness/hooks-env.test.d.ts.map +1 -0
- package/dist/harness/hooks-env.test.js +41 -0
- package/dist/harness/hooks-env.test.js.map +1 -0
- package/dist/harness/hooks.d.ts +7 -0
- package/dist/harness/hooks.d.ts.map +1 -1
- package/dist/harness/hooks.js +14 -0
- package/dist/harness/hooks.js.map +1 -1
- package/dist/harness/keybindings.d.ts.map +1 -1
- package/dist/harness/keybindings.js +4 -0
- package/dist/harness/keybindings.js.map +1 -1
- package/dist/harness/memory.d.ts +19 -0
- package/dist/harness/memory.d.ts.map +1 -1
- package/dist/harness/memory.js +85 -0
- package/dist/harness/memory.js.map +1 -1
- package/dist/harness/onboarding.d.ts +1 -1
- package/dist/harness/onboarding.d.ts.map +1 -1
- package/dist/harness/onboarding.js +59 -4
- package/dist/harness/onboarding.js.map +1 -1
- package/dist/harness/onboarding.test.d.ts +5 -0
- package/dist/harness/onboarding.test.d.ts.map +1 -0
- package/dist/harness/onboarding.test.js +93 -0
- package/dist/harness/onboarding.test.js.map +1 -0
- package/dist/harness/rules.d.ts +6 -1
- package/dist/harness/rules.d.ts.map +1 -1
- package/dist/harness/rules.js +52 -5
- package/dist/harness/rules.js.map +1 -1
- package/dist/harness/rules.test.js +30 -1
- package/dist/harness/rules.test.js.map +1 -1
- package/dist/harness/session.d.ts +8 -1
- package/dist/harness/session.d.ts.map +1 -1
- package/dist/harness/session.js +13 -5
- package/dist/harness/session.js.map +1 -1
- package/dist/harness/store.d.ts +46 -0
- package/dist/harness/store.d.ts.map +1 -0
- package/dist/harness/store.js +56 -0
- package/dist/harness/store.js.map +1 -0
- package/dist/harness/store.test.d.ts +2 -0
- package/dist/harness/store.test.d.ts.map +1 -0
- package/dist/harness/store.test.js +71 -0
- package/dist/harness/store.test.js.map +1 -0
- package/dist/harness/submit-handler.d.ts +2 -0
- package/dist/harness/submit-handler.d.ts.map +1 -1
- package/dist/harness/submit-handler.js +3 -0
- package/dist/harness/submit-handler.js.map +1 -1
- package/dist/main.js +153 -26
- package/dist/main.js.map +1 -1
- package/dist/mcp/client.d.ts +2 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +10 -2
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/loader.d.ts +2 -0
- package/dist/mcp/loader.d.ts.map +1 -1
- package/dist/mcp/loader.js +34 -18
- package/dist/mcp/loader.js.map +1 -1
- package/dist/mcp/loader.test.d.ts +7 -0
- package/dist/mcp/loader.test.d.ts.map +1 -0
- package/dist/mcp/loader.test.js +25 -0
- package/dist/mcp/loader.test.js.map +1 -0
- package/dist/providers/anthropic-convert.test.d.ts +5 -0
- package/dist/providers/anthropic-convert.test.d.ts.map +1 -0
- package/dist/providers/anthropic-convert.test.js +98 -0
- package/dist/providers/anthropic-convert.test.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +23 -4
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/stream-parsing.test.d.ts +6 -0
- package/dist/providers/stream-parsing.test.d.ts.map +1 -0
- package/dist/providers/stream-parsing.test.js +174 -0
- package/dist/providers/stream-parsing.test.js.map +1 -0
- package/dist/query/compress.d.ts +17 -0
- package/dist/query/compress.d.ts.map +1 -0
- package/dist/query/compress.js +115 -0
- package/dist/query/compress.js.map +1 -0
- package/dist/query/errors.d.ts +10 -0
- package/dist/query/errors.d.ts.map +1 -0
- package/dist/query/errors.js +22 -0
- package/dist/query/errors.js.map +1 -0
- package/dist/query/index.d.ts +15 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +199 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/tools.d.ts +17 -0
- package/dist/query/tools.d.ts.map +1 -0
- package/dist/query/tools.js +129 -0
- package/dist/query/tools.js.map +1 -0
- package/dist/query/types.d.ts +31 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/types.js +5 -0
- package/dist/query/types.js.map +1 -0
- package/dist/query.d.ts +8 -38
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +7 -444
- package/dist/query.js.map +1 -1
- package/dist/query.test.js +1 -1
- package/dist/query.test.js.map +1 -1
- package/dist/renderer/cells.d.ts.map +1 -1
- package/dist/renderer/cells.js +15 -2
- package/dist/renderer/cells.js.map +1 -1
- package/dist/renderer/colors.d.ts +8 -0
- package/dist/renderer/colors.d.ts.map +1 -0
- package/dist/renderer/colors.js +18 -0
- package/dist/renderer/colors.js.map +1 -0
- package/dist/renderer/diff.test.d.ts +5 -0
- package/dist/renderer/diff.test.d.ts.map +1 -0
- package/dist/renderer/diff.test.js +140 -0
- package/dist/renderer/diff.test.js.map +1 -0
- package/dist/renderer/differ.d.ts.map +1 -1
- package/dist/renderer/differ.js +1 -10
- package/dist/renderer/differ.js.map +1 -1
- package/dist/renderer/e2e.test.js +1 -0
- package/dist/renderer/e2e.test.js.map +1 -1
- package/dist/renderer/image.test.d.ts +5 -0
- package/dist/renderer/image.test.d.ts.map +1 -0
- package/dist/renderer/image.test.js +66 -0
- package/dist/renderer/image.test.js.map +1 -0
- package/dist/renderer/index.d.ts +21 -4
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +191 -116
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/layout.d.ts +5 -1
- package/dist/renderer/layout.d.ts.map +1 -1
- package/dist/renderer/layout.js +504 -614
- package/dist/renderer/layout.js.map +1 -1
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +42 -36
- package/dist/renderer/markdown.js.map +1 -1
- package/dist/renderer/perf.test.js +1 -0
- package/dist/renderer/perf.test.js.map +1 -1
- package/dist/renderer/session-browser.test.d.ts +6 -0
- package/dist/renderer/session-browser.test.d.ts.map +1 -0
- package/dist/renderer/session-browser.test.js +95 -0
- package/dist/renderer/session-browser.test.js.map +1 -0
- package/dist/renderer/ui-ux.test.d.ts +15 -0
- package/dist/renderer/ui-ux.test.d.ts.map +1 -0
- package/dist/renderer/ui-ux.test.js +470 -0
- package/dist/renderer/ui-ux.test.js.map +1 -0
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +178 -14
- package/dist/repl.js.map +1 -1
- package/dist/services/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/services/StreamingToolExecutor.js +4 -2
- package/dist/services/StreamingToolExecutor.js.map +1 -1
- package/dist/services/agent-messaging.d.ts +68 -0
- package/dist/services/agent-messaging.d.ts.map +1 -0
- package/dist/services/agent-messaging.js +121 -0
- package/dist/services/agent-messaging.js.map +1 -0
- package/dist/services/agent-messaging.test.d.ts +2 -0
- package/dist/services/agent-messaging.test.d.ts.map +1 -0
- package/dist/services/agent-messaging.test.js +88 -0
- package/dist/services/agent-messaging.test.js.map +1 -0
- package/dist/services/cron.d.ts +40 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +90 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/cron.test.d.ts +2 -0
- package/dist/services/cron.test.d.ts.map +1 -0
- package/dist/services/cron.test.js +49 -0
- package/dist/services/cron.test.js.map +1 -0
- package/dist/tools/AgentTool/index.d.ts +9 -0
- package/dist/tools/AgentTool/index.d.ts.map +1 -1
- package/dist/tools/AgentTool/index.js +89 -6
- package/dist/tools/AgentTool/index.js.map +1 -1
- package/dist/tools/BashTool/index.d.ts +6 -0
- package/dist/tools/BashTool/index.d.ts.map +1 -1
- package/dist/tools/BashTool/index.js +39 -1
- package/dist/tools/BashTool/index.js.map +1 -1
- package/dist/tools/FileEditTool/index.js +4 -4
- package/dist/tools/FileEditTool/index.js.map +1 -1
- package/dist/tools/FileReadTool/index.d.ts +3 -0
- package/dist/tools/FileReadTool/index.d.ts.map +1 -1
- package/dist/tools/FileReadTool/index.js +102 -4
- package/dist/tools/FileReadTool/index.js.map +1 -1
- package/dist/tools/FileWriteTool/index.d.ts.map +1 -1
- package/dist/tools/FileWriteTool/index.js +20 -5
- package/dist/tools/FileWriteTool/index.js.map +1 -1
- package/dist/tools/GlobTool/index.d.ts.map +1 -1
- package/dist/tools/GlobTool/index.js +4 -61
- package/dist/tools/GlobTool/index.js.map +1 -1
- package/dist/tools/GrepTool/index.d.ts +30 -0
- package/dist/tools/GrepTool/index.d.ts.map +1 -1
- package/dist/tools/GrepTool/index.js +153 -72
- package/dist/tools/GrepTool/index.js.map +1 -1
- package/dist/tools/LSTool/index.d.ts +3 -0
- package/dist/tools/LSTool/index.d.ts.map +1 -1
- package/dist/tools/LSTool/index.js +44 -29
- package/dist/tools/LSTool/index.js.map +1 -1
- package/dist/tools/TaskCreateTool/index.d.ts +6 -0
- package/dist/tools/TaskCreateTool/index.d.ts.map +1 -1
- package/dist/tools/TaskCreateTool/index.js +8 -2
- package/dist/tools/TaskCreateTool/index.js.map +1 -1
- package/dist/tools/TaskGetTool/index.d.ts +12 -0
- package/dist/tools/TaskGetTool/index.d.ts.map +1 -0
- package/dist/tools/TaskGetTool/index.js +50 -0
- package/dist/tools/TaskGetTool/index.js.map +1 -0
- package/dist/tools/TaskOutputTool/index.d.ts +15 -0
- package/dist/tools/TaskOutputTool/index.d.ts.map +1 -0
- package/dist/tools/TaskOutputTool/index.js +45 -0
- package/dist/tools/TaskOutputTool/index.js.map +1 -0
- package/dist/tools/TaskStopTool/index.d.ts +15 -0
- package/dist/tools/TaskStopTool/index.d.ts.map +1 -0
- package/dist/tools/TaskStopTool/index.js +51 -0
- package/dist/tools/TaskStopTool/index.js.map +1 -0
- package/dist/tools/TaskUpdateTool/index.d.ts +21 -3
- package/dist/tools/TaskUpdateTool/index.d.ts.map +1 -1
- package/dist/tools/TaskUpdateTool/index.js +48 -3
- package/dist/tools/TaskUpdateTool/index.js.map +1 -1
- package/dist/tools/tools-basic.test.js +191 -2
- package/dist/tools/tools-basic.test.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +6 -0
- package/dist/tools.js.map +1 -1
- package/dist/types/permissions.d.ts +2 -2
- package/dist/types/permissions.d.ts.map +1 -1
- package/dist/types/permissions.js +59 -13
- package/dist/types/permissions.js.map +1 -1
- package/dist/types/permissions.test.js +57 -0
- package/dist/types/permissions.test.js.map +1 -1
- package/dist/utils/bash-safety.d.ts +18 -0
- package/dist/utils/bash-safety.d.ts.map +1 -0
- package/dist/utils/bash-safety.js +227 -0
- package/dist/utils/bash-safety.js.map +1 -0
- package/dist/utils/bash-safety.test.d.ts +2 -0
- package/dist/utils/bash-safety.test.d.ts.map +1 -0
- package/dist/utils/bash-safety.test.js +112 -0
- package/dist/utils/bash-safety.test.js.map +1 -0
- package/dist/utils/fs.d.ts +15 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +64 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/fs.test.d.ts +5 -0
- package/dist/utils/fs.test.d.ts.map +1 -0
- package/dist/utils/fs.test.js +82 -0
- package/dist/utils/fs.test.js.map +1 -0
- package/dist/utils/safe-env.d.ts +10 -0
- package/dist/utils/safe-env.d.ts.map +1 -0
- package/dist/utils/safe-env.js +40 -0
- package/dist/utils/safe-env.js.map +1 -0
- package/package.json +3 -1
package/dist/renderer/layout.js
CHANGED
|
@@ -7,13 +7,18 @@ import { renderDiff } from './diff.js';
|
|
|
7
7
|
import { isImageOutput, renderImageInline } from './image.js';
|
|
8
8
|
import { renderSessionBrowser } from './session-browser.js';
|
|
9
9
|
import { getTheme } from '../utils/theme-data.js';
|
|
10
|
-
//
|
|
11
|
-
// Style helper
|
|
10
|
+
// ── Style constants ──
|
|
12
11
|
const s = (fg, bold = false, dim = false) => ({ fg, bg: null, bold, dim, underline: false });
|
|
13
|
-
// Theme-independent styles
|
|
14
12
|
const S_TEXT = s(null);
|
|
15
13
|
const S_DIM = s(null, false, true);
|
|
16
14
|
const S_BORDER = s(null, false, true);
|
|
15
|
+
const S_BRIGHT = s(null);
|
|
16
|
+
const S_BANNER = s('cyan');
|
|
17
|
+
const S_BANNER_DIM = s(null, false, true);
|
|
18
|
+
const S_AGENT = s('cyan', true);
|
|
19
|
+
const S_KEY_GREEN = s('green', true);
|
|
20
|
+
const S_KEY_RED = s('red', true);
|
|
21
|
+
const S_KEY_CYAN = s('cyan', true);
|
|
17
22
|
// Theme-dependent styles — lazily initialized on first rasterize() call
|
|
18
23
|
let S_USER;
|
|
19
24
|
let S_ASSISTANT;
|
|
@@ -37,272 +42,101 @@ function ensureStyles() {
|
|
|
37
42
|
S_GREEN = s(t.success);
|
|
38
43
|
}
|
|
39
44
|
const SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Footer height — capped at 50% of terminal to preserve message area
|
|
49
|
-
const companionHeight = state.companionLines ? Math.min(state.companionLines.length + 1, 8) : 0;
|
|
50
|
-
const maxDiffHeight = Math.min(15, Math.floor(h / 3));
|
|
51
|
-
const diffHeight = (state.permissionDiffVisible && state.permissionDiffInfo) ? maxDiffHeight : 0;
|
|
52
|
-
const permissionHeight = state.permissionBox ? 6 + diffHeight : 0;
|
|
53
|
-
const questionHeight = state.questionPrompt ? 4 + (state.questionPrompt.options?.length ?? 0) : 0;
|
|
54
|
-
const statusLineHeight = state.statusLine ? 1 : 0;
|
|
55
|
-
const contextWarningHeight = state.contextWarning ? 1 : 0;
|
|
56
|
-
const autocompleteHeight = state.autocomplete.length;
|
|
57
|
-
const inputLineCount = Math.min(5, (state.inputText.match(/\n/g)?.length ?? 0) + 1);
|
|
58
|
-
const rawFooterHeight = Math.max(2 + inputLineCount + statusLineHeight + autocompleteHeight, companionHeight + 1) + permissionHeight + questionHeight + contextWarningHeight;
|
|
59
|
-
const footerHeight = Math.min(rawFooterHeight, Math.floor(h / 2));
|
|
60
|
-
const msgAreaHeight = Math.max(1, h - footerHeight);
|
|
61
|
-
// ── Session browser overlay ──
|
|
62
|
-
if (state.sessionBrowser) {
|
|
63
|
-
const browserRows = renderSessionBrowser(grid, 0, 0, state.sessionBrowser, w, msgAreaHeight);
|
|
64
|
-
// Skip normal message rendering — show browser instead
|
|
65
|
-
const footerStart = Math.min(browserRows, msgAreaHeight);
|
|
66
|
-
// Render minimal footer (just input)
|
|
67
|
-
for (let c = 0; c < w; c++)
|
|
68
|
-
grid.setCell(footerStart, c, '─', S_BORDER);
|
|
69
|
-
const inputRow = footerStart + 1;
|
|
70
|
-
grid.writeText(inputRow, 0, '❯ ', S_USER);
|
|
71
|
-
grid.writeText(inputRow + 1, 0, '↑/↓ navigate | Enter resume | Esc cancel', S_DIM);
|
|
72
|
-
return { cursorRow: inputRow, cursorCol: 2 };
|
|
73
|
-
}
|
|
74
|
-
// ── Messages area (top) ──
|
|
75
|
-
// Compute total height of all messages + streaming
|
|
76
|
-
const allContent = [];
|
|
77
|
-
for (const msg of state.messages) {
|
|
78
|
-
if (msg.role === 'user') {
|
|
79
|
-
allContent.push({ role: 'user', content: msg.content, style: { ...S_TEXT, bold: true }, prefixStyle: S_USER, prefix: '❯ ' });
|
|
80
|
-
}
|
|
81
|
-
else if (msg.role === 'assistant') {
|
|
82
|
-
allContent.push({ role: 'assistant', content: msg.content, style: S_TEXT, prefixStyle: S_ASSISTANT, prefix: '◆ ' });
|
|
83
|
-
}
|
|
84
|
-
else if (msg.role === 'system') {
|
|
85
|
-
allContent.push({ role: 'system', content: msg.content, style: S_DIM, prefixStyle: S_DIM, prefix: ' ' });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
// Add streaming text
|
|
89
|
-
if (state.loading && state.streamingText) {
|
|
90
|
-
allContent.push({ role: 'streaming', content: state.streamingText, style: S_TEXT, prefixStyle: S_ASSISTANT, prefix: '◆ ' });
|
|
91
|
-
}
|
|
92
|
-
// Thinking text rendered directly below (shimmer effect needs per-char styling)
|
|
93
|
-
// Spinner rendered directly below (shimmer effect needs per-char styling)
|
|
94
|
-
// Add error
|
|
95
|
-
if (state.errorText) {
|
|
96
|
-
allContent.push({ role: 'error', content: state.errorText, style: S_ERROR, prefixStyle: S_ERROR, prefix: '✗ ' });
|
|
97
|
-
}
|
|
98
|
-
// Render messages top-down (scroll up when content exceeds area)
|
|
99
|
-
const prefixLen = 2;
|
|
100
|
-
const contentWidth = w - 1; // reserve rightmost column for scrollbar
|
|
101
|
-
const textWidth = contentWidth - prefixLen;
|
|
102
|
-
// Pre-compute total height to handle scrolling
|
|
103
|
-
let totalRows = 0;
|
|
104
|
-
// Banner height (compact on small terminals, hidden if very small)
|
|
105
|
-
if (state.bannerLines && h >= 30) {
|
|
106
|
-
const compact = h < 40;
|
|
107
|
-
const visibleLines = compact ? Math.min(2, state.bannerLines.length) : state.bannerLines.length;
|
|
108
|
-
totalRows += visibleLines + 1; // +1 blank line after
|
|
109
|
-
}
|
|
110
|
-
for (const item of allContent) {
|
|
111
|
-
if (item.role === 'user' && totalRows > 0)
|
|
112
|
-
totalRows++;
|
|
113
|
-
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
114
|
-
totalRows += measureMarkdown(item.content, contentWidth);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
const lines = item.content.split('\n');
|
|
118
|
-
for (const line of lines) {
|
|
119
|
-
totalRows += Math.max(1, Math.ceil((line.length || 1) / textWidth));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Include non-message content in totalRows for accurate scroll indicator
|
|
124
|
-
if (state.thinkingText) {
|
|
125
|
-
totalRows += state.thinkingExpanded ? Math.min(state.thinkingText.split('\n').length, 10) : 1;
|
|
126
|
-
}
|
|
127
|
-
if (!state.loading && state.lastThinkingSummary)
|
|
128
|
-
totalRows += 1;
|
|
129
|
-
if (state.loading && !state.streamingText && !state.thinkingText)
|
|
130
|
-
totalRows += 1; // spinner
|
|
131
|
-
for (const [callId, tc] of state.toolCalls) {
|
|
132
|
-
totalRows += 1; // tool header line
|
|
133
|
-
if (tc.isAgent && tc.agentDescription)
|
|
134
|
-
totalRows += 1; // agent description line
|
|
135
|
-
if (tc.status === 'running' && tc.liveOutput)
|
|
136
|
-
totalRows += Math.min(tc.liveOutput.length, 5);
|
|
137
|
-
// Collapsed tools show 0 output lines; expanded show up to 20
|
|
138
|
-
if (tc.output && tc.status !== 'running' && state.expandedToolCalls.has(callId)) {
|
|
139
|
-
totalRows += Math.min(tc.output.split('\n').length, 20);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (state.contextWarning)
|
|
143
|
-
totalRows += 1;
|
|
144
|
-
// If content exceeds area, scroll: offset so latest content is visible at bottom
|
|
145
|
-
// manualScroll > 0 means user scrolled up by that many rows
|
|
146
|
-
const autoOffset = totalRows > msgAreaHeight ? totalRows - msgAreaHeight : 0;
|
|
147
|
-
const scrollOffset = Math.max(0, autoOffset - state.manualScroll);
|
|
148
|
-
// Scrollbar geometry (rightmost column of message area)
|
|
149
|
-
const hasScrollbar = totalRows > msgAreaHeight;
|
|
150
|
-
let thumbStart = 0;
|
|
151
|
-
let thumbSize = msgAreaHeight;
|
|
152
|
-
if (hasScrollbar) {
|
|
153
|
-
thumbSize = Math.max(1, Math.round((msgAreaHeight / totalRows) * msgAreaHeight));
|
|
154
|
-
thumbStart = Math.round((scrollOffset / Math.max(1, totalRows)) * (msgAreaHeight - thumbSize));
|
|
155
|
-
}
|
|
156
|
-
let r = 0;
|
|
157
|
-
let virtualR = 0; // tracks position before scroll clipping
|
|
158
|
-
let contentIdx = 0;
|
|
159
|
-
// ── Banner (ASCII art at top) ──
|
|
160
|
-
if (state.bannerLines && h >= 30) {
|
|
161
|
-
const S_BANNER = s('cyan');
|
|
162
|
-
const S_BANNER_DIM = s(null, false, true);
|
|
163
|
-
// On small terminals, show only the last 2 lines (version + cwd info)
|
|
164
|
-
const compact = h < 40;
|
|
165
|
-
const startLine = compact ? Math.max(0, state.bannerLines.length - 2) : 0;
|
|
166
|
-
for (let i = startLine; i < state.bannerLines.length; i++) {
|
|
167
|
-
if (virtualR >= scrollOffset && r < msgAreaHeight) {
|
|
168
|
-
const line = state.bannerLines[i];
|
|
169
|
-
const isBannerArt = i < state.bannerLines.length - 2;
|
|
170
|
-
grid.writeText(r, 0, line, isBannerArt ? S_BANNER : S_BANNER_DIM);
|
|
171
|
-
r++;
|
|
172
|
-
}
|
|
173
|
-
virtualR++;
|
|
174
|
-
}
|
|
175
|
-
// Blank line after banner
|
|
176
|
-
if (virtualR >= scrollOffset && r < msgAreaHeight) {
|
|
177
|
-
r++;
|
|
178
|
-
}
|
|
179
|
-
virtualR++;
|
|
180
|
-
}
|
|
181
|
-
for (const item of allContent) {
|
|
182
|
-
if (r >= msgAreaHeight)
|
|
45
|
+
// ── Shared rendering helpers ──
|
|
46
|
+
// Each takes (state, grid, row, limit, ...options) and returns next row.
|
|
47
|
+
function renderBannerSection(state, grid, r, limit, opts) {
|
|
48
|
+
if (!state.bannerLines)
|
|
49
|
+
return r;
|
|
50
|
+
const startLine = opts.compact ? Math.max(0, state.bannerLines.length - 2) : 0;
|
|
51
|
+
for (let i = startLine; i < state.bannerLines.length; i++) {
|
|
52
|
+
if (r >= limit)
|
|
183
53
|
break;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
grid.setCell(r, c, '─', S_BORDER);
|
|
189
|
-
}
|
|
190
|
-
r++;
|
|
191
|
-
}
|
|
192
|
-
virtualR++;
|
|
193
|
-
}
|
|
194
|
-
// Compute how many rows this content will take
|
|
195
|
-
let itemRows;
|
|
196
|
-
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
197
|
-
itemRows = measureMarkdown(item.content, contentWidth);
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
const lines = item.content.split('\n');
|
|
201
|
-
itemRows = 0;
|
|
202
|
-
for (const line of lines) {
|
|
203
|
-
itemRows += Math.max(1, Math.ceil((line.length || 1) / textWidth));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (virtualR + itemRows <= scrollOffset) {
|
|
207
|
-
// Entirely above viewport — skip
|
|
208
|
-
virtualR += itemRows;
|
|
209
|
-
contentIdx++;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
// Write prefix
|
|
213
|
-
grid.writeText(r, 0, item.prefix, item.prefixStyle);
|
|
214
|
-
// Write content — use markdown renderer for assistant messages
|
|
215
|
-
let rows;
|
|
216
|
-
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
217
|
-
rows = renderMarkdown(grid, r, prefixLen, item.content, contentWidth, state.codeBlocksExpanded, msgAreaHeight);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
rows = grid.writeWrapped(r, prefixLen, item.content, item.style, contentWidth, msgAreaHeight);
|
|
221
|
-
}
|
|
222
|
-
r += rows;
|
|
223
|
-
virtualR += itemRows;
|
|
224
|
-
contentIdx++;
|
|
54
|
+
const line = state.bannerLines[i];
|
|
55
|
+
const isBannerArt = i < state.bannerLines.length - 2;
|
|
56
|
+
grid.writeText(r, 0, line, isBannerArt ? S_BANNER : S_BANNER_DIM);
|
|
57
|
+
r++;
|
|
225
58
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
59
|
+
if (r < limit)
|
|
60
|
+
r++; // blank line after banner
|
|
61
|
+
return r;
|
|
62
|
+
}
|
|
63
|
+
function renderThinkingSection(state, grid, r, limit) {
|
|
64
|
+
if (!state.thinkingText || r >= limit)
|
|
65
|
+
return r;
|
|
66
|
+
const w = grid.width;
|
|
67
|
+
if (state.thinkingExpanded) {
|
|
68
|
+
const thinkLines = state.thinkingText.split('\n').slice(-10);
|
|
69
|
+
const shimmerPos = state.spinnerFrame % 20;
|
|
70
|
+
for (const tLine of thinkLines) {
|
|
71
|
+
if (r >= limit)
|
|
72
|
+
break;
|
|
73
|
+
grid.writeText(r, 0, '💭 ', S_DIM);
|
|
74
|
+
const chars = [...tLine];
|
|
75
|
+
for (let ci = 0; ci < chars.length && ci + 3 < w; ci++) {
|
|
76
|
+
grid.setCell(r, 3 + ci, chars[ci], Math.abs(ci - shimmerPos) <= 2 ? S_BRIGHT : S_DIM);
|
|
242
77
|
}
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
// Collapsed: single line with live indicator
|
|
246
|
-
const lineCount = state.thinkingText.split('\n').length;
|
|
247
|
-
const elapsed = state.thinkingStartedAt ? Math.floor((Date.now() - state.thinkingStartedAt) / 1000) : 0;
|
|
248
|
-
const summary = `∴ Thinking${elapsed > 0 ? ` (${elapsed}s)` : ''} — ${lineCount} lines [Ctrl+O expand]`;
|
|
249
|
-
grid.writeText(r, 0, summary, S_DIM);
|
|
250
|
-
r++;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
// ── Collapsed thinking summary (after completion) ──
|
|
254
|
-
if (!state.loading && state.lastThinkingSummary && r < msgAreaHeight) {
|
|
255
|
-
if (state.thinkingExpanded) {
|
|
256
|
-
// Expanded mode not applicable after completion since text was cleared
|
|
257
|
-
grid.writeText(r, 0, state.lastThinkingSummary, S_DIM);
|
|
258
|
-
r++;
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
grid.writeText(r, 0, state.lastThinkingSummary, S_DIM);
|
|
262
78
|
r++;
|
|
263
79
|
}
|
|
264
80
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const thinkText = 'Thinking';
|
|
81
|
+
else {
|
|
82
|
+
const lineCount = state.thinkingText.split('\n').length;
|
|
268
83
|
const elapsed = state.thinkingStartedAt ? Math.floor((Date.now() - state.thinkingStartedAt) / 1000) : 0;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const baseColor = elapsed > 60 ? t.error : elapsed > 30 ? t.stall : t.primary;
|
|
272
|
-
const shimmerColor = elapsed > 60 ? t.stallShimmer : elapsed > 30 ? t.warning : t.primaryShimmer;
|
|
273
|
-
const baseStyle = { fg: baseColor, bg: null, bold: false, dim: false, underline: false };
|
|
274
|
-
// Prefix
|
|
275
|
-
const prefixStyle = { ...baseStyle, bold: true };
|
|
276
|
-
grid.writeText(r, 0, '◆ ', prefixStyle);
|
|
277
|
-
// Shimmer effect: bright color sweeps across text
|
|
278
|
-
const shimmerPos = state.spinnerFrame % (thinkText.length + 6);
|
|
279
|
-
const shimmerStyle = { fg: shimmerColor, bg: null, bold: true, dim: false, underline: false };
|
|
280
|
-
for (let ci = 0; ci < thinkText.length; ci++) {
|
|
281
|
-
grid.setCell(r, 2 + ci, thinkText[ci], Math.abs(ci - shimmerPos) <= 1 ? shimmerStyle : baseStyle);
|
|
282
|
-
}
|
|
283
|
-
// Suffix: elapsed + tokens
|
|
284
|
-
let suffix = '';
|
|
285
|
-
if (elapsed > 0)
|
|
286
|
-
suffix += ` ${elapsed}s`;
|
|
287
|
-
if (state.tokenCount > 0) {
|
|
288
|
-
const tokStr = state.tokenCount >= 1000 ? `${(state.tokenCount / 1000).toFixed(1)}K` : `${state.tokenCount}`;
|
|
289
|
-
suffix += ` | ${tokStr} tokens`;
|
|
290
|
-
}
|
|
291
|
-
suffix += '...';
|
|
292
|
-
grid.writeText(r, 2 + thinkText.length, suffix, S_DIM);
|
|
84
|
+
const summary = `∴ Thinking${elapsed > 0 ? ` (${elapsed}s)` : ''} — ${lineCount} lines [Ctrl+O expand]`;
|
|
85
|
+
grid.writeText(r, 0, summary, S_DIM);
|
|
293
86
|
r++;
|
|
294
87
|
}
|
|
295
|
-
|
|
88
|
+
return r;
|
|
89
|
+
}
|
|
90
|
+
function renderThinkingSummarySection(state, grid, r, limit) {
|
|
91
|
+
if (state.loading || !state.lastThinkingSummary || r >= limit)
|
|
92
|
+
return r;
|
|
93
|
+
grid.writeText(r, 0, state.lastThinkingSummary, S_DIM);
|
|
94
|
+
return r + 1;
|
|
95
|
+
}
|
|
96
|
+
function renderSpinnerSection(state, grid, r, limit) {
|
|
97
|
+
if (!state.loading || state.streamingText || state.thinkingText || r >= limit)
|
|
98
|
+
return r;
|
|
99
|
+
const w = grid.width;
|
|
100
|
+
const thinkText = 'Thinking';
|
|
101
|
+
const elapsed = state.thinkingStartedAt ? Math.floor((Date.now() - state.thinkingStartedAt) / 1000) : 0;
|
|
102
|
+
const t = getTheme();
|
|
103
|
+
const baseColor = elapsed > 60 ? t.error : elapsed > 30 ? t.stall : t.primary;
|
|
104
|
+
const shimmerColor = elapsed > 60 ? t.stallShimmer : elapsed > 30 ? t.warning : t.primaryShimmer;
|
|
105
|
+
const baseStyle = { fg: baseColor, bg: null, bold: false, dim: false, underline: false };
|
|
106
|
+
grid.writeText(r, 0, '◆ ', { ...baseStyle, bold: true });
|
|
107
|
+
const shimmerPos = state.spinnerFrame % (thinkText.length + 6);
|
|
108
|
+
const shimmerStyle = { fg: shimmerColor, bg: null, bold: true, dim: false, underline: false };
|
|
109
|
+
for (let ci = 0; ci < thinkText.length; ci++) {
|
|
110
|
+
grid.setCell(r, 2 + ci, thinkText[ci], Math.abs(ci - shimmerPos) <= 1 ? shimmerStyle : baseStyle);
|
|
111
|
+
}
|
|
112
|
+
let suffix = '';
|
|
113
|
+
if (elapsed > 0)
|
|
114
|
+
suffix += ` ${elapsed}s`;
|
|
115
|
+
if (state.tokenCount > 0) {
|
|
116
|
+
const tokStr = state.tokenCount >= 1000 ? `${(state.tokenCount / 1000).toFixed(1)}K` : `${state.tokenCount}`;
|
|
117
|
+
suffix += ` | ${tokStr} tokens`;
|
|
118
|
+
}
|
|
119
|
+
suffix += '...';
|
|
120
|
+
grid.writeText(r, 2 + thinkText.length, suffix, S_DIM);
|
|
121
|
+
return r + 1;
|
|
122
|
+
}
|
|
123
|
+
function renderErrorSection(state, grid, r, limit) {
|
|
124
|
+
if (!state.errorText || r >= limit)
|
|
125
|
+
return r;
|
|
126
|
+
const w = grid.width;
|
|
127
|
+
grid.writeText(r, 0, '✗ ', S_ERROR);
|
|
128
|
+
grid.writeText(r, 2, state.errorText.slice(0, w - 4), S_ERROR);
|
|
129
|
+
return r + 1;
|
|
130
|
+
}
|
|
131
|
+
function renderToolCallsSection(state, grid, r, limit, opts) {
|
|
132
|
+
const w = grid.width;
|
|
296
133
|
for (const [callId, tc] of state.toolCalls) {
|
|
297
|
-
if (r >=
|
|
134
|
+
if (r >= limit)
|
|
298
135
|
break;
|
|
299
|
-
const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
300
136
|
const isAgent = tc.isAgent || tc.toolName === 'Agent' || tc.toolName === 'ParallelAgents';
|
|
301
|
-
// Agent-specific icons and colors
|
|
302
137
|
const icon = isAgent
|
|
303
138
|
? (tc.status === 'running' ? '⊕' : tc.status === 'done' ? '◈' : '◇')
|
|
304
|
-
: (tc.status === 'running' ?
|
|
305
|
-
const S_AGENT = { fg: 'cyan', bg: null, bold: true, dim: false, underline: false };
|
|
139
|
+
: (tc.status === 'running' ? SPINNER_CHARS[state.spinnerFrame % SPINNER_CHARS.length] : tc.status === 'done' ? '✓' : '✗');
|
|
306
140
|
const statusStyle = tc.status === 'error' ? S_ERROR : tc.status === 'done' ? S_GREEN : isAgent ? S_AGENT : S_YELLOW;
|
|
307
141
|
const nameStyle = isAgent ? S_AGENT : { ...S_YELLOW, bold: true };
|
|
308
142
|
const isExpanded = state.expandedToolCalls.has(callId);
|
|
@@ -313,10 +147,9 @@ export function rasterize(state, grid) {
|
|
|
313
147
|
}
|
|
314
148
|
grid.writeText(r, 2, `${icon} `, statusStyle);
|
|
315
149
|
grid.writeText(r, 4, tc.toolName, nameStyle);
|
|
316
|
-
// Show args + elapsed time on the same line
|
|
317
150
|
let afterName = 4 + tc.toolName.length + 1;
|
|
318
151
|
if (tc.args) {
|
|
319
|
-
const maxArgs = w - afterName - 15;
|
|
152
|
+
const maxArgs = w - afterName - 15;
|
|
320
153
|
if (maxArgs > 5) {
|
|
321
154
|
const argsText = tc.args.slice(0, maxArgs) + (tc.args.length > maxArgs ? '…' : '');
|
|
322
155
|
grid.writeText(r, afterName, argsText, S_DIM);
|
|
@@ -332,7 +165,7 @@ export function rasterize(state, grid) {
|
|
|
332
165
|
grid.writeText(r, Math.min(afterName, w - elapsedStr.length - 2), elapsedStr, S_DIM);
|
|
333
166
|
}
|
|
334
167
|
}
|
|
335
|
-
// Result summary for completed tools
|
|
168
|
+
// Result summary for completed tools
|
|
336
169
|
if (tc.status !== 'running' && tc.resultSummary) {
|
|
337
170
|
const elapsed = tc.startedAt ? Math.floor((Date.now() - tc.startedAt) / 1000) : 0;
|
|
338
171
|
const suffix = elapsed > 0 ? `${tc.resultSummary} · ${elapsed}s` : tc.resultSummary;
|
|
@@ -340,28 +173,27 @@ export function rasterize(state, grid) {
|
|
|
340
173
|
}
|
|
341
174
|
r++;
|
|
342
175
|
// Agent description line
|
|
343
|
-
if (isAgent && tc.agentDescription && r <
|
|
176
|
+
if (isAgent && tc.agentDescription && r < limit) {
|
|
344
177
|
grid.writeText(r, 6, tc.agentDescription.slice(0, w - 8), S_DIM);
|
|
345
178
|
r++;
|
|
346
179
|
}
|
|
347
180
|
// Live streaming output while running
|
|
348
181
|
if (tc.status === 'running' && tc.liveOutput && tc.liveOutput.length > 0) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
if (overflow > 0 && r < msgAreaHeight) {
|
|
182
|
+
const overflow = tc.liveOutput.length > opts.maxLiveLines ? tc.liveOutput.length - opts.maxLiveLines : 0;
|
|
183
|
+
if (opts.showOverflow && overflow > 0 && r < limit) {
|
|
352
184
|
grid.writeText(r, 6, `… (${overflow} earlier lines)`, S_DIM);
|
|
353
185
|
r++;
|
|
354
186
|
}
|
|
355
|
-
const visible = overflow > 0 ? tc.liveOutput.slice(-
|
|
187
|
+
const visible = overflow > 0 ? tc.liveOutput.slice(-opts.maxLiveLines) : tc.liveOutput;
|
|
356
188
|
for (const line of visible) {
|
|
357
|
-
if (r >=
|
|
189
|
+
if (r >= limit)
|
|
358
190
|
break;
|
|
359
191
|
grid.writeText(r, 6, line.slice(0, w - 8), S_DIM);
|
|
360
192
|
r++;
|
|
361
193
|
}
|
|
362
194
|
}
|
|
363
195
|
// Final output — collapsed by default (only show when expanded via Tab)
|
|
364
|
-
if (tc.output && tc.status !== 'running' && isExpanded && r <
|
|
196
|
+
if (tc.output && tc.status !== 'running' && isExpanded && r < limit) {
|
|
365
197
|
// Image results: show inline placeholder
|
|
366
198
|
if (isImageOutput(tc.output)) {
|
|
367
199
|
const label = renderImageInline(tc.output);
|
|
@@ -373,68 +205,40 @@ export function rasterize(state, grid) {
|
|
|
373
205
|
const maxOut = 20;
|
|
374
206
|
const showLines = outLines.slice(0, maxOut);
|
|
375
207
|
for (const line of showLines) {
|
|
376
|
-
if (r >=
|
|
208
|
+
if (r >= limit)
|
|
377
209
|
break;
|
|
378
210
|
const lineStyle = tc.status === 'error' ? S_ERROR : S_DIM;
|
|
379
211
|
grid.writeText(r, 6, line.slice(0, w - 8), lineStyle);
|
|
380
212
|
r++;
|
|
381
213
|
}
|
|
382
|
-
if (outLines.length > maxOut && r <
|
|
214
|
+
if (outLines.length > maxOut && r < limit) {
|
|
383
215
|
grid.writeText(r, 6, `… (${outLines.length} lines total)`, S_DIM);
|
|
384
216
|
r++;
|
|
385
217
|
}
|
|
386
218
|
}
|
|
387
219
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// Border line with scroll indicator
|
|
408
|
-
for (let c = 0; c < w; c++) {
|
|
409
|
-
grid.setCell(footerStart, c, '─', S_BORDER);
|
|
410
|
-
}
|
|
411
|
-
// Connect scrollbar to footer border
|
|
412
|
-
if (hasScrollbar) {
|
|
413
|
-
grid.setCell(footerStart, w - 1, '┤', S_BORDER);
|
|
414
|
-
}
|
|
415
|
-
if (state.manualScroll > 0 && totalRows > msgAreaHeight) {
|
|
416
|
-
// User scrolled up — show how many lines are hidden below
|
|
417
|
-
const hiddenBelow = state.manualScroll;
|
|
418
|
-
const indicator = ` ↓ ${hiddenBelow} more below `;
|
|
419
|
-
const startCol = Math.max(0, Math.floor((w - indicator.length) / 2));
|
|
420
|
-
grid.writeText(footerStart, startCol, indicator, S_DIM);
|
|
421
|
-
}
|
|
422
|
-
else if (totalRows > msgAreaHeight && scrollOffset > 0) {
|
|
423
|
-
// Content overflows but auto-scrolled to bottom — show lines hidden above
|
|
424
|
-
const indicator = ` ↑ ${scrollOffset} more above `;
|
|
425
|
-
const startCol = Math.max(0, Math.floor((w - indicator.length) / 2));
|
|
426
|
-
grid.writeText(footerStart, startCol, indicator, S_DIM);
|
|
427
|
-
}
|
|
428
|
-
let nextRow = footerStart + 1;
|
|
429
|
-
// Permission prompt box (if active, skip if terminal too small)
|
|
430
|
-
// Ensure at least 6 rows available for the box (tool + desc + keys + borders)
|
|
431
|
-
if (state.permissionBox && w >= 20 && (h - nextRow) >= 6) {
|
|
432
|
-
const { toolName, description, riskLevel } = state.permissionBox;
|
|
433
|
-
const riskColor = riskLevel === 'high' ? 'red' : riskLevel === 'medium' ? 'yellow' : 'green';
|
|
434
|
-
const riskStyle = { fg: riskColor, bg: null, bold: true, dim: false, underline: false };
|
|
220
|
+
return r;
|
|
221
|
+
}
|
|
222
|
+
function renderContextWarningSection(state, grid, r, limit) {
|
|
223
|
+
if (!state.contextWarning || r >= limit)
|
|
224
|
+
return r;
|
|
225
|
+
const warnStyle = { fg: 'yellow', bg: null, bold: state.contextWarning.critical, dim: false, underline: false };
|
|
226
|
+
grid.writeText(r, 0, state.contextWarning.text, warnStyle);
|
|
227
|
+
return r + 1;
|
|
228
|
+
}
|
|
229
|
+
function renderPermissionBoxSection(state, grid, nextRow, h, opts) {
|
|
230
|
+
if (!state.permissionBox || grid.width < 20)
|
|
231
|
+
return nextRow;
|
|
232
|
+
const w = grid.width;
|
|
233
|
+
const { toolName, description, riskLevel } = state.permissionBox;
|
|
234
|
+
const riskColor = riskLevel === 'high' ? 'red' : riskLevel === 'medium' ? 'yellow' : 'green';
|
|
235
|
+
const riskStyle = { fg: riskColor, bg: null, bold: true, dim: false, underline: false };
|
|
236
|
+
if (opts.boxed) {
|
|
237
|
+
if ((h - nextRow) < 6)
|
|
238
|
+
return nextRow;
|
|
435
239
|
const riskDim = { fg: riskColor, bg: null, bold: false, dim: true, underline: false };
|
|
436
|
-
// Top border
|
|
437
240
|
const boxWidth = Math.max(15, Math.min(w - 2, 70));
|
|
241
|
+
// Top border
|
|
438
242
|
grid.writeText(nextRow, 1, '╭' + '─'.repeat(boxWidth - 2) + '╮', riskDim);
|
|
439
243
|
nextRow++;
|
|
440
244
|
// Tool name + risk
|
|
@@ -446,18 +250,17 @@ export function rasterize(state, grid) {
|
|
|
446
250
|
nextRow++;
|
|
447
251
|
// Description (truncated)
|
|
448
252
|
const rawDesc = state.permissionBox.suggestion || description.slice(0, boxWidth - 6);
|
|
449
|
-
const descText = rawDesc.replace(/\|/g, ' ').replace(/\\/g, '/');
|
|
253
|
+
const descText = rawDesc.replace(/\|/g, ' ').replace(/\\/g, '/');
|
|
450
254
|
grid.writeText(nextRow, 1, '│ ', riskDim);
|
|
451
255
|
grid.writeText(nextRow, 3, descText.slice(0, boxWidth - 4), S_DIM);
|
|
452
256
|
grid.writeText(nextRow, boxWidth, '│', riskDim);
|
|
453
257
|
nextRow++;
|
|
454
|
-
// Inline diff
|
|
258
|
+
// Inline diff
|
|
455
259
|
if (state.permissionDiffVisible && state.permissionDiffInfo && nextRow + 3 < h) {
|
|
456
260
|
grid.writeText(nextRow, 1, '│', riskDim);
|
|
457
261
|
nextRow++;
|
|
458
|
-
const availDiffRows = Math.min(maxDiffHeight, h - nextRow - 3);
|
|
262
|
+
const availDiffRows = Math.min(opts.maxDiffHeight, h - nextRow - 3);
|
|
459
263
|
const diffRows = renderDiff(grid, nextRow, 3, state.permissionDiffInfo, boxWidth - 2, availDiffRows);
|
|
460
|
-
// Draw left border for diff rows
|
|
461
264
|
for (let dr = 0; dr < diffRows; dr++) {
|
|
462
265
|
if (nextRow + dr < grid.height) {
|
|
463
266
|
grid.setCell(nextRow + dr, 1, '│', riskDim);
|
|
@@ -466,11 +269,7 @@ export function rasterize(state, grid) {
|
|
|
466
269
|
}
|
|
467
270
|
nextRow += diffRows;
|
|
468
271
|
}
|
|
469
|
-
// Action keys
|
|
470
|
-
const hasDiff = state.permissionDiffInfo !== null;
|
|
471
|
-
const S_KEY_GREEN = { fg: 'green', bg: null, bold: true, dim: false, underline: false };
|
|
472
|
-
const S_KEY_RED = { fg: 'red', bg: null, bold: true, dim: false, underline: false };
|
|
473
|
-
const S_KEY_CYAN = { fg: 'cyan', bg: null, bold: true, dim: false, underline: false };
|
|
272
|
+
// Action keys
|
|
474
273
|
grid.writeText(nextRow, 1, '│ ', riskDim);
|
|
475
274
|
let kc = 3;
|
|
476
275
|
grid.writeText(nextRow, kc, 'Y', S_KEY_GREEN);
|
|
@@ -483,7 +282,7 @@ export function rasterize(state, grid) {
|
|
|
483
282
|
kc += 1;
|
|
484
283
|
grid.writeText(nextRow, kc, 'o', S_DIM);
|
|
485
284
|
kc += 1;
|
|
486
|
-
if (
|
|
285
|
+
if (state.permissionDiffInfo) {
|
|
487
286
|
grid.writeText(nextRow, kc, ' ', S_DIM);
|
|
488
287
|
kc += 2;
|
|
489
288
|
grid.writeText(nextRow, kc, 'D', S_KEY_CYAN);
|
|
@@ -497,11 +296,37 @@ export function rasterize(state, grid) {
|
|
|
497
296
|
grid.writeText(nextRow, 1, '╰' + '─'.repeat(boxWidth - 2) + '╯', riskDim);
|
|
498
297
|
nextRow++;
|
|
499
298
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
299
|
+
else {
|
|
300
|
+
// Compact mode (rasterizeLive)
|
|
301
|
+
if ((h - nextRow) < 4)
|
|
302
|
+
return nextRow;
|
|
303
|
+
grid.writeText(nextRow, 1, `⚠ ${toolName} (${riskLevel} risk)`, riskStyle);
|
|
304
|
+
nextRow++;
|
|
305
|
+
grid.writeText(nextRow, 1, 'Y', S_KEY_GREEN);
|
|
306
|
+
grid.writeText(nextRow, 2, 'es ', S_DIM);
|
|
307
|
+
grid.writeText(nextRow, 6, 'N', S_KEY_RED);
|
|
308
|
+
grid.writeText(nextRow, 7, 'o', S_DIM);
|
|
309
|
+
if (state.permissionDiffInfo) {
|
|
310
|
+
grid.writeText(nextRow, 10, 'D', S_KEY_CYAN);
|
|
311
|
+
grid.writeText(nextRow, 11, 'iff', S_DIM);
|
|
312
|
+
}
|
|
313
|
+
nextRow++;
|
|
314
|
+
// Inline diff (when toggled)
|
|
315
|
+
if (state.permissionDiffVisible && state.permissionDiffInfo && nextRow + 3 < h) {
|
|
316
|
+
const availDiffRows = Math.min(15, h - nextRow - 3);
|
|
317
|
+
const diffRows = renderDiff(grid, nextRow, 3, state.permissionDiffInfo, Math.min(w - 2, 70), availDiffRows);
|
|
318
|
+
nextRow += diffRows;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return nextRow;
|
|
322
|
+
}
|
|
323
|
+
function renderQuestionPromptSection(state, grid, nextRow, h, opts) {
|
|
324
|
+
if (!state.questionPrompt || grid.width < 20)
|
|
325
|
+
return { nextRow, questionInputRow: -1 };
|
|
326
|
+
const w = grid.width;
|
|
327
|
+
const { question, options, input, cursor } = state.questionPrompt;
|
|
328
|
+
const qStyle = { fg: 'yellow', bg: null, bold: false, dim: false, underline: false };
|
|
329
|
+
if (opts.boxed) {
|
|
505
330
|
const qBorder = { fg: 'yellow', bg: null, bold: false, dim: true, underline: false };
|
|
506
331
|
const qBoxWidth = Math.max(15, Math.min(w - 2, 70));
|
|
507
332
|
grid.writeText(nextRow, 1, '╭' + '─'.repeat(qBoxWidth - 2) + '╮', qBorder);
|
|
@@ -518,7 +343,7 @@ export function rasterize(state, grid) {
|
|
|
518
343
|
nextRow++;
|
|
519
344
|
}
|
|
520
345
|
}
|
|
521
|
-
questionInputRow = nextRow;
|
|
346
|
+
const questionInputRow = nextRow;
|
|
522
347
|
grid.writeText(nextRow, 1, '│ ', qBorder);
|
|
523
348
|
grid.writeText(nextRow, 3, '❯ ', qStyle);
|
|
524
349
|
grid.writeText(nextRow, 5, input, S_TEXT);
|
|
@@ -526,343 +351,408 @@ export function rasterize(state, grid) {
|
|
|
526
351
|
nextRow++;
|
|
527
352
|
grid.writeText(nextRow, 1, '╰' + '─'.repeat(qBoxWidth - 2) + '╯', qBorder);
|
|
528
353
|
nextRow++;
|
|
354
|
+
return { nextRow, questionInputRow };
|
|
529
355
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
356
|
+
else {
|
|
357
|
+
// Compact mode (rasterizeLive)
|
|
358
|
+
if ((h - nextRow) < 3)
|
|
359
|
+
return { nextRow, questionInputRow: -1 };
|
|
360
|
+
grid.writeText(nextRow, 1, `❓ ${question}`, S_TEXT);
|
|
533
361
|
nextRow++;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (state.autocomplete.length > 0) {
|
|
541
|
-
for (let ai = 0; ai < state.autocomplete.length; ai++) {
|
|
542
|
-
const cmd = state.autocomplete[ai];
|
|
543
|
-
const desc = state.autocompleteDescriptions[ai] ?? '';
|
|
544
|
-
const selected = ai === state.autocompleteIndex;
|
|
545
|
-
const acStyle = selected
|
|
546
|
-
? s(getTheme().user, true)
|
|
547
|
-
: s(null, false, true);
|
|
548
|
-
grid.writeText(nextRow, promptWidth, `/${cmd.padEnd(12)}`, acStyle);
|
|
549
|
-
if (desc && w > promptWidth + 15)
|
|
550
|
-
grid.writeText(nextRow, promptWidth + 13, desc.slice(0, w - promptWidth - 15), S_DIM);
|
|
551
|
-
nextRow++;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
// Input line
|
|
555
|
-
const inputRow = nextRow;
|
|
556
|
-
let inputStart;
|
|
557
|
-
{
|
|
558
|
-
grid.writeText(inputRow, 0, promptText, S_USER);
|
|
559
|
-
inputStart = promptWidth;
|
|
560
|
-
// Multi-line input rendering
|
|
561
|
-
const inputLines = state.inputText.split('\n');
|
|
562
|
-
const maxInputLines = Math.min(inputLines.length, 5);
|
|
563
|
-
for (let li = 0; li < maxInputLines; li++) {
|
|
564
|
-
if (li === 0) {
|
|
565
|
-
grid.writeText(inputRow, inputStart, inputLines[0], S_TEXT);
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
// Align continuation to prompt position
|
|
569
|
-
grid.writeText(inputRow + li, inputStart, inputLines[li], S_TEXT);
|
|
362
|
+
if (options) {
|
|
363
|
+
for (const opt of options) {
|
|
364
|
+
if (nextRow >= h)
|
|
365
|
+
break;
|
|
366
|
+
grid.writeText(nextRow, 3, opt, S_DIM);
|
|
367
|
+
nextRow++;
|
|
570
368
|
}
|
|
571
369
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
grid.writeText(hintsRow, 0, hintsText, S_DIM);
|
|
370
|
+
const questionInputRow = nextRow;
|
|
371
|
+
grid.writeText(nextRow, 1, '❯ ', S_USER);
|
|
372
|
+
grid.writeText(nextRow, 3, input, S_TEXT);
|
|
373
|
+
nextRow++;
|
|
374
|
+
return { nextRow, questionInputRow };
|
|
578
375
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
376
|
+
}
|
|
377
|
+
function renderStatusLineSection(state, grid, nextRow, limit) {
|
|
378
|
+
if (!state.statusLine || nextRow >= limit)
|
|
379
|
+
return nextRow;
|
|
380
|
+
grid.writeText(nextRow, 0, state.statusLine, S_DIM);
|
|
381
|
+
return nextRow + 1;
|
|
382
|
+
}
|
|
383
|
+
function renderAutocompleteSection(state, grid, nextRow, limit, promptWidth) {
|
|
384
|
+
if (state.autocomplete.length === 0)
|
|
385
|
+
return nextRow;
|
|
386
|
+
const w = grid.width;
|
|
387
|
+
for (let ai = 0; ai < state.autocomplete.length; ai++) {
|
|
388
|
+
if (nextRow >= limit)
|
|
389
|
+
break;
|
|
390
|
+
const cmd = state.autocomplete[ai];
|
|
391
|
+
const desc = state.autocompleteDescriptions[ai] ?? '';
|
|
392
|
+
const selected = ai === state.autocompleteIndex;
|
|
393
|
+
const acStyle = selected ? s(getTheme().user, true) : s(null, false, true);
|
|
394
|
+
grid.writeText(nextRow, promptWidth, `/${cmd.padEnd(12)}`, acStyle);
|
|
395
|
+
if (desc && w > promptWidth + 15)
|
|
396
|
+
grid.writeText(nextRow, promptWidth + 13, desc.slice(0, w - promptWidth - 15), S_DIM);
|
|
397
|
+
nextRow++;
|
|
398
|
+
}
|
|
399
|
+
return nextRow;
|
|
400
|
+
}
|
|
401
|
+
function renderNotificationsSection(state, grid, nextRow, limit) {
|
|
402
|
+
if (!state.notifications || state.notifications.length === 0)
|
|
403
|
+
return nextRow;
|
|
404
|
+
for (const note of state.notifications.slice(-2)) {
|
|
405
|
+
if (nextRow >= limit)
|
|
406
|
+
break;
|
|
407
|
+
grid.writeText(nextRow, 0, ` ⚡ ${note.text}`, S_YELLOW);
|
|
408
|
+
nextRow++;
|
|
409
|
+
}
|
|
410
|
+
return nextRow;
|
|
411
|
+
}
|
|
412
|
+
function renderInputSection(state, grid, inputRow, limit, promptText, promptWidth) {
|
|
413
|
+
grid.writeText(inputRow, 0, promptText, S_USER);
|
|
414
|
+
const inputStart = promptWidth;
|
|
415
|
+
const inputLines = state.inputText.split('\n');
|
|
416
|
+
const maxInputLines = Math.min(inputLines.length, 5);
|
|
417
|
+
for (let li = 0; li < maxInputLines; li++) {
|
|
418
|
+
if (inputRow + li >= limit)
|
|
419
|
+
break;
|
|
420
|
+
if (li === 0) {
|
|
421
|
+
grid.writeText(inputRow, inputStart, inputLines[0], S_TEXT);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
grid.writeText(inputRow + li, inputStart, inputLines[li], S_TEXT);
|
|
596
425
|
}
|
|
597
426
|
}
|
|
598
|
-
//
|
|
427
|
+
// Line count indicator for multi-line input
|
|
428
|
+
if (inputLines.length > 1) {
|
|
429
|
+
const lineCountStr = ` [${inputLines.length} lines]`;
|
|
430
|
+
const lineCountCol = Math.min(inputStart + (inputLines[0]?.length ?? 0) + 1, grid.width - lineCountStr.length - 1);
|
|
431
|
+
if (lineCountCol > inputStart)
|
|
432
|
+
grid.writeText(inputRow, lineCountCol, lineCountStr, S_DIM);
|
|
433
|
+
}
|
|
434
|
+
const hintsRow = inputRow + maxInputLines;
|
|
435
|
+
if (hintsRow < limit) {
|
|
436
|
+
const hintsText = inputLines.length > 1
|
|
437
|
+
? `${state.statusHints} | Alt+Enter newline`
|
|
438
|
+
: state.statusHints;
|
|
439
|
+
grid.writeText(hintsRow, 0, hintsText, S_DIM);
|
|
440
|
+
}
|
|
441
|
+
return inputRow + maxInputLines + 1;
|
|
442
|
+
}
|
|
443
|
+
function renderCompanionSection(state, grid, anchorRow, limit, promptWidth) {
|
|
444
|
+
if (!state.companionLines || grid.width < 50)
|
|
445
|
+
return;
|
|
446
|
+
const w = grid.width;
|
|
447
|
+
const compWidth = Math.max(...state.companionLines.map(l => l.length), 0);
|
|
448
|
+
const compStartCol = Math.max(0, w - compWidth - 1);
|
|
449
|
+
if (compStartCol <= promptWidth + 20)
|
|
450
|
+
return;
|
|
451
|
+
const compStyle = { fg: state.companionColor || 'cyan', bg: null, bold: false, dim: false, underline: false };
|
|
452
|
+
for (let i = 0; i < state.companionLines.length; i++) {
|
|
453
|
+
const compRow = anchorRow + i;
|
|
454
|
+
if (compRow >= limit)
|
|
455
|
+
break;
|
|
456
|
+
grid.writeText(compRow, compStartCol, state.companionLines[i], compStyle);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function computeCursorPosition(state, inputRow, inputStart, questionInputRow) {
|
|
599
460
|
if (state.questionPrompt && questionInputRow >= 0) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
cursorCol: 5 + state.questionPrompt.cursor,
|
|
603
|
-
};
|
|
461
|
+
// In boxed mode cursor col is 5 (after "│ ❯ "), in compact mode it's 3 (after "❯ ")
|
|
462
|
+
return { cursorRow: questionInputRow, cursorCol: 5 + state.questionPrompt.cursor };
|
|
604
463
|
}
|
|
605
|
-
// 2D cursor positioning for multi-line input (all lines aligned to inputStart)
|
|
606
464
|
const textBeforeCursor = state.inputText.slice(0, state.inputCursor);
|
|
607
465
|
const cursorLines = textBeforeCursor.split('\n');
|
|
608
|
-
const cursorLineIdx = Math.min(cursorLines.length - 1, 4);
|
|
466
|
+
const cursorLineIdx = Math.min(cursorLines.length - 1, 4);
|
|
609
467
|
const cursorColInLine = cursorLines[cursorLines.length - 1].length;
|
|
610
|
-
return {
|
|
611
|
-
cursorRow: inputRow + cursorLineIdx,
|
|
612
|
-
cursorCol: inputStart + cursorColInLine,
|
|
613
|
-
};
|
|
468
|
+
return { cursorRow: inputRow + cursorLineIdx, cursorCol: inputStart + cursorColInLine };
|
|
614
469
|
}
|
|
615
|
-
|
|
470
|
+
function getPromptText(state) {
|
|
471
|
+
const vimIndicator = state.vimMode ? (state.vimMode === 'normal' ? '[N] ' : '[I] ') : '';
|
|
472
|
+
const promptText = vimIndicator + '❯ ';
|
|
473
|
+
return { promptText, promptWidth: promptText.length };
|
|
474
|
+
}
|
|
475
|
+
// ── Main rasterization functions ──
|
|
616
476
|
/**
|
|
617
|
-
* Rasterize
|
|
618
|
-
*
|
|
619
|
-
*
|
|
477
|
+
* Rasterize application state into the cell grid.
|
|
478
|
+
* Full-screen mode with message area + scrollbar + footer.
|
|
479
|
+
* Used by tests; production uses rasterizeLive().
|
|
620
480
|
*/
|
|
621
|
-
export function
|
|
481
|
+
export function rasterize(state, grid) {
|
|
622
482
|
ensureStyles();
|
|
623
483
|
const w = grid.width;
|
|
624
484
|
const h = grid.height;
|
|
625
|
-
|
|
626
|
-
|
|
485
|
+
// Footer height — capped at 50% of terminal to preserve message area
|
|
486
|
+
const companionHeight = state.companionLines ? Math.min(state.companionLines.length + 1, 8) : 0;
|
|
487
|
+
const maxDiffHeight = Math.min(15, Math.floor(h / 3));
|
|
488
|
+
const diffHeight = (state.permissionDiffVisible && state.permissionDiffInfo) ? maxDiffHeight : 0;
|
|
489
|
+
const permissionHeight = state.permissionBox ? 6 + diffHeight : 0;
|
|
490
|
+
const questionHeight = state.questionPrompt ? 4 + (state.questionPrompt.options?.length ?? 0) : 0;
|
|
491
|
+
const statusLineHeight = state.statusLine ? 1 : 0;
|
|
492
|
+
const contextWarningHeight = state.contextWarning ? 1 : 0;
|
|
493
|
+
const autocompleteHeight = state.autocomplete.length;
|
|
494
|
+
const inputLineCount = Math.min(5, (state.inputText.match(/\n/g)?.length ?? 0) + 1);
|
|
495
|
+
const rawFooterHeight = Math.max(2 + inputLineCount + statusLineHeight + autocompleteHeight, companionHeight + 1) + permissionHeight + questionHeight + contextWarningHeight;
|
|
496
|
+
const footerHeight = Math.min(rawFooterHeight, Math.floor(h / 2));
|
|
497
|
+
const msgAreaHeight = Math.max(1, h - footerHeight);
|
|
498
|
+
// ── Session browser overlay ──
|
|
499
|
+
if (state.sessionBrowser) {
|
|
500
|
+
const browserRows = renderSessionBrowser(grid, 0, 0, state.sessionBrowser, w, msgAreaHeight);
|
|
501
|
+
const footerStart = Math.min(browserRows, msgAreaHeight);
|
|
502
|
+
for (let c = 0; c < w; c++)
|
|
503
|
+
grid.setCell(footerStart, c, '─', S_BORDER);
|
|
504
|
+
const inputRow = footerStart + 1;
|
|
505
|
+
grid.writeText(inputRow, 0, '❯ ', S_USER);
|
|
506
|
+
grid.writeText(inputRow + 1, 0, '↑/↓ navigate | Enter resume | Esc cancel', S_DIM);
|
|
507
|
+
return { cursorRow: inputRow, cursorCol: 2 };
|
|
508
|
+
}
|
|
509
|
+
// ── Messages area (top) ──
|
|
510
|
+
const allContent = [];
|
|
511
|
+
for (const msg of state.messages) {
|
|
512
|
+
if (msg.role === 'user') {
|
|
513
|
+
allContent.push({ role: 'user', content: msg.content, style: { ...S_TEXT, bold: true }, prefixStyle: S_USER, prefix: '❯ ' });
|
|
514
|
+
}
|
|
515
|
+
else if (msg.role === 'assistant') {
|
|
516
|
+
allContent.push({ role: 'assistant', content: msg.content, style: S_TEXT, prefixStyle: S_ASSISTANT, prefix: '◆ ' });
|
|
517
|
+
}
|
|
518
|
+
else if (msg.role === 'system') {
|
|
519
|
+
allContent.push({ role: 'system', content: msg.content, style: S_DIM, prefixStyle: S_DIM, prefix: ' ' });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
627
522
|
if (state.loading && state.streamingText) {
|
|
628
|
-
|
|
629
|
-
const rows = renderMarkdown(grid, r, 2, state.streamingText, w, state.codeBlocksExpanded, h);
|
|
630
|
-
r += rows;
|
|
523
|
+
allContent.push({ role: 'streaming', content: state.streamingText, style: S_TEXT, prefixStyle: S_ASSISTANT, prefix: '◆ ' });
|
|
631
524
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
525
|
+
if (state.errorText) {
|
|
526
|
+
allContent.push({ role: 'error', content: state.errorText, style: S_ERROR, prefixStyle: S_ERROR, prefix: '✗ ' });
|
|
527
|
+
}
|
|
528
|
+
const prefixLen = 2;
|
|
529
|
+
const contentWidth = w - 1; // reserve rightmost column for scrollbar
|
|
530
|
+
const textWidth = contentWidth - prefixLen;
|
|
531
|
+
// Pre-compute total height to handle scrolling
|
|
532
|
+
let totalRows = 0;
|
|
533
|
+
if (state.bannerLines && h >= 30) {
|
|
534
|
+
const compact = h < 40;
|
|
535
|
+
const visibleLines = compact ? Math.min(2, state.bannerLines.length) : state.bannerLines.length;
|
|
536
|
+
totalRows += visibleLines + 1;
|
|
537
|
+
}
|
|
538
|
+
for (const item of allContent) {
|
|
539
|
+
if (item.role === 'user' && totalRows > 0)
|
|
540
|
+
totalRows++;
|
|
541
|
+
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
542
|
+
totalRows += measureMarkdown(item.content, contentWidth);
|
|
648
543
|
}
|
|
649
544
|
else {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
r++;
|
|
545
|
+
const lines = item.content.split('\n');
|
|
546
|
+
for (const line of lines) {
|
|
547
|
+
totalRows += Math.max(1, Math.ceil((line.length || 1) / textWidth));
|
|
548
|
+
}
|
|
655
549
|
}
|
|
656
550
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
grid.writeText(r, 0, state.lastThinkingSummary, S_DIM);
|
|
660
|
-
r++;
|
|
551
|
+
if (state.thinkingText) {
|
|
552
|
+
totalRows += state.thinkingExpanded ? Math.min(state.thinkingText.split('\n').length, 10) : 1;
|
|
661
553
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
let suffix = '';
|
|
677
|
-
if (elapsed > 0)
|
|
678
|
-
suffix += ` ${elapsed}s`;
|
|
679
|
-
if (state.tokenCount > 0) {
|
|
680
|
-
const tokStr = state.tokenCount >= 1000 ? `${(state.tokenCount / 1000).toFixed(1)}K` : `${state.tokenCount}`;
|
|
681
|
-
suffix += ` | ${tokStr} tokens`;
|
|
682
|
-
}
|
|
683
|
-
suffix += '...';
|
|
684
|
-
grid.writeText(r, 2 + thinkText.length, suffix, S_DIM);
|
|
685
|
-
r++;
|
|
554
|
+
if (!state.loading && state.lastThinkingSummary)
|
|
555
|
+
totalRows += 1;
|
|
556
|
+
if (state.loading && !state.streamingText && !state.thinkingText)
|
|
557
|
+
totalRows += 1;
|
|
558
|
+
for (const [callId, tc] of state.toolCalls) {
|
|
559
|
+
totalRows += 1;
|
|
560
|
+
if (tc.isAgent && tc.agentDescription)
|
|
561
|
+
totalRows += 1;
|
|
562
|
+
if (tc.status === 'running' && tc.liveOutput)
|
|
563
|
+
totalRows += Math.min(tc.liveOutput.length, 5);
|
|
564
|
+
if (tc.output && tc.status !== 'running' && state.expandedToolCalls.has(callId)) {
|
|
565
|
+
totalRows += Math.min(tc.output.split('\n').length, 20);
|
|
566
|
+
}
|
|
686
567
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
568
|
+
if (state.contextWarning)
|
|
569
|
+
totalRows += 1;
|
|
570
|
+
const autoOffset = totalRows > msgAreaHeight ? totalRows - msgAreaHeight : 0;
|
|
571
|
+
const scrollOffset = Math.max(0, autoOffset - state.manualScroll);
|
|
572
|
+
// Scrollbar geometry
|
|
573
|
+
const hasScrollbar = totalRows > msgAreaHeight;
|
|
574
|
+
let thumbStart = 0;
|
|
575
|
+
let thumbSize = msgAreaHeight;
|
|
576
|
+
if (hasScrollbar) {
|
|
577
|
+
thumbSize = Math.max(1, Math.round((msgAreaHeight / totalRows) * msgAreaHeight));
|
|
578
|
+
thumbStart = Math.round((scrollOffset / Math.max(1, totalRows)) * (msgAreaHeight - thumbSize));
|
|
692
579
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
grid.writeText(r, 2, `${icon} `, statusStyle);
|
|
707
|
-
grid.writeText(r, 4, tc.toolName, nameStyle);
|
|
708
|
-
let afterName = 4 + tc.toolName.length + 1;
|
|
709
|
-
if (tc.args) {
|
|
710
|
-
const maxArgs = w - afterName - 15;
|
|
711
|
-
if (maxArgs > 5) {
|
|
712
|
-
const argsText = tc.args.slice(0, maxArgs) + (tc.args.length > maxArgs ? '…' : '');
|
|
713
|
-
grid.writeText(r, afterName, argsText, S_DIM);
|
|
714
|
-
afterName += argsText.length + 1;
|
|
580
|
+
let r = 0;
|
|
581
|
+
let virtualR = 0;
|
|
582
|
+
let contentIdx = 0;
|
|
583
|
+
// ── Banner ──
|
|
584
|
+
if (state.bannerLines && h >= 30) {
|
|
585
|
+
const compact = h < 40;
|
|
586
|
+
const startLine = compact ? Math.max(0, state.bannerLines.length - 2) : 0;
|
|
587
|
+
for (let i = startLine; i < state.bannerLines.length; i++) {
|
|
588
|
+
if (virtualR >= scrollOffset && r < msgAreaHeight) {
|
|
589
|
+
const line = state.bannerLines[i];
|
|
590
|
+
const isBannerArt = i < state.bannerLines.length - 2;
|
|
591
|
+
grid.writeText(r, 0, line, isBannerArt ? S_BANNER : S_BANNER_DIM);
|
|
592
|
+
r++;
|
|
715
593
|
}
|
|
594
|
+
virtualR++;
|
|
716
595
|
}
|
|
717
|
-
if (
|
|
718
|
-
grid.writeText(r, Math.min(afterName, w - tc.resultSummary.length - 2), tc.resultSummary, S_DIM);
|
|
719
|
-
}
|
|
720
|
-
r++;
|
|
721
|
-
if (isAgent && tc.agentDescription && r < h) {
|
|
722
|
-
grid.writeText(r, 6, tc.agentDescription.slice(0, w - 8), S_DIM);
|
|
596
|
+
if (virtualR >= scrollOffset && r < msgAreaHeight) {
|
|
723
597
|
r++;
|
|
724
598
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
599
|
+
virtualR++;
|
|
600
|
+
}
|
|
601
|
+
// ── Messages ──
|
|
602
|
+
for (const item of allContent) {
|
|
603
|
+
if (r >= msgAreaHeight)
|
|
604
|
+
break;
|
|
605
|
+
if (item.role === 'user' && contentIdx > 0) {
|
|
606
|
+
if (virtualR >= scrollOffset) {
|
|
607
|
+
for (let c = 0; c < w; c++) {
|
|
608
|
+
grid.setCell(r, c, '─', S_BORDER);
|
|
609
|
+
}
|
|
732
610
|
r++;
|
|
733
611
|
}
|
|
612
|
+
virtualR++;
|
|
734
613
|
}
|
|
735
|
-
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
614
|
+
let itemRows;
|
|
615
|
+
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
616
|
+
itemRows = measureMarkdown(item.content, contentWidth);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
const lines = item.content.split('\n');
|
|
620
|
+
itemRows = 0;
|
|
621
|
+
for (const line of lines) {
|
|
622
|
+
itemRows += Math.max(1, Math.ceil((line.length || 1) / textWidth));
|
|
743
623
|
}
|
|
744
624
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
grid.writeText(r, 0, state.contextWarning.text, warnStyle);
|
|
750
|
-
r++;
|
|
751
|
-
}
|
|
752
|
-
// ── Footer border ──
|
|
753
|
-
if (r < h) {
|
|
754
|
-
for (let c = 0; c < w; c++)
|
|
755
|
-
grid.setCell(r, c, '─', S_BORDER);
|
|
756
|
-
r++;
|
|
757
|
-
}
|
|
758
|
-
let nextRow = r;
|
|
759
|
-
// ── Permission box ──
|
|
760
|
-
let questionInputRow = -1;
|
|
761
|
-
if (state.permissionBox && w >= 20 && (h - nextRow) >= 4) {
|
|
762
|
-
const { toolName, riskLevel } = state.permissionBox;
|
|
763
|
-
const riskColor = riskLevel === 'high' ? 'red' : riskLevel === 'medium' ? 'yellow' : 'green';
|
|
764
|
-
const riskStyle = { fg: riskColor, bg: null, bold: true, dim: false, underline: false };
|
|
765
|
-
grid.writeText(nextRow, 1, `⚠ ${toolName} (${riskLevel} risk)`, riskStyle);
|
|
766
|
-
nextRow++;
|
|
767
|
-
const S_KEY_GREEN = { fg: 'green', bg: null, bold: true, dim: false, underline: false };
|
|
768
|
-
const S_KEY_RED = { fg: 'red', bg: null, bold: true, dim: false, underline: false };
|
|
769
|
-
grid.writeText(nextRow, 1, 'Y', S_KEY_GREEN);
|
|
770
|
-
grid.writeText(nextRow, 2, 'es ', S_DIM);
|
|
771
|
-
grid.writeText(nextRow, 6, 'N', S_KEY_RED);
|
|
772
|
-
grid.writeText(nextRow, 7, 'o', S_DIM);
|
|
773
|
-
if (state.permissionDiffInfo) {
|
|
774
|
-
const S_KEY_CYAN = { fg: 'cyan', bg: null, bold: true, dim: false, underline: false };
|
|
775
|
-
grid.writeText(nextRow, 10, 'D', S_KEY_CYAN);
|
|
776
|
-
grid.writeText(nextRow, 11, 'iff', S_DIM);
|
|
625
|
+
if (virtualR + itemRows <= scrollOffset) {
|
|
626
|
+
virtualR += itemRows;
|
|
627
|
+
contentIdx++;
|
|
628
|
+
continue;
|
|
777
629
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
630
|
+
grid.writeText(r, 0, item.prefix, item.prefixStyle);
|
|
631
|
+
let rows;
|
|
632
|
+
if (item.role === 'assistant' || item.role === 'streaming') {
|
|
633
|
+
rows = renderMarkdown(grid, r, prefixLen, item.content, contentWidth, state.codeBlocksExpanded, msgAreaHeight);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
rows = grid.writeWrapped(r, prefixLen, item.content, item.style, contentWidth, msgAreaHeight);
|
|
784
637
|
}
|
|
638
|
+
r += rows;
|
|
639
|
+
virtualR += itemRows;
|
|
640
|
+
contentIdx++;
|
|
785
641
|
}
|
|
786
|
-
// ──
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
642
|
+
// ── Thinking, spinner, tool calls, context warning (shared helpers) ──
|
|
643
|
+
r = renderThinkingSection(state, grid, r, msgAreaHeight);
|
|
644
|
+
r = renderThinkingSummarySection(state, grid, r, msgAreaHeight);
|
|
645
|
+
r = renderSpinnerSection(state, grid, r, msgAreaHeight);
|
|
646
|
+
r = renderToolCallsSection(state, grid, r, msgAreaHeight, { maxLiveLines: 5, showOverflow: true });
|
|
647
|
+
r = renderContextWarningSection(state, grid, r, msgAreaHeight);
|
|
648
|
+
// ── Scrollbar ──
|
|
649
|
+
if (hasScrollbar) {
|
|
650
|
+
const S_TRACK = { fg: null, bg: null, bold: false, dim: true, underline: false };
|
|
651
|
+
const S_THUMB = { fg: null, bg: null, bold: false, dim: false, underline: false };
|
|
652
|
+
for (let sr = 0; sr < msgAreaHeight; sr++) {
|
|
653
|
+
const isThumb = sr >= thumbStart && sr < thumbStart + thumbSize;
|
|
654
|
+
grid.setCell(sr, w - 1, isThumb ? '█' : '░', isThumb ? S_THUMB : S_TRACK);
|
|
797
655
|
}
|
|
798
|
-
questionInputRow = nextRow;
|
|
799
|
-
grid.writeText(nextRow, 1, '❯ ', S_USER);
|
|
800
|
-
grid.writeText(nextRow, 3, state.questionPrompt.input, S_TEXT);
|
|
801
|
-
nextRow++;
|
|
802
656
|
}
|
|
803
|
-
// ──
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
657
|
+
// ── Footer ──
|
|
658
|
+
const footerStart = Math.min(r, msgAreaHeight);
|
|
659
|
+
for (let c = 0; c < w; c++) {
|
|
660
|
+
grid.setCell(footerStart, c, '─', S_BORDER);
|
|
807
661
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const promptText = vimIndicator + '❯ ';
|
|
811
|
-
const promptWidth = promptText.length;
|
|
812
|
-
if (state.autocomplete.length > 0) {
|
|
813
|
-
for (let ai = 0; ai < state.autocomplete.length; ai++) {
|
|
814
|
-
if (nextRow >= h)
|
|
815
|
-
break;
|
|
816
|
-
const cmd = state.autocomplete[ai];
|
|
817
|
-
const desc = state.autocompleteDescriptions[ai] ?? '';
|
|
818
|
-
const selected = ai === state.autocompleteIndex;
|
|
819
|
-
const acStyle = selected ? s(getTheme().user, true) : s(null, false, true);
|
|
820
|
-
grid.writeText(nextRow, promptWidth, `/${cmd.padEnd(12)}`, acStyle);
|
|
821
|
-
if (desc && w > promptWidth + 15)
|
|
822
|
-
grid.writeText(nextRow, promptWidth + 13, desc.slice(0, w - promptWidth - 15), S_DIM);
|
|
823
|
-
nextRow++;
|
|
824
|
-
}
|
|
662
|
+
if (hasScrollbar) {
|
|
663
|
+
grid.setCell(footerStart, w - 1, '┤', S_BORDER);
|
|
825
664
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
grid.writeText(
|
|
831
|
-
inputStart = promptWidth;
|
|
832
|
-
const inputLines = state.inputText.split('\n');
|
|
833
|
-
const maxInputLines = Math.min(inputLines.length, 5);
|
|
834
|
-
for (let li = 0; li < maxInputLines; li++) {
|
|
835
|
-
if (inputRow + li >= h)
|
|
836
|
-
break;
|
|
837
|
-
if (li === 0) {
|
|
838
|
-
grid.writeText(inputRow, inputStart, inputLines[0], S_TEXT);
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
grid.writeText(inputRow + li, inputStart, inputLines[li], S_TEXT);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
const hintsRow = inputRow + maxInputLines;
|
|
845
|
-
if (hintsRow < h) {
|
|
846
|
-
const hintsText = inputLines.length > 1 ? `${state.statusHints} | Alt+Enter newline` : state.statusHints;
|
|
847
|
-
grid.writeText(hintsRow, 0, hintsText, S_DIM);
|
|
848
|
-
}
|
|
665
|
+
if (state.manualScroll > 0 && totalRows > msgAreaHeight) {
|
|
666
|
+
const hiddenBelow = state.manualScroll;
|
|
667
|
+
const indicator = ` ↓ ${hiddenBelow} more below `;
|
|
668
|
+
const startCol = Math.max(0, Math.floor((w - indicator.length) / 2));
|
|
669
|
+
grid.writeText(footerStart, startCol, indicator, S_DIM);
|
|
849
670
|
}
|
|
850
|
-
|
|
671
|
+
else if (totalRows > msgAreaHeight && scrollOffset > 0) {
|
|
672
|
+
const indicator = ` ↑ ${scrollOffset} more above `;
|
|
673
|
+
const startCol = Math.max(0, Math.floor((w - indicator.length) / 2));
|
|
674
|
+
grid.writeText(footerStart, startCol, indicator, S_DIM);
|
|
675
|
+
}
|
|
676
|
+
let nextRow = footerStart + 1;
|
|
677
|
+
// ── Permission, question, status, autocomplete, notifications, input, companion (shared helpers) ──
|
|
678
|
+
nextRow = renderPermissionBoxSection(state, grid, nextRow, h, { boxed: true, maxDiffHeight });
|
|
679
|
+
const questionResult = renderQuestionPromptSection(state, grid, nextRow, h, { boxed: true });
|
|
680
|
+
nextRow = questionResult.nextRow;
|
|
681
|
+
const questionInputRow = questionResult.questionInputRow;
|
|
682
|
+
nextRow = renderStatusLineSection(state, grid, nextRow, h);
|
|
683
|
+
const { promptText, promptWidth } = getPromptText(state);
|
|
684
|
+
nextRow = renderAutocompleteSection(state, grid, nextRow, h, promptWidth);
|
|
685
|
+
nextRow = renderNotificationsSection(state, grid, nextRow, h);
|
|
686
|
+
const inputRow = nextRow;
|
|
687
|
+
renderInputSection(state, grid, inputRow, h, promptText, promptWidth);
|
|
688
|
+
// Companion (right-aligned in footer, skipped if it would overlap input)
|
|
851
689
|
if (state.companionLines && w >= 50) {
|
|
852
690
|
const compWidth = Math.max(...state.companionLines.map(l => l.length), 0);
|
|
853
691
|
const compStartCol = Math.max(0, w - compWidth - 1);
|
|
854
|
-
|
|
692
|
+
const inputEndCol = promptWidth + (state.inputText.split('\n')[0]?.length ?? 0);
|
|
693
|
+
if (compStartCol > inputEndCol + 3) {
|
|
855
694
|
const compStyle = { fg: state.companionColor || 'cyan', bg: null, bold: false, dim: false, underline: false };
|
|
856
|
-
// Place companion starting at the border row, right-aligned
|
|
857
|
-
const borderRow = r; // r is at the border line position
|
|
858
695
|
for (let i = 0; i < state.companionLines.length; i++) {
|
|
859
|
-
const compRow =
|
|
696
|
+
const compRow = footerStart + i;
|
|
697
|
+
if (compRow >= inputRow)
|
|
698
|
+
break;
|
|
860
699
|
if (compRow >= h)
|
|
861
700
|
break;
|
|
862
701
|
grid.writeText(compRow, compStartCol, state.companionLines[i], compStyle);
|
|
863
702
|
}
|
|
864
703
|
}
|
|
865
704
|
}
|
|
705
|
+
return computeCursorPosition(state, inputRow, promptWidth, questionInputRow);
|
|
706
|
+
}
|
|
707
|
+
// extractSuggestion moved to shared utils/tool-summary.ts as summarizeToolArgs
|
|
708
|
+
/**
|
|
709
|
+
* Rasterize only the "live area" — streaming text, thinking, tool calls, and footer.
|
|
710
|
+
* Used in hybrid mode where completed messages are flushed to terminal scrollback.
|
|
711
|
+
* The grid should be sized to fit just the live content.
|
|
712
|
+
*/
|
|
713
|
+
export function rasterizeLive(state, grid) {
|
|
714
|
+
ensureStyles();
|
|
715
|
+
const w = grid.width;
|
|
716
|
+
const h = grid.height;
|
|
717
|
+
let r = 0;
|
|
718
|
+
// ── Banner (shown when no messages have been flushed yet) ──
|
|
719
|
+
if (state.bannerLines && state.messages.length === 0 && !state.loading) {
|
|
720
|
+
r = renderBannerSection(state, grid, r, h - 4, { compact: h < 15 });
|
|
721
|
+
}
|
|
722
|
+
// ── Streaming text ──
|
|
723
|
+
if (state.loading && state.streamingText) {
|
|
724
|
+
grid.writeText(r, 0, '◆ ', S_ASSISTANT);
|
|
725
|
+
const rows = renderMarkdown(grid, r, 2, state.streamingText, w, state.codeBlocksExpanded, h);
|
|
726
|
+
r += rows;
|
|
727
|
+
}
|
|
728
|
+
// ── Thinking, spinner, error, tool calls, context warning (shared helpers) ──
|
|
729
|
+
r = renderThinkingSection(state, grid, r, h);
|
|
730
|
+
r = renderThinkingSummarySection(state, grid, r, h);
|
|
731
|
+
r = renderSpinnerSection(state, grid, r, h);
|
|
732
|
+
r = renderErrorSection(state, grid, r, h);
|
|
733
|
+
r = renderToolCallsSection(state, grid, r, h, { maxLiveLines: 3, showOverflow: false });
|
|
734
|
+
r = renderContextWarningSection(state, grid, r, h);
|
|
735
|
+
// ── Footer border ──
|
|
736
|
+
if (r < h) {
|
|
737
|
+
for (let c = 0; c < w; c++)
|
|
738
|
+
grid.setCell(r, c, '─', S_BORDER);
|
|
739
|
+
r++;
|
|
740
|
+
}
|
|
741
|
+
let nextRow = r;
|
|
742
|
+
const borderRow = r - 1; // for companion anchoring
|
|
743
|
+
// ── Permission, question, status, autocomplete, notifications, input (shared helpers) ──
|
|
744
|
+
nextRow = renderPermissionBoxSection(state, grid, nextRow, h, { boxed: false, maxDiffHeight: 15 });
|
|
745
|
+
const questionResult = renderQuestionPromptSection(state, grid, nextRow, h, { boxed: false });
|
|
746
|
+
nextRow = questionResult.nextRow;
|
|
747
|
+
const questionInputRow = questionResult.questionInputRow;
|
|
748
|
+
nextRow = renderStatusLineSection(state, grid, nextRow, h);
|
|
749
|
+
const { promptText, promptWidth } = getPromptText(state);
|
|
750
|
+
nextRow = renderAutocompleteSection(state, grid, nextRow, h, promptWidth);
|
|
751
|
+
nextRow = renderNotificationsSection(state, grid, nextRow, h);
|
|
752
|
+
const inputRow = nextRow;
|
|
753
|
+
renderInputSection(state, grid, inputRow, h, promptText, promptWidth);
|
|
754
|
+
// ── Companion (right-aligned, anchored at footer border) ──
|
|
755
|
+
renderCompanionSection(state, grid, borderRow, h, promptWidth);
|
|
866
756
|
// ── Cursor position ──
|
|
867
757
|
if (state.questionPrompt && questionInputRow >= 0) {
|
|
868
758
|
return { cursorRow: questionInputRow, cursorCol: 3 + state.questionPrompt.cursor };
|
|
@@ -871,6 +761,6 @@ export function rasterizeLive(state, grid) {
|
|
|
871
761
|
const cursorLines = textBeforeCursor.split('\n');
|
|
872
762
|
const cursorLineIdx = Math.min(cursorLines.length - 1, 4);
|
|
873
763
|
const cursorColInLine = cursorLines[cursorLines.length - 1].length;
|
|
874
|
-
return { cursorRow: inputRow + cursorLineIdx, cursorCol:
|
|
764
|
+
return { cursorRow: inputRow + cursorLineIdx, cursorCol: promptWidth + cursorColInLine };
|
|
875
765
|
}
|
|
876
766
|
//# sourceMappingURL=layout.js.map
|