commit 72066aa741afccc5b9a7fdd8ba7a79f686e3dc0f
parent 34583f3464ede2612ffc906a0bbe46437167feff
Author: finwo <finwo@pm.me>
Date: Sat, 21 Mar 2026 16:02:46 +0100
Fix redraw flickering when idle
Diffstat:
| M | vecfinder.js | | | 67 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
1 file changed, 58 insertions(+), 9 deletions(-)
diff --git a/vecfinder.js b/vecfinder.js
@@ -313,12 +313,18 @@ class UI {
this.useBat = useBatFlag !== null ? useBatFlag : checkBat();
this._batOutput = null;
this._batFileId = null;
+ this._lastRenderState = null;
+ this._needsFullRedraw = false;
- this.store.on('documentAdded', () => this.render());
+ this.store.on('documentAdded', () => {
+ this._needsFullRedraw = true;
+ this.render();
+ });
this.store.on('queryUpdated', () => {
this.selectedIndex = 0;
this.listOffset = 0;
this.previewOffset = 0;
+ this._needsFullRedraw = true;
this.render();
});
}
@@ -456,8 +462,6 @@ class UI {
}
render() {
- stderr.write('\x1b[2J\x1b[H');
-
const width = process.stdout.columns || 80;
const height = process.stdout.rows || 24;
@@ -475,15 +479,63 @@ class UI {
const titleCenterLeft = Math.floor(dashSpace / 2);
const titleCenterRight = dashSpace - titleCenterLeft;
- // Title bar
+ const selectedResult = results[this.selectedIndex];
+
+ const visibleIds = [];
+ for (let row = 0; row < maxHeight; row++) {
+ const result = results[row + this.listOffset];
+ visibleIds.push(result ? result.id : null);
+ }
+
+ const currentState = {
+ width,
+ height,
+ leftWidth,
+ rightWidth,
+ query: this.query,
+ selectedIndex: this.selectedIndex,
+ listOffset: this.listOffset,
+ previewOffset: this.previewOffset,
+ spinner,
+ consumersActive: this.store.consumersActive,
+ selectedFileId: selectedResult?.id,
+ visibleIds,
+ batFileId: this._batFileId,
+ batOutputHash: this._batOutput ? this._batOutput.slice(0, 100) : null,
+ };
+
+ if (!this._needsFullRedraw && this._lastRenderState) {
+ const s = this._lastRenderState;
+ if (
+ s.width === currentState.width &&
+ s.height === currentState.height &&
+ s.leftWidth === currentState.leftWidth &&
+ s.rightWidth === currentState.rightWidth &&
+ s.query === currentState.query &&
+ s.selectedIndex === currentState.selectedIndex &&
+ s.listOffset === currentState.listOffset &&
+ s.previewOffset === currentState.previewOffset &&
+ s.spinner === currentState.spinner &&
+ s.consumersActive === currentState.consumersActive &&
+ s.selectedFileId === currentState.selectedFileId &&
+ s.visibleIds.length === currentState.visibleIds.length &&
+ s.visibleIds.every((id, i) => id === currentState.visibleIds[i]) &&
+ s.batFileId === currentState.batFileId
+ ) {
+ return;
+ }
+ }
+ this._needsFullRedraw = false;
+ this._lastRenderState = currentState;
+
+ stderr.write('\x1b[2J\x1b[H');
+
stderr.write('─'.repeat(titleCenterLeft) + '┤ VecFinder ├' + '─'.repeat(titleCenterRight) + '┬' + '─'.repeat(rightWidth) + '\n');
- // File header line
let leftHeader = 'Files ' + spinner;
leftHeader = leftHeader.padEnd(leftWidth, ' ').slice(0, leftWidth);
let rightHeader = '';
- const selectedResult = results[this.selectedIndex];
if (selectedResult) {
const title = 'File: ' + selectedResult.id;
rightHeader = title.padEnd(rightWidth, ' ').slice(0, rightWidth);
@@ -493,10 +545,8 @@ class UI {
stderr.write(leftHeader + '│' + rightHeader + '\n');
- // Separator line
stderr.write('─'.repeat(leftWidth) + '┼' + '─'.repeat(rightWidth) + '\n');
- // Rows
for (let row = 0; row < maxHeight; row++) {
const result = results[row + this.listOffset];
const isSelected = (row + this.listOffset === this.selectedIndex);
@@ -569,7 +619,6 @@ class UI {
stderr.write('\n');
}
- // Footer
stderr.write('─'.repeat(leftWidth) + '┴' + '─'.repeat(rightWidth) + '\n');
stderr.write(`Query: ${this.query}`);
}