zames_pro 2.64.1 → 2.65.1
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/CHANGELOG.md +58 -0
- package/README.md +7 -12
- package/dist/backlog.js +20 -0
- package/dist/browser.js +15 -0
- package/dist/commands.js +81 -0
- package/dist/config-menu.js +70 -33
- package/dist/i18n/common.js +362 -0
- package/dist/i18n/config.js +199 -0
- package/dist/i18n/deepseek.js +194 -0
- package/dist/i18n/doctor.js +127 -0
- package/dist/i18n/help.js +552 -0
- package/dist/i18n/mcp.js +41 -0
- package/dist/i18n/prompt.js +20 -0
- package/dist/i18n/self-review.js +198 -0
- package/dist/i18n/spinner.js +36 -0
- package/dist/i18n.js +21 -1724
- package/dist/index.js +39 -503
- package/dist/input/layout.js +314 -0
- package/dist/input.js +88 -292
- package/dist/run-task.js +451 -0
- package/dist/spinner.js +5 -5
- package/package.json +2 -2
- package/scripts/render-demo.mjs +153 -0
- package/logo.jpg +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.65.1] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Длинные строки preview больше не создают горизонтальный скролл в
|
|
15
|
+
терминале: `toolCall`/`toolResult` (LineEditor и спиннер) теперь режут
|
|
16
|
+
превью по ВИДИМОЙ ШИРИНЕ терминала (`truncateToWidth`), а не по числу
|
|
17
|
+
символов. Раньше длинная команда/результат печатались шире экрана.
|
|
18
|
+
- Ctrl+Delete / Ctrl+Backspace (и Alt+Delete / Alt+Backspace) теперь удаляют
|
|
19
|
+
слово в строке ввода. Раньше эти escape-последовательности попадали в
|
|
20
|
+
общий пропуск клавиш и ничего не делали. Поддержаны варианты: `ESC[3;5~`
|
|
21
|
+
(xterm Ctrl+Delete), `ESC[3;3~` (Alt+Delete), `ESC DEL`/`ESC 8`
|
|
22
|
+
(Alt/Ctrl+Backspace) и kitty-протокол `ESC[127;5u` / `ESC[127;3u`.
|
|
23
|
+
Новый метод `_deleteWordRight()` + тест `test/editor-word-delete.test.ts`.
|
|
24
|
+
- Меню `/config` теперь держит ВЫБРАННЫЙ пункт на экране: окно строится
|
|
25
|
+
«назад» от курсора с учётом строк, которые занимают заголовки групп
|
|
26
|
+
(раньше индексный `scrollTop` мог вытолкнуть выбранную строку вниз за
|
|
27
|
+
пределы окна).
|
|
28
|
+
- `/new` сбрасывает счётчик контекста: `_lastTokenUsage` копится НА ЧАТ,
|
|
29
|
+
и без сброса в пустом чате оставался старый `ctx: 302k · 30%` до первой
|
|
30
|
+
отправки. Тест в `test/newchat-verify.test.ts`.
|
|
31
|
+
|
|
32
|
+
## [2.65.0] - 2026-10-07
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- README-скриншот `docs/demo.png` перегенерирован на английском языке
|
|
37
|
+
(раньше был на русском). Воспроизводимый генератор
|
|
38
|
+
`npm run render-demo` (`scripts/render-demo.mjs`) рендерит реальную
|
|
39
|
+
раскладку редактора и палитру темы через headless Chromium с
|
|
40
|
+
изолированным профилем.
|
|
41
|
+
- README: ASCII-схема «как это работает» заменена на диаграмму Mermaid
|
|
42
|
+
(стрелки не «плывут») плюс текстовое описание словами.
|
|
43
|
+
- Убраны неиспользуемые файлы: `logo.jpg` (в README используется только
|
|
44
|
+
`logo-small.jpg`) и заготовка `docs/demo.tape` (VHS-рендер так и не был
|
|
45
|
+
сгенерирован). `logo.jpg` удалён и из списка `files` в package.json.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Перерисовка терминала после ресайза окна: относительный стирающий
|
|
50
|
+
`ESC[n A` после изменения размера попадал на устаревшую строку (терминал
|
|
51
|
+
переформатировал переносы), из-за чего статус/инпут рисовались не там.
|
|
52
|
+
Теперь после ресайза блок заново «прибивается» к низу (home + padding),
|
|
53
|
+
история скроллбэка сохраняется. Тест в `test/terminal-drift.test.ts`.
|
|
54
|
+
- Живая смена языка (`/config lang`) не доходила до двух «одноразово
|
|
55
|
+
созданных» объектов: спиннер думающих фраз брал язык по умолчанию
|
|
56
|
+
(`randomThinkingPhrase()` вместо `randomThinkingPhrase(this.locale)`),
|
|
57
|
+
а `DeepSeekBrowser` печатал свои уведомления (`ds.*`) на языке,
|
|
58
|
+
зафиксированном при старте. Из-за этого при английском UI всплывали
|
|
59
|
+
русские «Структурирую мысли…» и «⏳ DeepSeek оборвал ответ…». Добавлен
|
|
60
|
+
`DeepSeekBrowser.setLocale()`, вызываемый из `setConfigRuntime`.
|
|
61
|
+
- Меню `/config` теперь рисуется ОКНОМ по высоте терминала (раньше весь
|
|
62
|
+
список из 49 полей выводился одним блоком). На невысоком терминале блок
|
|
63
|
+
уходил верхними строками за экран, из-за чего восстановление курсора
|
|
64
|
+
`ESC[lastRows A` попадало не на ту строку и навигация стрелками «не
|
|
65
|
+
скроллила» список. Заодно исправлена мёртвая регулярка снятия ANSI
|
|
66
|
+
(лишний обратный апостроф) в `src/config-menu.ts`.
|
|
67
|
+
|
|
10
68
|
## [2.64.1] - 2026-10-07
|
|
11
69
|
|
|
12
70
|
### Changed
|
package/README.md
CHANGED
|
@@ -33,18 +33,13 @@ zames does not call the model API. It launches a headless Chromium with a
|
|
|
33
33
|
persistent profile, signs in to `chat.deepseek.com` like a human, types the task
|
|
34
34
|
into the chat box, and reads the answer back.
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
answer ◀──── render ◀───── parse tool-call ◀──── raw answer ◀────┘
|
|
44
|
-
│
|
|
45
|
-
▼
|
|
46
|
-
run tool (Read/Edit/Bash/…) → feed result back
|
|
47
|
-
```
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://raw.githubusercontent.com/Viqto0r/zames_pro/master/docs/how-it-works.png" alt="how zames works" width="860">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
In text: you type a task → zames sends the system prompt plus the task into
|
|
41
|
+
the DeepSeek web chat → the model answers → zames parses the tool call, runs
|
|
42
|
+
the tool locally, feeds the result back, and prints the final answer for you.
|
|
48
43
|
|
|
49
44
|
Key pieces:
|
|
50
45
|
|
package/dist/backlog.js
CHANGED
|
@@ -15,6 +15,26 @@ const NEW_ITEM_PREFIX = 'N';
|
|
|
15
15
|
// worth, so the operator is nudged to collapse it.
|
|
16
16
|
export const BACKLOG_WARN_LINES = 500;
|
|
17
17
|
export const BACKLOG_WARN_CHARS = 60_000;
|
|
18
|
+
// Parse a `/backlog <text>` note into the shape appendBacklogItem() wants. An
|
|
19
|
+
// optional leading `P0..P3` token sets the priority; the first line becomes the
|
|
20
|
+
// title and the rest the body. PURE so the operator's note is placed exactly as
|
|
21
|
+
// shown — no model round-trip, no chance of a different id than the one printed.
|
|
22
|
+
export function parseBacklogNote(note) {
|
|
23
|
+
const raw = String(note ?? '');
|
|
24
|
+
let priority = 'P2';
|
|
25
|
+
let body = raw;
|
|
26
|
+
for (const p of ['P0', 'P1', 'P2', 'P3']) {
|
|
27
|
+
if (raw.toUpperCase().startsWith(p + ' ')) {
|
|
28
|
+
priority = p;
|
|
29
|
+
body = raw.slice(p.length + 1);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const bodyLines = body.split(NL);
|
|
34
|
+
const title = bodyLines[0];
|
|
35
|
+
const rest = bodyLines.slice(1).join(NL).trim();
|
|
36
|
+
return { priority, title, note: rest || undefined };
|
|
37
|
+
}
|
|
18
38
|
function squashBlankLines(lines) {
|
|
19
39
|
const out = [];
|
|
20
40
|
let blanks = 0;
|
package/dist/browser.js
CHANGED
|
@@ -550,6 +550,17 @@ export class DeepSeekBrowser {
|
|
|
550
550
|
_t(key, params) {
|
|
551
551
|
return translate(this.locale)(key, params);
|
|
552
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Switch the language of the messages the browser itself prints (login
|
|
555
|
+
* prompts, DeepSeek-side notices from `_notice()`). The browser is created
|
|
556
|
+
* ONCE at startup, so without this a `/config lang` change would leave every
|
|
557
|
+
* later notice in the OLD language until a restart — the operator saw
|
|
558
|
+
* Russian "⏳ DeepSeek оборвал ответ…" while the UI was set to English.
|
|
559
|
+
*/
|
|
560
|
+
setLocale(locale) {
|
|
561
|
+
if (locale)
|
|
562
|
+
this.locale = locale;
|
|
563
|
+
}
|
|
553
564
|
// A service notice for the operator. When a UI hook is wired (LineEditor is
|
|
554
565
|
// active during a task) the notice goes ABOVE the input line so it is not
|
|
555
566
|
// overwritten by the editor's repaint; otherwise it falls back to stderr.
|
|
@@ -1219,6 +1230,10 @@ export class DeepSeekBrowser {
|
|
|
1219
1230
|
// STALE old id for the fresh chat. Clear it — a fresh chat has no id until
|
|
1220
1231
|
// the first message creates one.
|
|
1221
1232
|
this._netChatId = null;
|
|
1233
|
+
// The token counter is cumulative FOR A CHAT. A fresh chat has no messages,
|
|
1234
|
+
// so carrying the old value over left a stale "ctx: 302k · 30%" on the
|
|
1235
|
+
// status line until the first send. Reset it (and the cached status).
|
|
1236
|
+
this._lastTokenUsage = null;
|
|
1222
1237
|
}
|
|
1223
1238
|
async _findVisible(selectors, timeout = 1000) {
|
|
1224
1239
|
for (const sel of selectors) {
|
package/dist/commands.js
CHANGED
|
@@ -951,3 +951,84 @@ export function buildReviewPrompt(focus, hasStaged = false) {
|
|
|
951
951
|
s += NL + 'Extra focus: ' + f;
|
|
952
952
|
return s;
|
|
953
953
|
}
|
|
954
|
+
// The canonical list. Dev-only entries are filtered by buildSlashCommandHints()
|
|
955
|
+
// unless dev mode is on (DEV_ONLY_COMMANDS above), so a regular install never
|
|
956
|
+
// advertises them.
|
|
957
|
+
export const SLASH_COMMANDS = [
|
|
958
|
+
{ name: '/help', key: 'help.cmd.help' },
|
|
959
|
+
{ name: '/new', key: 'help.cmd.new' },
|
|
960
|
+
{ name: '/clear', key: 'help.cmd.new' },
|
|
961
|
+
{ name: '/sessions', key: 'help.cmd.sessions' },
|
|
962
|
+
{ name: '/chats', key: 'help.cmd.chats' },
|
|
963
|
+
{ name: '/resume', key: 'help.cmd.resume' },
|
|
964
|
+
{ name: '/resume-id', key: 'help.cmd.resume_id' },
|
|
965
|
+
{ name: '/last', key: 'help.cmd.last' },
|
|
966
|
+
{ name: '/retry', key: 'help.cmd.retry' },
|
|
967
|
+
{ name: '/rename', key: 'help.cmd.rename' },
|
|
968
|
+
{ name: '/context', key: 'help.cmd.context' },
|
|
969
|
+
{ name: '/copy', key: 'help.cmd.copy' },
|
|
970
|
+
{ name: '/chat', key: 'help.cmd.chat' },
|
|
971
|
+
{ name: '/cd', key: 'help.cmd.cd' },
|
|
972
|
+
{ name: '/pwd', key: 'help.cmd.pwd' },
|
|
973
|
+
{ name: '/status', key: 'help.cmd.status' },
|
|
974
|
+
{ name: '/reload', key: 'help.cmd.reload' },
|
|
975
|
+
{ name: '/undo', key: 'help.cmd.undo' },
|
|
976
|
+
{ name: '/undo-list', key: 'help.cmd.undo_list' },
|
|
977
|
+
{ name: '/rewind', key: 'help.cmd.rewind' },
|
|
978
|
+
{ name: '/rewind-list', key: 'help.cmd.rewind_list' },
|
|
979
|
+
{ name: '/transcript', key: 'help.cmd.transcript' },
|
|
980
|
+
{ name: '/diff', key: 'help.cmd.diff' },
|
|
981
|
+
{ name: '/diffstat', key: 'help.cmd.diffstat' },
|
|
982
|
+
{ name: '/cost', key: 'help.cmd.cost' },
|
|
983
|
+
{ name: '/export', key: 'help.cmd.export' },
|
|
984
|
+
{ name: '/doctor', key: 'help.cmd.doctor' },
|
|
985
|
+
{ name: '/add-dir', key: 'help.cmd.add_dir' },
|
|
986
|
+
{ name: '/review', key: 'help.cmd.review' },
|
|
987
|
+
{ name: '/improve', key: 'help.cmd.improve' },
|
|
988
|
+
{ name: '/backlog', key: 'help.cmd.backlog' },
|
|
989
|
+
{ name: '/goal', key: 'help.cmd.goal' },
|
|
990
|
+
{ name: '/loop', key: 'help.cmd.loop' },
|
|
991
|
+
{ name: '/cron', key: 'help.cmd.cron' },
|
|
992
|
+
{ name: '/jobs', key: 'help.cmd.jobs' },
|
|
993
|
+
{ name: '/thinking', key: 'help.cmd.thinking' },
|
|
994
|
+
{ name: '/web', key: 'help.cmd.web' },
|
|
995
|
+
{ name: '/plan', key: 'help.cmd.plan' },
|
|
996
|
+
{ name: '/compact', key: 'help.cmd.compact' },
|
|
997
|
+
{ name: '/queue', key: 'help.cmd.queue' },
|
|
998
|
+
{ name: '/tasks', key: 'help.cmd.tasks' },
|
|
999
|
+
{ name: '/config', key: 'help.cmd.config' },
|
|
1000
|
+
{ name: '/skills', key: 'help.cmd.skills' },
|
|
1001
|
+
{ name: '/memory', key: 'help.cmd.memory' },
|
|
1002
|
+
{ name: '/remember', key: 'help.cmd.remember' },
|
|
1003
|
+
{ name: '/init', key: 'help.cmd.init' },
|
|
1004
|
+
{ name: '/mcp', key: 'help.cmd.mcp' },
|
|
1005
|
+
{ name: '/debug-dom', key: 'help.cmd.debug_dom' },
|
|
1006
|
+
{ name: '/self-review', key: 'help.self.review' },
|
|
1007
|
+
{ name: '/self-fix', key: 'help.self.fix' },
|
|
1008
|
+
{ name: '/self-done', key: 'help.self.done' },
|
|
1009
|
+
{ name: '/self-list', key: 'help.self.list' },
|
|
1010
|
+
{ name: '/self-diff', key: 'help.self.diff' },
|
|
1011
|
+
{ name: '/self-apply', key: 'help.self.apply' },
|
|
1012
|
+
{ name: '/exit', key: 'help.cmd.exit' },
|
|
1013
|
+
{ name: '/quit', key: 'help.cmd.exit' },
|
|
1014
|
+
];
|
|
1015
|
+
// Build the localized hint list from the built-in table plus the dynamic
|
|
1016
|
+
// skills/custom commands discovered for the current workdir. PURE: the caller
|
|
1017
|
+
// passes `t` and the data; nothing here touches the terminal or the editor.
|
|
1018
|
+
// Dev-only commands are dropped unless `devMode`; a dynamic entry whose name
|
|
1019
|
+
// clashes with a built-in (case-insensitive) is skipped, so a skill cannot
|
|
1020
|
+
// shadow /help.
|
|
1021
|
+
export function buildSlashCommandHints(opts) {
|
|
1022
|
+
const { devMode, t } = opts;
|
|
1023
|
+
const table = opts.commands ?? SLASH_COMMANDS;
|
|
1024
|
+
const base = table
|
|
1025
|
+
.filter((c) => devMode || !isDevOnlyCommand(c.name))
|
|
1026
|
+
.map((c) => ({ name: c.name, description: t(c.key) }));
|
|
1027
|
+
const known = new Set(base.map((c) => c.name.toLowerCase()));
|
|
1028
|
+
for (const d of opts.dynamic ?? []) {
|
|
1029
|
+
if (known.has(d.name.toLowerCase()))
|
|
1030
|
+
continue;
|
|
1031
|
+
base.push(d);
|
|
1032
|
+
}
|
|
1033
|
+
return base;
|
|
1034
|
+
}
|
package/dist/config-menu.js
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
import { theme } from './theme.js';
|
|
12
12
|
import { visLen } from './input.js';
|
|
13
13
|
const ESC = String.fromCharCode(27);
|
|
14
|
-
// Strip
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// Strip ANSI sequences to compute the visible length of a line. (A module-
|
|
15
|
+
// level name distinct from input.ts's internal helper; the old regex had a
|
|
16
|
+
// stray backtick and never matched a real CSI, so visCols was over-counted.)
|
|
17
|
+
function stripAnsiLocal(s) {
|
|
18
|
+
return s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
|
|
17
19
|
}
|
|
18
20
|
// Visible COLUMNS of a line (ANSI removed, wide chars counted as 2). Using the
|
|
19
21
|
// code-point count made the block height disagree with the real screen on lines
|
|
@@ -21,7 +23,7 @@ function stripAnsi(s) {
|
|
|
21
23
|
// duplicated. A row that fills the terminal exactly also triggers autowrap on
|
|
22
24
|
// some terminals, so we keep one column free here as the editor does.
|
|
23
25
|
function visCols(s) {
|
|
24
|
-
return visLen(
|
|
26
|
+
return visLen(stripAnsiLocal(s));
|
|
25
27
|
}
|
|
26
28
|
function displayValue(field, value, t) {
|
|
27
29
|
if (value === undefined)
|
|
@@ -102,22 +104,82 @@ export function runConfigMenu(opts) {
|
|
|
102
104
|
// start of the block via relative moves so we don't spawn blank lines.
|
|
103
105
|
let lastRows = 0;
|
|
104
106
|
const render = () => {
|
|
105
|
-
const lines = [];
|
|
106
107
|
const vis = visible();
|
|
107
108
|
if (cursor >= vis.length)
|
|
108
109
|
cursor = Math.max(0, vis.length - 1);
|
|
110
|
+
// Trailing block (below the list): the edit/filter prompt or the hints.
|
|
111
|
+
const tail = [''];
|
|
112
|
+
if (editing) {
|
|
113
|
+
tail.push(theme.user(t('cfg.menu.edit_hint')));
|
|
114
|
+
tail.push(theme.prompt('\u276f ') + editBuf);
|
|
115
|
+
}
|
|
116
|
+
else if (filterMode) {
|
|
117
|
+
tail.push(theme.user(t('cfg.menu.filter_hint')));
|
|
118
|
+
tail.push(theme.prompt('\u276f ') + filterQuery);
|
|
119
|
+
}
|
|
120
|
+
else if (message) {
|
|
121
|
+
tail.push(theme.assistant(message));
|
|
122
|
+
}
|
|
123
|
+
else if (filterQuery) {
|
|
124
|
+
tail.push(theme.dim(t('cfg.menu.filter_active', { q: filterQuery }) +
|
|
125
|
+
' ' +
|
|
126
|
+
t('cfg.menu.hint')));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
tail.push(' ' + theme.dim(t('cfg.menu.hint')));
|
|
130
|
+
tail.push(' ' + theme.dim(t('cfg.menu.filter_hint')));
|
|
131
|
+
}
|
|
132
|
+
// How many ITEM rows (fields + group headers) fit: terminal height minus
|
|
133
|
+
// the title and the trailing block, with one spare row. This is what keeps
|
|
134
|
+
// the block from scrolling off the top on a long list.
|
|
135
|
+
const termRows = output.rows || 24;
|
|
136
|
+
// Bias one row to the SAFE side (a spare blank row is harmless, a block
|
|
137
|
+
// that overflows scrolls the top away and breaks the cursor restore).
|
|
138
|
+
const maxItemRows = Math.max(3, termRows - 2 - tail.length);
|
|
139
|
+
// WINDOW over the fields. We walk BACK from the cursor, counting the rows
|
|
140
|
+
// the window would take (a group header consumes a row too), until adding
|
|
141
|
+
// one more field would overflow. This guarantees the SELECTED item is always
|
|
142
|
+
// on screen — the old index-based scrollTop could push it out when many
|
|
143
|
+
// group headers sat between the window top and the cursor.
|
|
144
|
+
const groupStart = (i) => i === 0 || vis[i].groupKey !== vis[i - 1].groupKey;
|
|
145
|
+
// Rows needed to draw the window [from, cursor] (inclusive).
|
|
146
|
+
const rowsFor = (from) => {
|
|
147
|
+
let rows = 0;
|
|
148
|
+
for (let i = from; i <= cursor; i++) {
|
|
149
|
+
if (i === from || groupStart(i))
|
|
150
|
+
rows++;
|
|
151
|
+
rows++;
|
|
152
|
+
}
|
|
153
|
+
return rows;
|
|
154
|
+
};
|
|
155
|
+
let start = vis.length ? Math.max(0, Math.min(cursor, vis.length - 1)) : 0;
|
|
156
|
+
while (start > 0 && rowsFor(start - 1) <= maxItemRows)
|
|
157
|
+
start--;
|
|
158
|
+
const lines = [];
|
|
109
159
|
// Title with a position counter ("Settings — 12/49") so the operator knows
|
|
110
160
|
// how far down a long list they are; a filter narrows both numbers.
|
|
111
161
|
const pos = vis.length ? ' ' + (cursor + 1) + '/' + vis.length : '';
|
|
112
162
|
lines.push(theme.bold(t('cfg.menu.title')) + theme.dim(pos));
|
|
163
|
+
// Group headers also consume a row, so we stop as soon as the item-row
|
|
164
|
+
// budget is used up (the window is advisory; this is the hard cap that
|
|
165
|
+
// guarantees the block fits the terminal).
|
|
166
|
+
let itemRows = 0;
|
|
113
167
|
let lastGroup = '';
|
|
114
|
-
for (let i =
|
|
168
|
+
for (let i = start; i < vis.length; i++) {
|
|
115
169
|
const f = vis[i];
|
|
116
170
|
const g = groupLabel(f, t);
|
|
117
|
-
|
|
171
|
+
// Always print the header for the FIRST shown row, so a windowed list
|
|
172
|
+
// still tells the operator which section it starts in.
|
|
173
|
+
if (i === start || g !== lastGroup) {
|
|
174
|
+
if (itemRows >= maxItemRows)
|
|
175
|
+
break;
|
|
118
176
|
lines.push(g);
|
|
119
177
|
lastGroup = g;
|
|
178
|
+
itemRows++;
|
|
120
179
|
}
|
|
180
|
+
if (itemRows >= maxItemRows)
|
|
181
|
+
break;
|
|
182
|
+
itemRows++;
|
|
121
183
|
const selected = i === cursor;
|
|
122
184
|
const marker = selected ? theme.prompt('\u276f ') : ' ';
|
|
123
185
|
const label = selected
|
|
@@ -132,32 +194,7 @@ export function runConfigMenu(opts) {
|
|
|
132
194
|
if (!vis.length) {
|
|
133
195
|
lines.push(theme.dim(' ' + t('cfg.menu.no_match')));
|
|
134
196
|
}
|
|
135
|
-
|
|
136
|
-
lines.push('');
|
|
137
|
-
lines.push(theme.user(t('cfg.menu.edit_hint')));
|
|
138
|
-
lines.push(theme.prompt('\u276f ') + editBuf);
|
|
139
|
-
}
|
|
140
|
-
else if (filterMode) {
|
|
141
|
-
lines.push('');
|
|
142
|
-
lines.push(theme.user(t('cfg.menu.filter_hint')));
|
|
143
|
-
lines.push(theme.prompt('\u276f ') + filterQuery);
|
|
144
|
-
}
|
|
145
|
-
else if (message) {
|
|
146
|
-
lines.push('');
|
|
147
|
-
lines.push(theme.assistant(message));
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
lines.push('');
|
|
151
|
-
if (filterQuery) {
|
|
152
|
-
lines.push(theme.dim(t('cfg.menu.filter_active', { q: filterQuery }) +
|
|
153
|
-
' ' +
|
|
154
|
-
t('cfg.menu.hint')));
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
lines.push(' ' + theme.dim(t('cfg.menu.hint')));
|
|
158
|
-
lines.push(' ' + theme.dim(t('cfg.menu.filter_hint')));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
197
|
+
lines.push(...tail);
|
|
161
198
|
// Erase the previous block and print the new one. We count VISUAL lines
|
|
162
199
|
// (accounting for wrapping by terminal width), otherwise on narrow
|
|
163
200
|
// terminals you get "garbage" from unerased tails.
|