vecfinder

Document searching tool based on embedding vectors
git clone git://git.finwo.net/app/vecfinder
Log | Files | Refs

commit dd9d7d30ab0380a8f399f9003a36510c0b6d841a
parent 062d92b04571a4f93163cafab7756ced100ad282
Author: finwo <finwo@pm.me>
Date:   Sat, 21 Mar 2026 05:19:05 +0100

Clamp preview to file length; keep selected file in view

Diffstat:
Mvecfinder.js | 52+++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 3 deletions(-)

diff --git a/vecfinder.js b/vecfinder.js @@ -275,6 +275,7 @@ class UI { this.store = store; this.query = ''; this.selectedIndex = 0; + this.listOffset = 0; this.previewOffset = 0; this.rl = null; this._previewWrapped = null; @@ -286,6 +287,7 @@ class UI { this.store.on('documentAdded', () => this.render()); this.store.on('queryUpdated', () => { this.selectedIndex = 0; + this.listOffset = 0; this.previewOffset = 0; this.render(); }); @@ -309,6 +311,20 @@ class UI { if (this.rl) this.rl.close(); } + adjustListOffset(maxHeight) { + const results = this.store.getResults(); + const topThreshold = this.listOffset + 2; + const bottomThreshold = this.listOffset + maxHeight - 3; + + if (this.selectedIndex < topThreshold) { + this.listOffset = Math.max(0, this.selectedIndex - 2); + } else if (this.selectedIndex > bottomThreshold) { + this.listOffset = Math.min(results.length - maxHeight, this.selectedIndex - (maxHeight - 3)); + } + + this.listOffset = Math.max(0, Math.min(this.listOffset, Math.max(0, results.length - maxHeight))); + } + handleKeyPress(str, key) { if (!key) return; @@ -321,6 +337,9 @@ class UI { if (this.selectedIndex > 0) { this.selectedIndex--; this.previewOffset = 0; + const height = process.stdout.rows || 24; + const maxHeight = height - 4; + this.adjustListOffset(maxHeight); this.render(); } return; @@ -331,11 +350,35 @@ class UI { if (this.selectedIndex < results.length - 1) { this.selectedIndex++; this.previewOffset = 0; + const height = process.stdout.rows || 24; + const maxHeight = height - 4; + this.adjustListOffset(maxHeight); this.render(); } return; } + if (key.name === 'home') { + this.selectedIndex = 0; + this.previewOffset = 0; + const height = process.stdout.rows || 24; + const maxHeight = height - 4; + this.adjustListOffset(maxHeight); + this.render(); + return; + } + + if (key.name === 'end') { + const results = this.store.getResults(); + this.selectedIndex = results.length - 1; + this.previewOffset = 0; + const height = process.stdout.rows || 24; + const maxHeight = height - 4; + this.adjustListOffset(maxHeight); + this.render(); + return; + } + if (key.name === 'pageup') { this.previewOffset = Math.max(0, this.previewOffset - 10); this.render(); @@ -343,7 +386,10 @@ class UI { } if (key.name === 'pagedown') { - this.previewOffset += 10; + const height = process.stdout.rows || 24; + const previewHeight = height - 4; + const maxPreviewOffset = this._previewWrapped ? Math.max(0, this._previewWrapped.length - previewHeight) : 0; + this.previewOffset = Math.min(maxPreviewOffset, this.previewOffset + 10); this.render(); return; } @@ -395,8 +441,8 @@ class UI { // Rows for (let row = 0; row < maxHeight; row++) { - const result = results[row]; - const isSelected = (row === this.selectedIndex); + const result = results[row + this.listOffset]; + const isSelected = (row + this.listOffset === this.selectedIndex); // Left pane - name left, score right let left;