dsh-code 1.3.0 → 1.5.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 +10 -8
- package/README.md +10 -8
- package/lib/index.mjs +6980 -5942
- package/lib/session-query.mjs +18 -1
- package/lib/types/app.d.ts +38 -65
- package/lib/types/completion.d.ts +29 -0
- package/lib/types/composer.d.ts +150 -0
- package/lib/types/git-workflow.d.ts +6 -0
- package/lib/types/index.d.ts +6 -188
- package/lib/types/locales/en.d.ts +20 -8
- package/lib/types/{authorization-panel.d.ts → panels/authorization-panel.d.ts} +6 -1
- package/lib/types/panels/completion-panel.d.ts +13 -0
- package/lib/types/panels/interaction-bars.d.ts +38 -0
- package/lib/types/{kernel-panels.d.ts → panels/kernel-panels.d.ts} +16 -37
- package/lib/types/{language-panel.d.ts → panels/language-panel.d.ts} +1 -1
- package/lib/types/panels/model-panels.d.ts +86 -0
- package/lib/types/{theme-panel.d.ts → panels/theme-panel.d.ts} +1 -1
- package/lib/types/{update-panel.d.ts → panels/update-panel.d.ts} +1 -1
- package/lib/types/provider-settings.d.ts +11 -0
- package/lib/types/render/inspector.d.ts +16 -0
- package/lib/types/render/projection-events.d.ts +33 -0
- package/lib/types/render/text.d.ts +4 -0
- package/lib/types/render/transcript-viewport.d.ts +32 -0
- package/lib/types/runner/harness-gate.d.ts +83 -0
- package/lib/types/runner/input-history.d.ts +31 -0
- package/lib/types/runner/mode-cycle.d.ts +44 -0
- package/lib/types/runner/preferences.d.ts +40 -0
- package/lib/types/runner/quit.d.ts +27 -0
- package/lib/types/runner/search-rows.d.ts +38 -0
- package/lib/types/runner/session-io.d.ts +57 -0
- package/lib/types/runner/session-target.d.ts +42 -0
- package/lib/types/runner/startup-config.d.ts +33 -0
- package/lib/types/runner/submissions.d.ts +87 -0
- package/lib/types/session/attach.d.ts +39 -0
- package/lib/types/{store.d.ts → session/store.d.ts} +1 -1
- package/lib/types/{subagents.d.ts → session/subagents.d.ts} +8 -1
- package/lib/types/session-query.d.ts +11 -0
- package/lib/types/settings-file.d.ts +10 -0
- package/lib/types/{panel-accent.d.ts → ui/panel-accent.d.ts} +1 -1
- package/lib/types/ui/panel-gap.d.ts +6 -0
- package/lib/types/ui/query-editor.d.ts +10 -0
- package/lib/types/ui/styled-rows.d.ts +8 -0
- package/lib/types/{terminal-title.d.ts → ui/terminal-title.d.ts} +1 -1
- package/lib/types/ui/ui-contract.d.ts +12 -0
- package/lib/types/ui/use-frames.d.ts +6 -0
- package/lib/types/ui/use-stable-input.d.ts +7 -0
- package/package.json +3 -2
- package/src/app.ts +802 -4162
- package/src/completion.ts +117 -0
- package/src/composer.ts +1956 -0
- package/src/git-workflow.ts +18 -0
- package/src/index.ts +113 -633
- package/src/internals.ts +1 -1
- package/src/locales/en.ts +20 -8
- package/src/locales/zh.ts +20 -8
- package/src/{authorization-panel.ts → panels/authorization-panel.ts} +25 -6
- package/src/panels/completion-panel.ts +79 -0
- package/src/panels/interaction-bars.ts +567 -0
- package/src/{kernel-panels.ts → panels/kernel-panels.ts} +84 -65
- package/src/{language-panel.ts → panels/language-panel.ts} +4 -4
- package/src/panels/model-panels.ts +1021 -0
- package/src/{theme-panel.ts → panels/theme-panel.ts} +5 -5
- package/src/{update-panel.ts → panels/update-panel.ts} +6 -6
- package/src/provider-settings.ts +38 -0
- package/src/render/inspector.ts +35 -0
- package/src/render/lines.ts +45 -18
- package/src/render/projection-events.ts +109 -0
- package/src/render/projection.ts +11 -2
- package/src/render/text.ts +8 -0
- package/src/render/tool-preview.ts +35 -2
- package/src/render/transcript-viewport.ts +61 -0
- package/src/render/usage.ts +1 -2
- package/src/runner/harness-gate.ts +168 -0
- package/src/runner/input-history.ts +77 -0
- package/src/runner/mode-cycle.ts +49 -0
- package/src/runner/preferences.ts +67 -0
- package/src/runner/quit.ts +53 -0
- package/src/runner/search-rows.ts +55 -0
- package/src/runner/session-io.ts +245 -0
- package/src/runner/session-target.ts +81 -0
- package/src/runner/startup-config.ts +54 -0
- package/src/runner/submissions.ts +157 -0
- package/src/session/attach.ts +87 -0
- package/src/{session-directory.ts → session/session-directory.ts} +1 -1
- package/src/{store.ts → session/store.ts} +1 -1
- package/src/{subagents.ts → session/subagents.ts} +12 -1
- package/src/session-query.ts +22 -0
- package/src/settings-file.ts +19 -1
- package/src/{panel-accent.ts → ui/panel-accent.ts} +1 -1
- package/src/ui/panel-gap.ts +9 -0
- package/src/ui/query-editor.ts +16 -0
- package/src/ui/styled-rows.ts +124 -0
- package/src/{terminal-title.ts → ui/terminal-title.ts} +1 -1
- package/src/ui/ui-contract.ts +10 -0
- package/src/ui/use-frames.ts +23 -0
- package/src/ui/use-stable-input.ts +17 -0
- /package/lib/types/{fork.d.ts → session/fork.d.ts} +0 -0
- /package/lib/types/{history.d.ts → session/history.d.ts} +0 -0
- /package/lib/types/{session-directory.d.ts → session/session-directory.d.ts} +0 -0
- /package/lib/types/{session-switch.d.ts → session/session-switch.d.ts} +0 -0
- /package/src/{fork.ts → session/fork.ts} +0 -0
- /package/src/{history.ts → session/history.ts} +0 -0
- /package/src/{session-switch.ts → session/session-switch.ts} +0 -0
package/src/internals.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
shouldEnableKeyboardEnhancement,
|
|
20
20
|
} from './keyboard.ts'
|
|
21
21
|
import { createSplitStdin } from './input-split.ts'
|
|
22
|
-
import { ensureVsCodeTabTitleSetting } from './terminal-title.ts'
|
|
22
|
+
import { ensureVsCodeTabTitleSetting } from './ui/terminal-title.ts'
|
|
23
23
|
|
|
24
24
|
/** A mounted terminal app instance; the runner owns unmount ordering. */
|
|
25
25
|
export interface TuiMount {
|
package/src/locales/en.ts
CHANGED
|
@@ -103,8 +103,15 @@ export const en = {
|
|
|
103
103
|
|
|
104
104
|
// Transcript document panel (/resume details)
|
|
105
105
|
'document.loading': 'loading transcript…',
|
|
106
|
+
'attach.bar': 'observing · {label} · r reload · esc/ctrl+d back',
|
|
107
|
+
'attach.loading': 'loading {label}\'s conversation…',
|
|
108
|
+
'attach.status': 'subagent {label} · {id} · {turns} turns',
|
|
109
|
+
'notice.attachUnavailable': 'subagent attachment is unavailable in this profile',
|
|
106
110
|
'document.compact': 'transcript · esc close',
|
|
107
|
-
'document.
|
|
111
|
+
'document.expanded': 'expanded',
|
|
112
|
+
'document.refreshHint': 'r refresh',
|
|
113
|
+
'document.footerExpanded': 'lines {from}-{to}/{total} · ↑↓/pg/g/G{refresh} · esc/ctrl+d back',
|
|
114
|
+
'document.footer': 'lines {from}-{to}/{total} · ↑↓/pg/g/G · enter/f expand · t/esc close',
|
|
108
115
|
|
|
109
116
|
// /review picker
|
|
110
117
|
'review.picker.title': '/review · choose a target',
|
|
@@ -157,7 +164,7 @@ export const en = {
|
|
|
157
164
|
'cmd.theme': 'switch the color theme',
|
|
158
165
|
'cmd.language': 'switch the interface language (/language [en|zh])',
|
|
159
166
|
'cmd.rainbow': 'reroll or pin the rainbow palette (/rainbow [seed])',
|
|
160
|
-
'cmd.animation': 'toggle
|
|
167
|
+
'cmd.animation': 'toggle decorative animations; shimmer and activity stay live (/animation [on|off])',
|
|
161
168
|
'cmd.history': 'search and recall past prompts',
|
|
162
169
|
'cmd.queue': 'manage messages queued for the next turn',
|
|
163
170
|
'cmd.usage': 'show this session token usage',
|
|
@@ -305,12 +312,12 @@ export const en = {
|
|
|
305
312
|
'notice.imageCancelled': 'image submission cancelled',
|
|
306
313
|
'notice.themeConfigUnreadable': 'theme config unreadable, using dark: {message}',
|
|
307
314
|
'notice.languageConfigUnreadable': 'language config unreadable, using english: {message}',
|
|
308
|
-
'notice.animationsConfigUnreadable': '
|
|
315
|
+
'notice.animationsConfigUnreadable': 'decorative animation config unreadable, effects stay on: {message}',
|
|
309
316
|
'notice.statuslineConfigUnreadable': 'statusline config unreadable, using defaults: {message}',
|
|
310
317
|
'notice.sessionSearchUnavailable': 'session search is unavailable in this deployment',
|
|
311
318
|
'notice.subagentsReadOnly': 'subagent conversations are read-only',
|
|
312
319
|
'notice.statuslineSaveFailed': 'statusline save failed: {message}',
|
|
313
|
-
'notice.animationsSaveFailed': '
|
|
320
|
+
'notice.animationsSaveFailed': 'decorative animation save failed: {message}',
|
|
314
321
|
'notice.commandRegistryMissing': 'no command registry is mounted in this composition',
|
|
315
322
|
'notice.commandFallbackFailed': 'command fallback failed: {message}',
|
|
316
323
|
'notice.commandFailed': 'command failed: {message}',
|
|
@@ -361,7 +368,7 @@ export const en = {
|
|
|
361
368
|
'notice.commandAttachments': 'commands cannot carry attachments; the line will be sent to the model as a prompt',
|
|
362
369
|
'notice.copyFailed': 'copy failed: {message}',
|
|
363
370
|
'notice.vscodeKeysFailed': 'vscode-keys failed: {message}',
|
|
364
|
-
'notice.animationState': 'animations {state}',
|
|
371
|
+
'notice.animationState': 'decorative animations {state}; activity indicators stay live',
|
|
365
372
|
'notice.deleteFailed': 'delete failed: {message}',
|
|
366
373
|
'notice.modelChangedPlaceholder': 'model → {model} · image history will be sent as text placeholders',
|
|
367
374
|
'notice.modelNextStep': 'model → next step uses {model}',
|
|
@@ -456,7 +463,6 @@ export const en = {
|
|
|
456
463
|
'panel.provider.notConfiguredDivider': '── not configured ──',
|
|
457
464
|
'panel.provider.title': '/model — providers',
|
|
458
465
|
'panel.provider.titleCount': '/model — providers · {index}/{total}',
|
|
459
|
-
'panel.provider.state': '{route} · {value}',
|
|
460
466
|
'panel.provider.key': 'key {value}',
|
|
461
467
|
'panel.provider.authConfigured': 'provider auth',
|
|
462
468
|
'panel.provider.noKey': 'key missing',
|
|
@@ -471,7 +477,7 @@ export const en = {
|
|
|
471
477
|
'panel.provider.readOnlyLogin': 'this login record is read-only',
|
|
472
478
|
'panel.provider.notManaged': 'this provider is not managed by Harness settings',
|
|
473
479
|
'panel.providers.compact': '/model providers · enter configure · d remove key · esc back',
|
|
474
|
-
'panel.providers.footer': '↑↓ move · enter configure ·
|
|
480
|
+
'panel.providers.footer': '↑↓ move · enter configure · d remove key · x remove provider · r retry · esc back',
|
|
475
481
|
'panel.update.compact': '/update · {summary}',
|
|
476
482
|
'panel.update.checking': 'checking…',
|
|
477
483
|
'panel.update.probeFailed': 'probe failed',
|
|
@@ -499,8 +505,14 @@ export const en = {
|
|
|
499
505
|
'panel.setup.copyInto': 'copy verbatim into {id}',
|
|
500
506
|
'panel.setup.copyTitle': '/model — copy efforts',
|
|
501
507
|
'panel.setup.copyFooter': '↑↓ move · enter copy · esc back',
|
|
508
|
+
'panel.setup.mode.subscribe': 'Subscription',
|
|
509
|
+
'panel.setup.mode.key': 'Key',
|
|
510
|
+
'panel.setup.mode.login.plan': 'signed in · plan',
|
|
511
|
+
'panel.setup.mode.login.none': 'not signed in',
|
|
512
|
+
'panel.setup.mode.login.running': 'signing in…',
|
|
513
|
+
'panel.setup.mode.overridden': '⚠ the settings key overrides the plan sign-in',
|
|
502
514
|
'panel.setup.title': '/model — configure {provider}',
|
|
503
|
-
'panel.setup.footer': '↑↓ move · ←→ in/out · space remove · e efforts · c copy efforts · tab discover · enter save · esc back',
|
|
515
|
+
'panel.setup.footer': '↑↓ move · ←→ in/out · space remove · e efforts · c copy efforts · tab per channel (subscription = web sign-in, key = discover) · enter save · esc back',
|
|
504
516
|
'panel.discovery.compact': 'model discovery · terminal too small · esc back',
|
|
505
517
|
'panel.discovery.loading': 'discovering models…',
|
|
506
518
|
'panel.discovery.empty': 'the endpoint advertised no models; add ids by hand on the setup page',
|
package/src/locales/zh.ts
CHANGED
|
@@ -105,8 +105,15 @@ export const zh: MessageCatalog = {
|
|
|
105
105
|
|
|
106
106
|
// Transcript document panel (/resume details)
|
|
107
107
|
'document.loading': '转录加载中…',
|
|
108
|
+
'attach.bar': '观察模式 · {label} · r 重新载入 · esc/ctrl+d 返回',
|
|
109
|
+
'attach.loading': '正在载入 {label} 的子会话…',
|
|
110
|
+
'attach.status': '子代理 {label} · {id} · {turns} 回合',
|
|
111
|
+
'notice.attachUnavailable': '当前配置无法附加子代理会话',
|
|
108
112
|
'document.compact': '转录 · esc 关闭',
|
|
109
|
-
'document.
|
|
113
|
+
'document.expanded': '全屏',
|
|
114
|
+
'document.refreshHint': 'r 刷新',
|
|
115
|
+
'document.footerExpanded': '行 {from}-{to}/{total} · ↑↓/pg/g/G{refresh} · esc/ctrl+d 返回',
|
|
116
|
+
'document.footer': '行 {from}-{to}/{total} · ↑↓/pg/g/G · enter/f 全屏 · t/esc 关闭',
|
|
110
117
|
|
|
111
118
|
// /review picker
|
|
112
119
|
'review.picker.title': '/review · 选择审查目标',
|
|
@@ -159,7 +166,7 @@ export const zh: MessageCatalog = {
|
|
|
159
166
|
'cmd.theme': '切换配色主题',
|
|
160
167
|
'cmd.language': '切换界面语言(/language [en|zh])',
|
|
161
168
|
'cmd.rainbow': '重掷或指定彩虹配色(/rainbow [seed])',
|
|
162
|
-
'cmd.animation': '
|
|
169
|
+
'cmd.animation': '开关装饰动画,shimmer 与活动指示始终保留(/animation [on|off])',
|
|
163
170
|
'cmd.history': '搜索并复用历史提示词',
|
|
164
171
|
'cmd.queue': '管理等待下一轮的消息',
|
|
165
172
|
'cmd.usage': '查看本会话的 token 用量',
|
|
@@ -307,12 +314,12 @@ export const zh: MessageCatalog = {
|
|
|
307
314
|
'notice.imageCancelled': '图片提交已取消',
|
|
308
315
|
'notice.themeConfigUnreadable': '主题配置无法读取,使用暗色:{message}',
|
|
309
316
|
'notice.languageConfigUnreadable': '语言配置无法读取,使用英文:{message}',
|
|
310
|
-
'notice.animationsConfigUnreadable': '
|
|
317
|
+
'notice.animationsConfigUnreadable': '装饰动画配置无法读取,保持开启:{message}',
|
|
311
318
|
'notice.statuslineConfigUnreadable': '状态栏配置无法读取,使用默认:{message}',
|
|
312
319
|
'notice.sessionSearchUnavailable': '此部署未启用会话检索',
|
|
313
320
|
'notice.subagentsReadOnly': '子代理会话为只读',
|
|
314
321
|
'notice.statuslineSaveFailed': '状态栏保存失败:{message}',
|
|
315
|
-
'notice.animationsSaveFailed': '
|
|
322
|
+
'notice.animationsSaveFailed': '装饰动画保存失败:{message}',
|
|
316
323
|
'notice.commandRegistryMissing': '当前组合未加载命令注册表',
|
|
317
324
|
'notice.commandFallbackFailed': '命令回退失败:{message}',
|
|
318
325
|
'notice.commandFailed': '命令执行失败:{message}',
|
|
@@ -363,7 +370,7 @@ export const zh: MessageCatalog = {
|
|
|
363
370
|
'notice.commandAttachments': '命令不能携带附件;该行将作为提示发送给模型',
|
|
364
371
|
'notice.copyFailed': '复制失败:{message}',
|
|
365
372
|
'notice.vscodeKeysFailed': 'vscode-keys 失败:{message}',
|
|
366
|
-
'notice.animationState': '
|
|
373
|
+
'notice.animationState': '装饰动画{state};活动指示保持开启',
|
|
367
374
|
'notice.deleteFailed': '删除失败:{message}',
|
|
368
375
|
'notice.modelChangedPlaceholder': '模型 → {model} · 历史图片将以文本占位符发送',
|
|
369
376
|
'notice.modelNextStep': '模型 → 下一步使用 {model}',
|
|
@@ -458,7 +465,6 @@ export const zh: MessageCatalog = {
|
|
|
458
465
|
'panel.provider.notConfiguredDivider': '── 未配置 ──',
|
|
459
466
|
'panel.provider.title': '/model — 供应商',
|
|
460
467
|
'panel.provider.titleCount': '/model — 供应商 · {index}/{total}',
|
|
461
|
-
'panel.provider.state': '{route} · {value}',
|
|
462
468
|
'panel.provider.key': '密钥 {value}',
|
|
463
469
|
'panel.provider.authConfigured': '供应商认证',
|
|
464
470
|
'panel.provider.noKey': '缺少密钥',
|
|
@@ -473,7 +479,7 @@ export const zh: MessageCatalog = {
|
|
|
473
479
|
'panel.provider.readOnlyLogin': '该登录记录为只读',
|
|
474
480
|
'panel.provider.notManaged': '该供应商不受 Harness settings 管理',
|
|
475
481
|
'panel.providers.compact': '/model providers · 回车配置 · d 删除密钥 · esc 返回',
|
|
476
|
-
'panel.providers.footer': '↑↓ 移动 · 回车配置 ·
|
|
482
|
+
'panel.providers.footer': '↑↓ 移动 · 回车配置 · d 删除密钥 · x 删除供应商 · r 重试 · esc 返回',
|
|
477
483
|
'panel.update.compact': '/update · {summary}',
|
|
478
484
|
'panel.update.checking': '检查中…',
|
|
479
485
|
'panel.update.probeFailed': '检查失败',
|
|
@@ -501,8 +507,14 @@ export const zh: MessageCatalog = {
|
|
|
501
507
|
'panel.setup.copyInto': '原样复制到 {id}',
|
|
502
508
|
'panel.setup.copyTitle': '/model — 复制推理档位',
|
|
503
509
|
'panel.setup.copyFooter': '↑↓ 移动 · 回车复制 · esc 返回',
|
|
510
|
+
'panel.setup.mode.subscribe': '订阅',
|
|
511
|
+
'panel.setup.mode.key': 'Key',
|
|
512
|
+
'panel.setup.mode.login.plan': '已登录 · 套餐',
|
|
513
|
+
'panel.setup.mode.login.none': '未登录',
|
|
514
|
+
'panel.setup.mode.login.running': '登录中…',
|
|
515
|
+
'panel.setup.mode.overridden': '⚠ 设置中的 Key 覆盖订阅登录',
|
|
504
516
|
'panel.setup.title': '/model — 配置 {provider}',
|
|
505
|
-
'panel.setup.footer': '↑↓ 移动 · ←→ 输入/输出 · 空格移除 · e 推理档位 · c 复制推理档位 · tab
|
|
517
|
+
'panel.setup.footer': '↑↓ 移动 · ←→ 输入/输出 · 空格移除 · e 推理档位 · c 复制推理档位 · tab 按当前方式(订阅=网页登录 / Key=发现) · 回车保存 · esc 返回',
|
|
506
518
|
'panel.discovery.compact': '模型发现 · 终端过窄 · esc 返回',
|
|
507
519
|
'panel.discovery.loading': '正在发现模型…',
|
|
508
520
|
'panel.discovery.empty': '端点未提供模型;请在配置页手动添加 ID',
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
type AuthorizationStatus,
|
|
11
11
|
} from '@deepseek-ai/dsh-authorization'
|
|
12
12
|
import type { CredentialKey } from '@deepseek-ai/dsh-credentials'
|
|
13
|
-
import type { ProviderAuthorizationRow } from '
|
|
14
|
-
import { panelViewport } from '
|
|
15
|
-
import { displayText, singleLineText, truncateColumns } from '
|
|
16
|
-
import { panelAccent } from '
|
|
17
|
-
import { getPalette, inkColor } from '
|
|
18
|
-
import { t } from '
|
|
13
|
+
import type { ProviderAuthorizationRow } from '../authorization.ts'
|
|
14
|
+
import { panelViewport } from '../render/inspector.ts'
|
|
15
|
+
import { displayText, singleLineText, truncateColumns } from '../render/text.ts'
|
|
16
|
+
import { panelAccent } from '../ui/panel-accent.ts'
|
|
17
|
+
import { getPalette, inkColor } from '../theme.ts'
|
|
18
|
+
import { t } from '../i18n.ts'
|
|
19
19
|
|
|
20
20
|
interface PromptReply {
|
|
21
21
|
readonly resolve: (value: string) => void
|
|
@@ -25,6 +25,11 @@ interface PromptReply {
|
|
|
25
25
|
|
|
26
26
|
export interface ProviderAuthorizationPanelProps {
|
|
27
27
|
readonly row: ProviderAuthorizationRow
|
|
28
|
+
/**
|
|
29
|
+
* Start this method immediately instead of showing the picker: a
|
|
30
|
+
* single-method provider signs in with no extra step.
|
|
31
|
+
*/
|
|
32
|
+
readonly autoStartMethod?: string
|
|
28
33
|
begin(
|
|
29
34
|
row: ProviderAuthorizationRow,
|
|
30
35
|
method: string,
|
|
@@ -78,6 +83,20 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
78
83
|
const propsRef = useRef(props)
|
|
79
84
|
propsRef.current = props
|
|
80
85
|
const rowKey = props.row.key
|
|
86
|
+
// One-shot auto start (single-method providers): guarded by a ref so a
|
|
87
|
+
// remount of the same row cannot start a second attempt.
|
|
88
|
+
const autoStarted = useRef(false)
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const method = props.autoStartMethod
|
|
91
|
+
if (method === undefined || autoStarted.current) return
|
|
92
|
+
autoStarted.current = true
|
|
93
|
+
if (phase !== 'methods') return
|
|
94
|
+
if (props.row.methods.some(entry => entry.id === method)) start(method)
|
|
95
|
+
// One mount-time decision: the auto method, the method list, and the
|
|
96
|
+
// phase are fixed for this mount, so re-running on their identity would
|
|
97
|
+
// risk starting a second attempt.
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
}, [props.autoStartMethod])
|
|
81
100
|
useEffect(() => () => {
|
|
82
101
|
controllerRef.current?.abort()
|
|
83
102
|
propsRef.current.cancel(rowKey)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Bounded slash-command and mention completion menu. */
|
|
2
|
+
|
|
3
|
+
import { createElement, type ReactElement } from 'react'
|
|
4
|
+
import { Box, Text, useStdout } from 'ink'
|
|
5
|
+
import type { CompletionCandidate } from '../completion.ts'
|
|
6
|
+
import { selectionWindow } from '../render/inspector.ts'
|
|
7
|
+
import { visibleColumns } from '../render/markdown.ts'
|
|
8
|
+
import { displayText, padColumns, singleLineText, truncateColumns } from '../render/text.ts'
|
|
9
|
+
import { dim, getPalette, inkColor } from '../theme.ts'
|
|
10
|
+
|
|
11
|
+
/** Shared geometry used by both the menu view and the App dynamic-row budget. */
|
|
12
|
+
function completionMenuMetrics(terminalRows: number): { limit: number; showFooter: boolean; verticalPadding: number } {
|
|
13
|
+
const showFooter = terminalRows >= 12
|
|
14
|
+
const verticalPadding = terminalRows >= 14 ? 1 : 0
|
|
15
|
+
const limit = Math.max(1, Math.min(6, terminalRows - (showFooter ? 11 : 10) - verticalPadding * 2))
|
|
16
|
+
return { limit, showFooter, verticalPadding }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Total physical rows occupied by the completion menu at this terminal height. */
|
|
20
|
+
export function completionMenuRowCount(terminalRows: number, rowCount: number): number {
|
|
21
|
+
const { limit, showFooter, verticalPadding } = completionMenuMetrics(terminalRows)
|
|
22
|
+
const visible = rowCount === 0 ? 1 : Math.min(rowCount, limit)
|
|
23
|
+
const hidden = rowCount === 0 ? 0 : rowCount - visible
|
|
24
|
+
return visible + (hidden > 0 ? 1 : 0) + (showFooter ? 1 : 0) + verticalPadding * 2
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Completion view anchored directly above the composer band. */
|
|
28
|
+
export function CompletionMenu({ active, mention, index, rows, error }: {
|
|
29
|
+
active: boolean
|
|
30
|
+
mention: boolean
|
|
31
|
+
index: number
|
|
32
|
+
rows: readonly CompletionCandidate[]
|
|
33
|
+
error?: string
|
|
34
|
+
}): ReactElement | undefined {
|
|
35
|
+
const stdout = useStdout().stdout
|
|
36
|
+
const columns = stdout?.columns ?? 80
|
|
37
|
+
const terminalRows = stdout?.rows ?? 30
|
|
38
|
+
if (!active) return undefined
|
|
39
|
+
const contentColumns = Math.max(1, columns - 4)
|
|
40
|
+
const nameWidth = mention
|
|
41
|
+
? Math.max(1, contentColumns - 2)
|
|
42
|
+
: Math.min(18, Math.max(1, contentColumns - 2), Math.max(0, ...rows.map(row => visibleColumns(row.label))) + 2)
|
|
43
|
+
const descBudget = Math.max(0, contentColumns - nameWidth - 2)
|
|
44
|
+
const { limit, showFooter, verticalPadding } = completionMenuMetrics(terminalRows)
|
|
45
|
+
const selected = rows.length === 0 ? 0 : index % rows.length
|
|
46
|
+
const first = selectionWindow(selected, rows.length, limit)
|
|
47
|
+
const visible = rows.slice(first, first + limit)
|
|
48
|
+
const hidden = rows.length - visible.length
|
|
49
|
+
return createElement(
|
|
50
|
+
Box,
|
|
51
|
+
{ flexDirection: 'column', marginLeft: 2, paddingY: verticalPadding },
|
|
52
|
+
...(rows.length === 0
|
|
53
|
+
? [error === undefined
|
|
54
|
+
? createElement(Text, { key: 'loading', dimColor: true }, 'searching…')
|
|
55
|
+
: createElement(
|
|
56
|
+
Text,
|
|
57
|
+
{ key: 'error', color: inkColor(getPalette().error), wrap: 'truncate-end' },
|
|
58
|
+
truncateColumns(`workspace search unavailable: ${singleLineText(error)} · keep typing to retry`, contentColumns),
|
|
59
|
+
)]
|
|
60
|
+
: visible.map((candidate, at) => {
|
|
61
|
+
const absolute = first + at
|
|
62
|
+
return createElement(
|
|
63
|
+
Text,
|
|
64
|
+
{
|
|
65
|
+
key: candidate.label,
|
|
66
|
+
color: absolute === selected ? inkColor(getPalette().brandBright) : inkColor(getPalette().dim),
|
|
67
|
+
wrap: 'truncate-end',
|
|
68
|
+
},
|
|
69
|
+
`${absolute === selected ? '❯ ' : ' '}${padColumns(candidate.label, nameWidth)}${dim(truncateColumns(displayText(candidate.description), descBudget))}`,
|
|
70
|
+
)
|
|
71
|
+
})),
|
|
72
|
+
hidden > 0
|
|
73
|
+
? createElement(Text, { key: 'more', color: inkColor(getPalette().dim), wrap: 'truncate-end' }, ` … +${hidden} more`)
|
|
74
|
+
: undefined,
|
|
75
|
+
showFooter
|
|
76
|
+
? createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, dim(mention ? `↑↓ choose · ${rows.length} items · tab insert` : `↑↓ choose · ${rows.length} items · tab complete`))
|
|
77
|
+
: undefined,
|
|
78
|
+
)
|
|
79
|
+
}
|