@pellux/goodvibes-tui 0.27.0 → 0.29.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 +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- 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 +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- 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 +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- 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';
|
|
@@ -8,6 +8,7 @@ import { SyntaxHighlighter, type SyntaxToken } from '../renderer/syntax-highligh
|
|
|
8
8
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
9
9
|
import {
|
|
10
10
|
buildEmptyState,
|
|
11
|
+
buildKeyboardHints,
|
|
11
12
|
buildPanelLine,
|
|
12
13
|
buildPanelWorkspace,
|
|
13
14
|
resolveScrollablePanelSection,
|
|
@@ -19,12 +20,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
19
20
|
|
|
20
21
|
const MAX_FILE_SIZE = 100 * 1024; // 100 KB
|
|
21
22
|
const BG = '#0d0d0d';
|
|
22
|
-
const HEADER_BG = '#1e1e1e';
|
|
23
|
-
const HEADER_FG = '#d4d4d4';
|
|
24
|
-
const HEADER_ACCENT = '#4ec9b0';
|
|
25
23
|
const LINE_NUM_FG = '238';
|
|
26
|
-
const WARNING_FG = '#f44747';
|
|
27
|
-
const EMPTY_FG = '244';
|
|
28
24
|
|
|
29
25
|
// ─── Language Detection (from file extension) ─────────────────────────────────
|
|
30
26
|
|
|
@@ -63,8 +59,31 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
63
59
|
/** Per-file scroll position memory: path -> scrollOffset */
|
|
64
60
|
private readonly scrollMemory = new Map<string, number>();
|
|
65
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
|
+
|
|
66
85
|
constructor() {
|
|
67
|
-
super('preview', 'Preview', '
|
|
86
|
+
super('preview', 'Preview', '◑', 'development');
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -82,6 +101,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
82
101
|
this.filePath = filePath;
|
|
83
102
|
this.oversized = false;
|
|
84
103
|
this.fenceTag = extToFenceTag(filePath);
|
|
104
|
+
this.highlightedLine = null;
|
|
85
105
|
|
|
86
106
|
// Restore scroll position for this file, or start at top
|
|
87
107
|
this.scrollOffset = this.scrollMemory.get(filePath) ?? 0;
|
|
@@ -100,10 +120,53 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
100
120
|
} catch {
|
|
101
121
|
this.fileLines = [`(cannot open: ${filePath})`];
|
|
102
122
|
}
|
|
123
|
+
this.contentVersion++;
|
|
103
124
|
|
|
125
|
+
this._startWatching(filePath);
|
|
104
126
|
void this._loadFileAsync(filePath);
|
|
105
127
|
}
|
|
106
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
|
+
|
|
107
170
|
private async _loadFileAsync(filePath: string): Promise<void> {
|
|
108
171
|
try {
|
|
109
172
|
await this.withLoading('Loading…', async () => {
|
|
@@ -147,6 +210,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
147
210
|
} catch (err) {
|
|
148
211
|
this.setError(summarizeError(err));
|
|
149
212
|
}
|
|
213
|
+
this.contentVersion++;
|
|
150
214
|
this.markDirty();
|
|
151
215
|
}
|
|
152
216
|
|
|
@@ -161,7 +225,9 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
161
225
|
|
|
162
226
|
goToLine(line: number): void {
|
|
163
227
|
if (!Number.isFinite(line)) return;
|
|
164
|
-
|
|
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;
|
|
165
231
|
this.markDirty();
|
|
166
232
|
}
|
|
167
233
|
|
|
@@ -182,6 +248,11 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
182
248
|
}
|
|
183
249
|
}
|
|
184
250
|
|
|
251
|
+
override onDestroy(): void {
|
|
252
|
+
this._stopWatching();
|
|
253
|
+
super.onDestroy();
|
|
254
|
+
}
|
|
255
|
+
|
|
185
256
|
// ─── Input handling ──────────────────────────────────────────────────────────
|
|
186
257
|
|
|
187
258
|
handleInput(key: string): boolean {
|
|
@@ -192,6 +263,12 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
192
263
|
case 'pagedown': return this.scrollPage(1);
|
|
193
264
|
case 'home': return this.scrollTo(0);
|
|
194
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;
|
|
195
272
|
default: return false;
|
|
196
273
|
}
|
|
197
274
|
}
|
|
@@ -273,26 +350,48 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
273
350
|
],
|
|
274
351
|
} as const;
|
|
275
352
|
const footerLines = [
|
|
276
|
-
|
|
353
|
+
buildKeyboardHints(width, [
|
|
354
|
+
{ keys: '↑/↓', label: 'scroll' },
|
|
355
|
+
{ keys: 'PgUp/PgDn', label: 'page' },
|
|
356
|
+
{ keys: 'Home/End', label: 'top/bottom' },
|
|
357
|
+
], DEFAULT_PANEL_PALETTE),
|
|
277
358
|
];
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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 };
|
|
295
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.
|
|
296
395
|
const previewSection = resolveScrollablePanelSection(width, height, {
|
|
297
396
|
intro,
|
|
298
397
|
footerLines,
|
|
@@ -302,6 +401,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
302
401
|
title: 'Preview',
|
|
303
402
|
scrollableLines: previewLines,
|
|
304
403
|
scrollOffset: this.scrollOffset,
|
|
404
|
+
selectedIndex: this.highlightedLine !== null ? this.highlightedLine - 1 : undefined,
|
|
305
405
|
minRows: 8,
|
|
306
406
|
},
|
|
307
407
|
});
|
|
@@ -319,8 +419,10 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
319
419
|
buildPanelLine(width, [
|
|
320
420
|
[' Lines ', DEFAULT_PANEL_PALETTE.label],
|
|
321
421
|
[String(this.fileLines.length), DEFAULT_PANEL_PALETTE.value],
|
|
322
|
-
['
|
|
422
|
+
[' Viewing ', DEFAULT_PANEL_PALETTE.label],
|
|
323
423
|
[`${window.start + 1}-${window.end}`, DEFAULT_PANEL_PALETTE.info],
|
|
424
|
+
[' Lang ', DEFAULT_PANEL_PALETTE.label],
|
|
425
|
+
[this.fenceTag || 'text', this.fenceTag ? DEFAULT_PANEL_PALETTE.value : DEFAULT_PANEL_PALETTE.dim],
|
|
324
426
|
]),
|
|
325
427
|
],
|
|
326
428
|
},
|
|
@@ -340,9 +442,14 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
340
442
|
contentX: number,
|
|
341
443
|
tokens: SyntaxToken[],
|
|
342
444
|
width: number,
|
|
445
|
+
highlighted = false,
|
|
343
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;
|
|
344
451
|
const line: Cell[] = new Array(width).fill(null).map(() =>
|
|
345
|
-
createStyledCell(' ', { bg
|
|
452
|
+
createStyledCell(' ', { bg }),
|
|
346
453
|
);
|
|
347
454
|
|
|
348
455
|
// Line number gutter
|
|
@@ -350,12 +457,12 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
350
457
|
let cx = 0;
|
|
351
458
|
for (const ch of lineNum) {
|
|
352
459
|
if (cx >= lineNumW) break;
|
|
353
|
-
line[cx++] = createStyledCell(ch, { fg: LINE_NUM_FG, bg
|
|
460
|
+
line[cx++] = createStyledCell(ch, { fg: LINE_NUM_FG, bg, dim: true });
|
|
354
461
|
}
|
|
355
462
|
// Separator " | "
|
|
356
|
-
line[cx++] = createStyledCell(' ', { bg
|
|
357
|
-
line[cx++] = createStyledCell('│', { fg: LINE_NUM_FG, bg
|
|
358
|
-
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 });
|
|
359
466
|
|
|
360
467
|
// Syntax tokens
|
|
361
468
|
for (const token of tokens) {
|
|
@@ -366,7 +473,7 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
366
473
|
const cw = getDisplayWidth(ch);
|
|
367
474
|
line[cx] = createStyledCell(ch, {
|
|
368
475
|
fg: token.fg || '',
|
|
369
|
-
bg
|
|
476
|
+
bg,
|
|
370
477
|
bold: token.bold,
|
|
371
478
|
italic: token.italic,
|
|
372
479
|
});
|
|
@@ -378,31 +485,6 @@ export class FilePreviewPanel extends BasePanel {
|
|
|
378
485
|
return line;
|
|
379
486
|
}
|
|
380
487
|
|
|
381
|
-
private renderEmpty(width: number, height: number, message: string): Line[] {
|
|
382
|
-
const lines: Line[] = [];
|
|
383
|
-
const msgLine = createEmptyLine(width);
|
|
384
|
-
const isWarning = message.startsWith('File too large');
|
|
385
|
-
const fg = isWarning ? WARNING_FG : EMPTY_FG;
|
|
386
|
-
let cx = 2;
|
|
387
|
-
for (const ch of message) {
|
|
388
|
-
if (cx >= width - 1) break;
|
|
389
|
-
msgLine[cx++] = createStyledCell(ch, { fg, bg: BG });
|
|
390
|
-
}
|
|
391
|
-
lines.push(msgLine);
|
|
392
|
-
for (let i = 1; i < height; i++) {
|
|
393
|
-
lines.push(this.renderBgLine(width));
|
|
394
|
-
}
|
|
395
|
-
return lines;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private renderBgLine(width: number): Line {
|
|
399
|
-
const line = createEmptyLine(width);
|
|
400
|
-
for (let x = 0; x < width; x++) {
|
|
401
|
-
line[x] = createStyledCell(' ', { bg: BG });
|
|
402
|
-
}
|
|
403
|
-
return line;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
488
|
private scroll(delta: number): boolean {
|
|
407
489
|
const before = this.scrollOffset;
|
|
408
490
|
this.scrollOffset = Math.max(0, this.scrollOffset + delta);
|