@pellux/goodvibes-tui 0.28.0 → 1.0.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/CHANGELOG.md +133 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/conversation-line-cache.ts +432 -0
- package/src/core/conversation-rendering.ts +11 -3
- package/src/core/conversation.ts +50 -4
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-content-actions.ts +8 -3
- package/src/input/handler-feed-routes.ts +12 -35
- package/src/input/handler-feed.ts +4 -8
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler.ts +43 -6
- package/src/input/input-history.ts +17 -4
- package/src/input/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +19 -11
- package/src/panels/agent-inspector-panel.ts +125 -36
- package/src/panels/agent-inspector-shared.ts +144 -0
- package/src/panels/approval-panel.ts +67 -16
- package/src/panels/automation-control-panel.ts +368 -124
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +232 -73
- package/src/panels/communication-panel.ts +58 -20
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +351 -118
- package/src/panels/cost-tracker-panel.ts +165 -7
- package/src/panels/debug-panel.ts +312 -159
- package/src/panels/diff-panel.ts +209 -57
- package/src/panels/docs-panel.ts +152 -66
- package/src/panels/eval-panel.ts +220 -42
- package/src/panels/file-explorer-panel.ts +202 -122
- package/src/panels/file-preview-panel.ts +132 -57
- package/src/panels/git-panel.ts +310 -129
- package/src/panels/hooks-panel.ts +150 -22
- package/src/panels/incident-review-panel.ts +223 -35
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +212 -86
- package/src/panels/knowledge-graph-panel.ts +461 -101
- package/src/panels/local-auth-panel.ts +240 -28
- package/src/panels/marketplace-panel.ts +193 -26
- package/src/panels/memory-panel.ts +78 -77
- package/src/panels/ops-control-panel.ts +146 -29
- package/src/panels/ops-strategy-panel.ts +72 -4
- package/src/panels/orchestration-panel.ts +231 -69
- package/src/panels/panel-list-panel.ts +154 -131
- package/src/panels/panel-manager.ts +97 -9
- package/src/panels/plan-dashboard-panel.ts +333 -17
- package/src/panels/plugins-panel.ts +184 -29
- package/src/panels/policy-panel.ts +210 -38
- package/src/panels/polish-core.ts +7 -2
- package/src/panels/polish.ts +23 -4
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +62 -113
- package/src/panels/provider-health-panel.ts +434 -518
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +116 -30
- package/src/panels/remote-panel.ts +114 -36
- package/src/panels/routes-panel.ts +63 -22
- package/src/panels/sandbox-panel.ts +174 -42
- package/src/panels/scrollable-list-panel.ts +19 -135
- package/src/panels/security-panel.ts +133 -33
- package/src/panels/services-panel.ts +116 -64
- package/src/panels/session-browser-panel.ts +73 -2
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +335 -72
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +86 -33
- package/src/panels/symbol-outline-panel.ts +248 -108
- package/src/panels/system-messages-panel.ts +114 -13
- package/src/panels/tasks-panel.ts +326 -139
- package/src/panels/thinking-panel.ts +43 -10
- package/src/panels/token-budget-panel.ts +194 -18
- package/src/panels/tool-inspector-panel.ts +144 -34
- package/src/panels/types.ts +40 -4
- package/src/panels/work-plan-panel.ts +280 -17
- package/src/panels/worktree-panel.ts +175 -42
- package/src/panels/wrfc-panel.ts +116 -24
- package/src/renderer/agent-detail-modal.ts +37 -8
- package/src/renderer/code-block.ts +58 -28
- package/src/renderer/context-inspector.ts +3 -6
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/conversation-surface.ts +1 -21
- package/src/renderer/diff-view.ts +6 -4
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +23 -17
- package/src/renderer/fullscreen-workspace.ts +2 -7
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/history-search-overlay.ts +10 -16
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/markdown.ts +9 -1
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +8 -485
- package/src/renderer/model-workspace.ts +9 -24
- package/src/renderer/overlay-box.ts +23 -19
- package/src/renderer/process-indicator.ts +15 -25
- package/src/renderer/profile-picker-modal.ts +13 -14
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/selection-modal-overlay.ts +3 -1
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal-helpers.ts +10 -34
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +54 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/surface-layout.ts +0 -12
- package/src/renderer/term-caps.ts +1 -1
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/tool-call.ts +6 -20
- package/src/renderer/ui-factory.ts +48 -37
- package/src/renderer/ui-primitives.ts +41 -3
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/render-scheduler.ts +80 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +54 -5
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -635
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -238
- package/src/panels/forensics-panel.ts +0 -378
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -255
- package/src/panels/provider-stats-panel.ts +0 -391
- package/src/panels/schedule-panel.ts +0 -365
- package/src/panels/watchers-panel.ts +0 -213
- package/src/renderer/file-tree.ts +0 -153
- package/src/renderer/progress.ts +0 -99
- package/src/renderer/status-token.ts +0 -67
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Stats } from 'node:fs';
|
|
2
|
-
import { promises as fsPromises, readFileSync, statSync } from 'node:fs';
|
|
1
|
+
import type { Stats, FSWatcher } from 'node:fs';
|
|
2
|
+
import { promises as fsPromises, readFileSync, statSync, watch } from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import type { Line, Cell } from '../types/grid.ts';
|
|
5
5
|
import { createStyledCell, createEmptyLine } from '../types/grid.ts';
|
|
@@ -20,12 +20,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
20
20
|
|
|
21
21
|
const MAX_FILE_SIZE = 100 * 1024; // 100 KB
|
|
22
22
|
const BG = '#0d0d0d';
|
|
23
|
-
const HEADER_BG = '#1e1e1e';
|
|
24
|
-
const HEADER_FG = '#d4d4d4';
|
|
25
|
-
const HEADER_ACCENT = '#4ec9b0';
|
|
26
23
|
const LINE_NUM_FG = '238';
|
|
27
|
-
const WARNING_FG = '#f44747';
|
|
28
|
-
const EMPTY_FG = '244';
|
|
29
24
|
|
|
30
25
|
// ─── Language Detection (from file extension) ─────────────────────────────────
|
|
31
26
|
|
|
@@ -64,8 +59,31 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
64
59
|
/** Per-file scroll position memory: path -> scrollOffset */
|
|
65
60
|
private readonly scrollMemory = new Map<string, number>();
|
|
66
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 1-based line number to visibly highlight, set by goToLine() (e.g. a
|
|
64
|
+
* symbol-outline jump). Cleared when a new file is opened.
|
|
65
|
+
*/
|
|
66
|
+
private highlightedLine: number | null = null;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Monotonic counter bumped every time fileLines is replaced. Combined with
|
|
70
|
+
* filePath/width/highlightedLine, forms the cache key for the built
|
|
71
|
+
* preview lines so render() doesn't rebuild the whole file every frame.
|
|
72
|
+
*/
|
|
73
|
+
private contentVersion = 0;
|
|
74
|
+
private cachedPreviewLines: { key: string; lines: Line[] } | null = null;
|
|
75
|
+
|
|
76
|
+
/** Set by the 'r' key; consumed by handlePanelIntegrationAction on the next
|
|
77
|
+
* dispatch, which awaits the reload before re-syncing the symbol outline
|
|
78
|
+
* (panel-integration-actions.ts owns the panelManager reference this
|
|
79
|
+
* panel doesn't have). */
|
|
80
|
+
private pendingReload = false;
|
|
81
|
+
|
|
82
|
+
/** Optional live-refresh watcher on the currently open file. */
|
|
83
|
+
private watcher: FSWatcher | null = null;
|
|
84
|
+
|
|
67
85
|
constructor() {
|
|
68
|
-
super('preview', 'Preview', '
|
|
86
|
+
super('preview', 'Preview', '◑', 'development');
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -83,6 +101,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
83
101
|
this.filePath = filePath;
|
|
84
102
|
this.oversized = false;
|
|
85
103
|
this.fenceTag = extToFenceTag(filePath);
|
|
104
|
+
this.highlightedLine = null;
|
|
86
105
|
|
|
87
106
|
// Restore scroll position for this file, or start at top
|
|
88
107
|
this.scrollOffset = this.scrollMemory.get(filePath) ?? 0;
|
|
@@ -101,10 +120,53 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
101
120
|
} catch {
|
|
102
121
|
this.fileLines = [`(cannot open: ${filePath})`];
|
|
103
122
|
}
|
|
123
|
+
this.contentVersion++;
|
|
104
124
|
|
|
125
|
+
this._startWatching(filePath);
|
|
105
126
|
void this._loadFileAsync(filePath);
|
|
106
127
|
}
|
|
107
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Re-read the current file from disk (the 'r' reload key). Returns the
|
|
131
|
+
* pending load's promise so callers can chain follow-up work (e.g.
|
|
132
|
+
* re-syncing the symbol outline) after it settles, or null if there is no
|
|
133
|
+
* open file or no reload was queued via the 'r' key.
|
|
134
|
+
*/
|
|
135
|
+
consumePendingReload(): Promise<void> | null {
|
|
136
|
+
if (!this.pendingReload) return null;
|
|
137
|
+
this.pendingReload = false;
|
|
138
|
+
if (this.filePath === null) return null;
|
|
139
|
+
return this._loadFileAsync(this.filePath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Start (or restart) an optional fs.watch auto-refresh on the given file.
|
|
144
|
+
* Best-effort: some filesystems/platforms don't support watch, so failures
|
|
145
|
+
* are swallowed and auto-refresh is simply unavailable for this file — the
|
|
146
|
+
* explicit 'r' reload key always still works.
|
|
147
|
+
*/
|
|
148
|
+
private _startWatching(filePath: string): void {
|
|
149
|
+
this._stopWatching();
|
|
150
|
+
try {
|
|
151
|
+
const watcher = watch(filePath, { persistent: false }, (eventType) => {
|
|
152
|
+
if (eventType !== 'change' && eventType !== 'rename') return;
|
|
153
|
+
if (this.filePath !== filePath) return; // stale watcher from a since-replaced file
|
|
154
|
+
void this._loadFileAsync(filePath);
|
|
155
|
+
});
|
|
156
|
+
watcher.on('error', () => this._stopWatching());
|
|
157
|
+
this.watcher = watcher;
|
|
158
|
+
} catch {
|
|
159
|
+
this.watcher = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private _stopWatching(): void {
|
|
164
|
+
if (this.watcher) {
|
|
165
|
+
try { this.watcher.close(); } catch { /* already closed */ }
|
|
166
|
+
this.watcher = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
108
170
|
private async _loadFileAsync(filePath: string): Promise<void> {
|
|
109
171
|
try {
|
|
110
172
|
await this.withLoading('Loading…', async () => {
|
|
@@ -148,6 +210,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
148
210
|
} catch (err) {
|
|
149
211
|
this.setError(summarizeError(err));
|
|
150
212
|
}
|
|
213
|
+
this.contentVersion++;
|
|
151
214
|
this.markDirty();
|
|
152
215
|
}
|
|
153
216
|
|
|
@@ -162,7 +225,9 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
162
225
|
|
|
163
226
|
goToLine(line: number): void {
|
|
164
227
|
if (!Number.isFinite(line)) return;
|
|
165
|
-
|
|
228
|
+
const target = Math.max(0, Math.min(Math.floor(line) - 1, Math.max(0, this.fileLines.length - 1)));
|
|
229
|
+
this.scrollOffset = target;
|
|
230
|
+
this.highlightedLine = target + 1;
|
|
166
231
|
this.markDirty();
|
|
167
232
|
}
|
|
168
233
|
|
|
@@ -183,6 +248,11 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
183
248
|
}
|
|
184
249
|
}
|
|
185
250
|
|
|
251
|
+
override onDestroy(): void {
|
|
252
|
+
this._stopWatching();
|
|
253
|
+
super.onDestroy();
|
|
254
|
+
}
|
|
255
|
+
|
|
186
256
|
// ─── Input handling ──────────────────────────────────────────────────────────
|
|
187
257
|
|
|
188
258
|
handleInput(key: string): boolean {
|
|
@@ -193,6 +263,12 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
193
263
|
case 'pagedown': return this.scrollPage(1);
|
|
194
264
|
case 'home': return this.scrollTo(0);
|
|
195
265
|
case 'end': return this.scrollTo(Infinity);
|
|
266
|
+
case 'r':
|
|
267
|
+
if (this.filePath === null) return false;
|
|
268
|
+
this.pendingReload = true;
|
|
269
|
+
return true;
|
|
270
|
+
case 'd':
|
|
271
|
+
return this.filePath !== null;
|
|
196
272
|
default: return false;
|
|
197
273
|
}
|
|
198
274
|
}
|
|
@@ -280,24 +356,42 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
280
356
|
{ keys: 'Home/End', label: 'top/bottom' },
|
|
281
357
|
], DEFAULT_PANEL_PALETTE),
|
|
282
358
|
];
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
359
|
+
// Line-build cache keyed on (filePath, contentVersion, width, highlightedLine):
|
|
360
|
+
// rebuilding every line on every frame showed up as an O(file-length)
|
|
361
|
+
// cost per render even when nothing changed (e.g. while idly scrolling
|
|
362
|
+
// within the same file). Only re-tokenize/re-render when the underlying
|
|
363
|
+
// content, panel width, or highlighted row actually changes.
|
|
364
|
+
const cacheKey = `${this.filePath ?? ''}${this.contentVersion}${width}${this.highlightedLine ?? ''}`;
|
|
365
|
+
let previewLines: Line[];
|
|
366
|
+
if (this.cachedPreviewLines && this.cachedPreviewLines.key === cacheKey) {
|
|
367
|
+
previewLines = this.cachedPreviewLines.lines;
|
|
368
|
+
} else {
|
|
369
|
+
const fullCode = this.fileLines.join('\n');
|
|
370
|
+
const hlLines = this.fenceTag
|
|
371
|
+
? this.syntaxHighlighter.highlight(fullCode, this.fenceTag)
|
|
372
|
+
: null;
|
|
373
|
+
|
|
374
|
+
const lineNumW = String(this.fileLines.length).length;
|
|
375
|
+
const contentX = lineNumW + 2; // "NNN | "
|
|
376
|
+
const built: Line[] = [];
|
|
377
|
+
for (let fileIdx = 0; fileIdx < this.fileLines.length; fileIdx++) {
|
|
378
|
+
|
|
379
|
+
const rawLine = this.fileLines[fileIdx];
|
|
380
|
+
const tokens: SyntaxToken[] =
|
|
381
|
+
hlLines && fileIdx < hlLines.length && hlLines[fileIdx].length > 0
|
|
382
|
+
? (hlLines[fileIdx] as SyntaxToken[])
|
|
383
|
+
: [{ text: rawLine, fg: '' }];
|
|
384
|
+
|
|
385
|
+
const highlighted = this.highlightedLine !== null && fileIdx === this.highlightedLine - 1;
|
|
386
|
+
built.push(this.renderCodeLine(fileIdx, lineNumW, contentX, tokens, width, highlighted));
|
|
387
|
+
}
|
|
388
|
+
previewLines = built;
|
|
389
|
+
this.cachedPreviewLines = { key: cacheKey, lines: previewLines };
|
|
300
390
|
}
|
|
391
|
+
// Once a goToLine target is set, track it with a selectedIndex so the
|
|
392
|
+
// scrollable-section window keeps that row visible (with guard rows)
|
|
393
|
+
// the same way list-cursor panels do; plain free-scroll (no active
|
|
394
|
+
// highlight) keeps using the scrollOffset-only window it always has.
|
|
301
395
|
const previewSection = resolveScrollablePanelSection(width, height, {
|
|
302
396
|
intro,
|
|
303
397
|
footerLines,
|
|
@@ -307,6 +401,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
307
401
|
title: 'Preview',
|
|
308
402
|
scrollableLines: previewLines,
|
|
309
403
|
scrollOffset: this.scrollOffset,
|
|
404
|
+
selectedIndex: this.highlightedLine !== null ? this.highlightedLine - 1 : undefined,
|
|
310
405
|
minRows: 8,
|
|
311
406
|
},
|
|
312
407
|
});
|
|
@@ -347,9 +442,14 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
347
442
|
contentX: number,
|
|
348
443
|
tokens: SyntaxToken[],
|
|
349
444
|
width: number,
|
|
445
|
+
highlighted = false,
|
|
350
446
|
): Line {
|
|
447
|
+
// The goToLine target row (e.g. a symbol-outline jump) renders on the
|
|
448
|
+
// theme's selection background instead of the plain code background so
|
|
449
|
+
// it's visibly distinguishable from the rest of the file.
|
|
450
|
+
const bg = highlighted ? DEFAULT_PANEL_PALETTE.selectBg : BG;
|
|
351
451
|
const line: Cell[] = new Array(width).fill(null).map(() =>
|
|
352
|
-
createStyledCell(' ', { bg
|
|
452
|
+
createStyledCell(' ', { bg }),
|
|
353
453
|
);
|
|
354
454
|
|
|
355
455
|
// Line number gutter
|
|
@@ -357,12 +457,12 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
357
457
|
let cx = 0;
|
|
358
458
|
for (const ch of lineNum) {
|
|
359
459
|
if (cx >= lineNumW) break;
|
|
360
|
-
line[cx++] = createStyledCell(ch, { fg: LINE_NUM_FG, bg
|
|
460
|
+
line[cx++] = createStyledCell(ch, { fg: LINE_NUM_FG, bg, dim: true });
|
|
361
461
|
}
|
|
362
462
|
// Separator " | "
|
|
363
|
-
line[cx++] = createStyledCell(' ', { bg
|
|
364
|
-
line[cx++] = createStyledCell('│', { fg: LINE_NUM_FG, bg
|
|
365
|
-
line[cx++] = createStyledCell(' ', { bg
|
|
463
|
+
line[cx++] = createStyledCell(' ', { bg });
|
|
464
|
+
line[cx++] = createStyledCell('│', { fg: LINE_NUM_FG, bg, dim: true });
|
|
465
|
+
line[cx++] = createStyledCell(' ', { bg });
|
|
366
466
|
|
|
367
467
|
// Syntax tokens
|
|
368
468
|
for (const token of tokens) {
|
|
@@ -373,7 +473,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
373
473
|
const cw = getDisplayWidth(ch);
|
|
374
474
|
line[cx] = createStyledCell(ch, {
|
|
375
475
|
fg: token.fg || '',
|
|
376
|
-
bg
|
|
476
|
+
bg,
|
|
377
477
|
bold: token.bold,
|
|
378
478
|
italic: token.italic,
|
|
379
479
|
});
|
|
@@ -385,31 +485,6 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
385
485
|
return line;
|
|
386
486
|
}
|
|
387
487
|
|
|
388
|
-
private renderEmpty(width: number, height: number, message: string): Line[] {
|
|
389
|
-
const lines: Line[] = [];
|
|
390
|
-
const msgLine = createEmptyLine(width);
|
|
391
|
-
const isWarning = message.startsWith('File too large');
|
|
392
|
-
const fg = isWarning ? WARNING_FG : EMPTY_FG;
|
|
393
|
-
let cx = 2;
|
|
394
|
-
for (const ch of message) {
|
|
395
|
-
if (cx >= width - 1) break;
|
|
396
|
-
msgLine[cx++] = createStyledCell(ch, { fg, bg: BG });
|
|
397
|
-
}
|
|
398
|
-
lines.push(msgLine);
|
|
399
|
-
for (let i = 1; i < height; i++) {
|
|
400
|
-
lines.push(this.renderBgLine(width));
|
|
401
|
-
}
|
|
402
|
-
return lines;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private renderBgLine(width: number): Line {
|
|
406
|
-
const line = createEmptyLine(width);
|
|
407
|
-
for (let x = 0; x < width; x++) {
|
|
408
|
-
line[x] = createStyledCell(' ', { bg: BG });
|
|
409
|
-
}
|
|
410
|
-
return line;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
488
|
private scroll(delta: number): boolean {
|
|
414
489
|
const before = this.scrollOffset;
|
|
415
490
|
this.scrollOffset = Math.max(0, this.scrollOffset + delta);
|