dsh-code 1.0.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +123 -26
- package/README.md +124 -27
- package/bin/deepseek.mjs +283 -35
- package/cordis.patch.yml +97 -0
- package/lib/index.mjs +5008 -881
- package/lib/session-query.mjs +150 -0
- package/lib/startup.mjs +4 -4
- package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
- package/lib/types/app.d.ts +106 -62
- package/lib/types/authorization-panel.d.ts +3 -3
- package/lib/types/git-workflow.d.ts +91 -2
- package/lib/types/i18n.d.ts +39 -0
- package/lib/types/index.d.ts +100 -1
- package/lib/types/input-split.d.ts +1 -1
- package/lib/types/kernel-panels.d.ts +107 -29
- package/lib/types/language-panel.d.ts +12 -0
- package/lib/types/locales/en.d.ts +450 -0
- package/lib/types/locales/zh.d.ts +9 -0
- package/lib/types/mentions.d.ts +7 -3
- package/lib/types/models.d.ts +14 -0
- package/lib/types/panel-accent.d.ts +28 -0
- package/lib/types/rainbow.d.ts +69 -0
- package/lib/types/render/animations.d.ts +42 -0
- package/lib/types/render/editor.d.ts +4 -3
- package/lib/types/render/ime-cursor.d.ts +60 -0
- package/lib/types/render/inspector.d.ts +26 -0
- package/lib/types/render/lines.d.ts +21 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +130 -4
- package/lib/types/render/status.d.ts +9 -9
- package/lib/types/render/text.d.ts +6 -0
- package/lib/types/render/usage.d.ts +113 -0
- package/lib/types/session-directory.d.ts +17 -0
- package/lib/types/session-query.d.ts +92 -0
- package/lib/types/startup.d.ts +1 -1
- package/lib/types/terminal-title.d.ts +66 -0
- package/lib/types/theme-panel.d.ts +2 -2
- package/lib/types/theme.d.ts +271 -52
- package/lib/types/update-panel.d.ts +49 -0
- package/lib/types/update.d.ts +75 -0
- package/lib/types/version.d.ts +4 -3
- package/package.json +246 -90
- package/src/app.ts +1369 -509
- package/src/approval.ts +166 -166
- package/src/authorization-panel.ts +19 -16
- package/src/editor-keys.ts +371 -371
- package/src/git-workflow.ts +229 -3
- package/src/i18n.ts +68 -0
- package/src/index.ts +534 -80
- package/src/input-split.ts +3 -3
- package/src/internals.ts +5 -0
- package/src/kernel-panels.ts +554 -86
- package/src/keyboard.ts +5 -4
- package/src/language-panel.ts +53 -0
- package/src/locales/en.ts +489 -0
- package/src/locales/zh.ts +488 -0
- package/src/mentions.ts +8 -4
- package/src/models.ts +264 -212
- package/src/panel-accent.ts +41 -0
- package/src/presets.ts +1 -1
- package/src/provider-settings.ts +1 -1
- package/src/rainbow.ts +208 -0
- package/src/render/animations.ts +104 -6
- package/src/render/editor.ts +25 -24
- package/src/render/export.ts +116 -95
- package/src/render/ime-cursor.ts +147 -0
- package/src/render/inspector.ts +42 -0
- package/src/render/lines.ts +628 -415
- package/src/render/markdown.ts +15 -3
- package/src/render/projection.ts +572 -21
- package/src/render/status.ts +59 -39
- package/src/render/text.ts +14 -0
- package/src/render/tool-preview.ts +77 -77
- package/src/render/usage.ts +430 -0
- package/src/render/width.ts +2 -2
- package/src/session-directory.ts +8 -6
- package/src/session-query.ts +239 -0
- package/src/startup.ts +3 -3
- package/src/subagents.ts +229 -229
- package/src/terminal-title.ts +190 -0
- package/src/theme-panel.ts +17 -21
- package/src/theme.ts +281 -33
- package/src/update-panel.ts +256 -0
- package/src/update.ts +126 -0
- package/src/version.ts +58 -20
- package/src/whale-glyph.ts +23 -23
package/src/render/export.ts
CHANGED
|
@@ -1,95 +1,116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Markdown export of one transcript view: the /export command's pure
|
|
3
|
-
* formatter. Deterministic and side-effect free — the runner owns the file
|
|
4
|
-
* write, so tests drive the builder with folded views directly.
|
|
5
|
-
*
|
|
6
|
-
* @module @deepseek-ai/dsh-code/render/export
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
|
10
|
-
import { fileLabels, imageLabels, type TranscriptView } from './projection.ts'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* @param
|
|
16
|
-
* @
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
out.push(
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (entry.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
out.push(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
out.push(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Markdown export of one transcript view: the /export command's pure
|
|
3
|
+
* formatter. Deterministic and side-effect free — the runner owns the file
|
|
4
|
+
* write, so tests drive the builder with folded views directly.
|
|
5
|
+
*
|
|
6
|
+
* @module @deepseek-ai/dsh-code/render/export
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
|
10
|
+
import { fileLabels, imageLabels, type TranscriptView } from './projection.ts'
|
|
11
|
+
import { t } from '../i18n.ts'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render the transcript as a standalone markdown document.
|
|
15
|
+
* @param view - the folded transcript view to export.
|
|
16
|
+
* @param sessionId - the full session identity for the header.
|
|
17
|
+
* @returns the complete markdown text.
|
|
18
|
+
*/
|
|
19
|
+
/** Both user and queued rows export the same attachment label block. */
|
|
20
|
+
const attachmentLabels = (entry: { images?: readonly unknown[]; files?: readonly unknown[] }): string =>
|
|
21
|
+
[imageLabels(entry.images as never), fileLabels(entry.files as never)].filter(label => label !== '').join('\n')
|
|
22
|
+
|
|
23
|
+
export function buildExportMarkdown(view: TranscriptView, sessionId: string): string {
|
|
24
|
+
const out: string[] = [
|
|
25
|
+
view.title === ''
|
|
26
|
+
? `# dsh session ${sessionId}`
|
|
27
|
+
: `# ${view.title}`,
|
|
28
|
+
`> session ${sessionId}`,
|
|
29
|
+
'',
|
|
30
|
+
]
|
|
31
|
+
// The effective system prompt (v3 surface nodes) heads the export in a
|
|
32
|
+
// collapsed block: visible when audited, out of the way when scrolled.
|
|
33
|
+
if (view.systemPrompt !== '') {
|
|
34
|
+
out.push('<details><summary>system prompt</summary>', '', view.systemPrompt, '', '</details>', '')
|
|
35
|
+
}
|
|
36
|
+
for (const entry of view.entries) {
|
|
37
|
+
switch (entry.kind) {
|
|
38
|
+
case 'user':
|
|
39
|
+
if (entry.notice) {
|
|
40
|
+
out.push(`> ⤷ context: ${entry.text}`, '')
|
|
41
|
+
} else {
|
|
42
|
+
const attachments = attachmentLabels(entry)
|
|
43
|
+
// How the prompt was delivered is part of the record: a queued or
|
|
44
|
+
// steered message reads differently from one typed into an idle
|
|
45
|
+
// composer. Ordinary prompts keep the bare heading.
|
|
46
|
+
const marker = entry.delivery === undefined ? '' : ` (${t(entry.delivery === 'queued' ? 'entry.delivery.queued' : 'entry.delivery.steered')})`
|
|
47
|
+
out.push(`## user${marker}`, '', entry.text, ...(attachments === '' ? [] : [attachments]), '')
|
|
48
|
+
}
|
|
49
|
+
break
|
|
50
|
+
case 'assistant':
|
|
51
|
+
if (entry.reasoning !== '') {
|
|
52
|
+
out.push('<details><summary>thinking</summary>', '', entry.reasoning, '', '</details>', '')
|
|
53
|
+
}
|
|
54
|
+
out.push('## assistant', '', entry.text, '')
|
|
55
|
+
break
|
|
56
|
+
case 'tool':
|
|
57
|
+
out.push(`### tool \`${entry.name}\``, '')
|
|
58
|
+
if (entry.preview !== '') out.push(`- args: ${entry.preview}`)
|
|
59
|
+
if (entry.subs.length > 0) {
|
|
60
|
+
for (const sub of entry.subs) {
|
|
61
|
+
const label = sub.preview === '' ? sub.name : `${sub.name} ${sub.preview}`
|
|
62
|
+
out.push(`- dispatch: ${sub.state === 'error' ? 'error' : sub.state === 'running' ? 'running' : 'ok'} · ${label}${sub.summary === '' ? '' : ` · ${sub.summary}`}`)
|
|
63
|
+
}
|
|
64
|
+
if (entry.subsDropped > 0) out.push(`- dispatch: … ${entry.subsDropped} earlier dispatch${entry.subsDropped === 1 ? '' : 'es'}`)
|
|
65
|
+
}
|
|
66
|
+
if (entry.summary !== '') out.push(`- ${entry.state === 'error' ? 'error' : 'result'}: ${entry.summary}`)
|
|
67
|
+
out.push('')
|
|
68
|
+
break
|
|
69
|
+
case 'command':
|
|
70
|
+
out.push(`### /${entry.name}${entry.args === '' ? '' : ` ${entry.args}`}`, '')
|
|
71
|
+
if (entry.summary !== '') out.push(`- ${entry.state === 'error' ? 'error' : 'result'}: ${entry.summary}`)
|
|
72
|
+
out.push('')
|
|
73
|
+
break
|
|
74
|
+
case 'error':
|
|
75
|
+
out.push(`> ⨯ ${entry.text}`, '')
|
|
76
|
+
break
|
|
77
|
+
case 'turn-marker':
|
|
78
|
+
out.push(`> ${entry.text}`, '')
|
|
79
|
+
break
|
|
80
|
+
case 'compaction':
|
|
81
|
+
out.push(entry.ok
|
|
82
|
+
? `> compacted ~${entry.tokens} tokens`
|
|
83
|
+
: `> compaction failed: ${entry.error}`, '')
|
|
84
|
+
break
|
|
85
|
+
case 'retry':
|
|
86
|
+
out.push(`> retry ${entry.attempt}/${entry.max} (${entry.code})`, '')
|
|
87
|
+
break
|
|
88
|
+
case 'files':
|
|
89
|
+
out.push(`> files changed: ${entry.paths.join(', ')}`, '')
|
|
90
|
+
break
|
|
91
|
+
case 'workflow':
|
|
92
|
+
out.push(`### workflow \`${entry.name}\` (${entry.state})`, '')
|
|
93
|
+
for (const member of entry.members) {
|
|
94
|
+
const phase = member.phase === '' ? '' : ` [${member.phase}]`
|
|
95
|
+
out.push(`- ${member.outcome}: ${member.label}${phase}`)
|
|
96
|
+
}
|
|
97
|
+
if (entry.membersDropped > 0) out.push(`- … ${entry.membersDropped} earlier member${entry.membersDropped === 1 ? '' : 's'}`)
|
|
98
|
+
out.push('')
|
|
99
|
+
break
|
|
100
|
+
case 'pending':
|
|
101
|
+
// Codex PendingSteer: queued prompts export like ordinary user rows.
|
|
102
|
+
out.push('## user', '', entry.text, ...(attachmentLabels(entry) === '' ? [] : [attachmentLabels(entry)]), '')
|
|
103
|
+
break
|
|
104
|
+
default:
|
|
105
|
+
assertNever(entry, 'transcript entry kind')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (view.streaming !== '') out.push('## assistant (streaming)', '', view.streaming, '')
|
|
109
|
+
const { stats } = view
|
|
110
|
+
out.push('---', '')
|
|
111
|
+
out.push(`- model: ${view.model === '' ? '(none yet)' : view.model}`)
|
|
112
|
+
out.push(`- turns: ${stats.turns} · steps: ${stats.steps}`)
|
|
113
|
+
out.push(`- tokens: ↑${stats.usage.uncachedInputTokens} ↓${stats.usage.outputTokens} · cache read ${stats.usage.cacheReadTokens} · cache write ${stats.usage.cacheWriteTokens}`)
|
|
114
|
+
out.push(`- todos: ${view.todos.length}`)
|
|
115
|
+
return out.join('\n')
|
|
116
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IME cursor anchoring for the composer.
|
|
3
|
+
*
|
|
4
|
+
* Ink keeps the real terminal cursor hidden and parks it just below the
|
|
5
|
+
* dynamic tree (after the status row). IME composition text and candidate
|
|
6
|
+
* windows anchor to that real cursor cell - VS Code's integrated terminal
|
|
7
|
+
* positions its hidden IME textarea there, and Windows consoles behave the
|
|
8
|
+
* same - so CJK input appeared at the bottom of the screen instead of at the
|
|
9
|
+
* caret. The anchor moves the real cursor onto the caret cell without
|
|
10
|
+
* touching Ink's relative erase ledger:
|
|
11
|
+
*
|
|
12
|
+
* - the displacement is owned: before any foreign write or re-anchor, the
|
|
13
|
+
* wrapper cancels it (cursor down, column 1), so every writer - Ink's
|
|
14
|
+
* log-update rewrites, the resize replay, protocol pushes - keeps seeing
|
|
15
|
+
* the cursor exactly where it was left;
|
|
16
|
+
* - log-update frame chunks (which start with the erase-line sequence) get
|
|
17
|
+
* the anchor re-appended inside the same write, so a repaint can never
|
|
18
|
+
* leave the cursor behind - in particular the caret blink keeps the anchor
|
|
19
|
+
* stable while an IME composition is open, because the terminal parses the
|
|
20
|
+
* rewrite and the re-anchor as one atomic update.
|
|
21
|
+
*
|
|
22
|
+
* @module @deepseek-ai/dsh-code/render/ime-cursor
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useEffect, useRef } from 'react'
|
|
26
|
+
import { useStdout } from 'ink'
|
|
27
|
+
|
|
28
|
+
/** Cancel `rows` of owned upward displacement and return to column 1. */
|
|
29
|
+
export function imeCursorRestore(rows: number): string {
|
|
30
|
+
return rows > 0 ? `\x1b[${rows}B\r` : ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Move onto the caret cell: `rows` up from Ink's parked row, 0-based column. */
|
|
34
|
+
export function imeCursorMove(rows: number, column: number): string {
|
|
35
|
+
return rows > 0 ? `\x1b[${rows}A\x1b[${column + 1}G` : ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Rows between the caret cell and Ink's parked cursor row: the band's bottom
|
|
40
|
+
* blank row, the editor window rows below the caret, the status footer, and
|
|
41
|
+
* Ink's own below-frame row. Rows above the composer (gutter, live content)
|
|
42
|
+
* never enter this distance.
|
|
43
|
+
*/
|
|
44
|
+
export function imeCursorRowsUp(input: {
|
|
45
|
+
editorWindowRows: number
|
|
46
|
+
caretRowInWindow: number
|
|
47
|
+
rowsBelowComposer: number
|
|
48
|
+
}): number {
|
|
49
|
+
return Math.max(1, input.editorWindowRows - input.caretRowInWindow + input.rowsBelowComposer)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** log-update chunks start with the erase-line sequence; a chunk without it
|
|
53
|
+
* leaves no frame behind, so the next commit re-applies the anchor. */
|
|
54
|
+
const FRAME_CHUNK_MARKER = '\x1b[2K'
|
|
55
|
+
|
|
56
|
+
/** The installed anchor handle. */
|
|
57
|
+
export interface ImeCursorAnchor {
|
|
58
|
+
/** Anchor on the caret cell: `rows` above Ink's parked row at the 0-based
|
|
59
|
+
* `column`; `rows <= 0` releases the anchor. */
|
|
60
|
+
anchor(rows: number, column: number): void
|
|
61
|
+
/** Cancel the displacement, restore the original write path, and detach. */
|
|
62
|
+
release(): void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ANCHOR_STATE = Symbol.for('dsh-code.ime-cursor-anchor')
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Take over `stream.write` so the anchor displacement stays invisible to every
|
|
69
|
+
* other writer. Idempotent per stream: a second install returns the live
|
|
70
|
+
* handle. Returns `undefined` on non-TTY streams where anchoring is meaningless.
|
|
71
|
+
*/
|
|
72
|
+
export function installImeCursorAnchor(stream: NodeJS.WriteStream): ImeCursorAnchor | undefined {
|
|
73
|
+
if (stream?.isTTY !== true) return undefined
|
|
74
|
+
const target = stream as NodeJS.WriteStream & { [ANCHOR_STATE]?: ImeCursorAnchor | undefined }
|
|
75
|
+
const installed = target[ANCHOR_STATE]
|
|
76
|
+
if (installed !== undefined) return installed
|
|
77
|
+
const originalWrite = target.write.bind(target) as (...args: unknown[]) => unknown
|
|
78
|
+
let rows = 0
|
|
79
|
+
let column = -1
|
|
80
|
+
let detached = false
|
|
81
|
+
const anchor: ImeCursorAnchor = {
|
|
82
|
+
anchor(nextRows, nextColumn) {
|
|
83
|
+
if (detached) return
|
|
84
|
+
if (nextRows <= 0) {
|
|
85
|
+
if (rows !== 0) originalWrite(imeCursorRestore(rows))
|
|
86
|
+
rows = 0
|
|
87
|
+
column = -1
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
if (rows === nextRows && column === nextColumn) return
|
|
91
|
+
originalWrite(imeCursorRestore(rows) + imeCursorMove(nextRows, nextColumn))
|
|
92
|
+
rows = nextRows
|
|
93
|
+
column = nextColumn
|
|
94
|
+
},
|
|
95
|
+
release() {
|
|
96
|
+
if (detached) return
|
|
97
|
+
detached = true
|
|
98
|
+
if (rows !== 0) originalWrite(imeCursorRestore(rows))
|
|
99
|
+
rows = 0
|
|
100
|
+
column = -1
|
|
101
|
+
target.write = originalWrite as typeof target.write
|
|
102
|
+
delete target[ANCHOR_STATE]
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
target.write = ((chunk: unknown, ...rest: unknown[]) => {
|
|
106
|
+
const ownedRows = rows
|
|
107
|
+
const ownedColumn = column
|
|
108
|
+
if (ownedRows === 0 || typeof chunk !== 'string') {
|
|
109
|
+
if (ownedRows !== 0) {
|
|
110
|
+
originalWrite(imeCursorRestore(ownedRows))
|
|
111
|
+
rows = 0
|
|
112
|
+
column = -1
|
|
113
|
+
}
|
|
114
|
+
return originalWrite(chunk, ...rest)
|
|
115
|
+
}
|
|
116
|
+
const reanchor = chunk.startsWith(FRAME_CHUNK_MARKER)
|
|
117
|
+
rows = reanchor ? ownedRows : 0
|
|
118
|
+
column = reanchor ? ownedColumn : -1
|
|
119
|
+
return originalWrite(imeCursorRestore(ownedRows) + chunk + (reanchor ? imeCursorMove(ownedRows, ownedColumn) : ''), ...rest)
|
|
120
|
+
}) as typeof target.write
|
|
121
|
+
target[ANCHOR_STATE] = anchor
|
|
122
|
+
return anchor
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Keep the real terminal cursor on the composer's caret cell while `active`
|
|
127
|
+
* (the editable composer). The second effect runs after every commit without
|
|
128
|
+
* a dep list: frame rewrites restore the anchor themselves, but any other
|
|
129
|
+
* write (protocol push, resize replay) leaves the cursor at Ink's parked
|
|
130
|
+
* position, and the next commit re-anchors it.
|
|
131
|
+
*/
|
|
132
|
+
export function useImeCursorAnchor(active: boolean, rows: number, column: number): void {
|
|
133
|
+
const { stdout } = useStdout()
|
|
134
|
+
const anchorRef = useRef<ImeCursorAnchor | undefined>(undefined)
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (stdout === undefined) return undefined
|
|
137
|
+
const installed = installImeCursorAnchor(stdout)
|
|
138
|
+
anchorRef.current = installed
|
|
139
|
+
return () => {
|
|
140
|
+
installed?.release()
|
|
141
|
+
if (anchorRef.current === installed) anchorRef.current = undefined
|
|
142
|
+
}
|
|
143
|
+
}, [stdout])
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
anchorRef.current?.anchor(active && rows > 0 ? rows : 0, column)
|
|
146
|
+
})
|
|
147
|
+
}
|
package/src/render/inspector.ts
CHANGED
|
@@ -24,6 +24,48 @@ export function layoutGutterRows(rows: number): 0 | 1 {
|
|
|
24
24
|
return Math.max(1, Math.floor(rows)) >= 14 ? 1 : 0
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Measured chrome that sits below (or in) the live region. */
|
|
28
|
+
export interface LiveRegionChrome {
|
|
29
|
+
/** Terminal rows. */
|
|
30
|
+
readonly terminalRows: number
|
|
31
|
+
/** Editor rows inside the composer band (frozen reports 1). */
|
|
32
|
+
readonly composerRows: number
|
|
33
|
+
/** Status footer rows. */
|
|
34
|
+
readonly statusBarRows: 1 | 2
|
|
35
|
+
/** Completion menu rows (0 when closed). */
|
|
36
|
+
readonly menuRows: number
|
|
37
|
+
/** Transcript-to-composer gutter. */
|
|
38
|
+
readonly gutterRows: 0 | 1
|
|
39
|
+
/** Notice line present. */
|
|
40
|
+
readonly notice: boolean
|
|
41
|
+
/** Todo summary line present. */
|
|
42
|
+
readonly todo: boolean
|
|
43
|
+
/** Agents summary line present. */
|
|
44
|
+
readonly agents: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Rows left for live transcript and streaming after pinning the composer and
|
|
49
|
+
* status at the bottom. Variable chrome (menu, notice, todos, agents, extra
|
|
50
|
+
* status row, extra editor rows) is deducted here so those rows cover the
|
|
51
|
+
* live region instead of growing the tree and moving the bottom bar.
|
|
52
|
+
*/
|
|
53
|
+
export function liveRegionBudget(chrome: LiveRegionChrome): number {
|
|
54
|
+
const terminal = Math.max(1, Math.floor(chrome.terminalRows))
|
|
55
|
+
const editor = Math.max(1, Math.floor(chrome.composerRows))
|
|
56
|
+
const band = editor + 2
|
|
57
|
+
const status = chrome.statusBarRows === 2 ? 2 : 1
|
|
58
|
+
const menu = Math.max(0, Math.floor(chrome.menuRows))
|
|
59
|
+
const gutter = chrome.gutterRows
|
|
60
|
+
const notice = chrome.notice ? 1 : 0
|
|
61
|
+
const todo = chrome.todo ? 1 : 0
|
|
62
|
+
const agents = chrome.agents ? 1 : 0
|
|
63
|
+
// Two spare rows: Ink's parked cursor, plus one so the painted tree never
|
|
64
|
+
// reaches stdout.rows (equality takes the full-terminal clear path).
|
|
65
|
+
const inkSpare = 2
|
|
66
|
+
return Math.max(1, terminal - band - status - menu - gutter - notice - todo - agents - inkSpare)
|
|
67
|
+
}
|
|
68
|
+
|
|
27
69
|
/**
|
|
28
70
|
* Keep the inspector plus its persistent status/composer chrome below
|
|
29
71
|
* `stdout.rows`: at equality Ink clears the terminal and rewrites all
|