- Reorder Category() to check known extensions (text, config, image,
pdf, audio, video, archive) before the executable bit check.
Fixes video/audio/image files with executable bit being opened in
editor instead of system default application.
- Remove 'executable' from isEditableEntry() - executables are now
launched via handleExecute() instead of opened in Neovim.
- Add handleExecute() method that runs executable files in the
terminal via tea.ExecProcess.
- Update handleOpenSelected() to route: text/config -> editor,
executable -> launch, everything else -> system default (xdg-open).
- Bump version to v0.2.1
Add rich preview support for three new file categories by leveraging
external CLI tools with graceful fallback when tools are missing.
- PDF: text extraction via pdftotext, page count via pdfinfo
- Audio: metadata via ffprobe (duration, bitrate, codec, sample rate, channels)
- Video: metadata via ffprobe (duration, bitrate, video/audio codec, resolution)
- New PreviewKind constants: PDF, Audio, Video
- New Metadata fields for extended preview data
- New extension maps and Category() entries for pdf/audio/video
- Icons: PDF (), audio (), video () in preview header
Closes#5
Pressing / opens a filter bar at the bottom of the screen. As the user
types, the active pane's entries are filtered in real-time using
case-insensitive substring matching. The .. entry is always preserved.
Key bindings:
/ - open filter input (re-opens with previous query if active)
Esc - clear filter and restore full list
Enter - commit filter (keeps filtering active, closes input bar)
Up/Down - navigate the filtered list while typing
PgUp/Dn - scroll the filtered list
Filter is automatically cleared when navigating to a different directory
(Enter on a folder, Backspace to parent, opening an archive).
Modified files:
- internal/ui/keymap.go: added Filter key binding (/)
- internal/ui/model.go: filterMode, filterQuery, filterInput fields;
filteredPane() helper for View rendering; filter handling in Update();
renderFilterBar() for the bottom bar UI; clearFilter() helper;
adjustCursorForFilter() to keep cursor in bounds
Created files:
- plans/feature-roadmap.md
Root cause: renderPreviewPane() was stuffing directory entries into the
viewport model via SetContent(), then rendering through
renderPreviewContent() which wraps the viewport in an additional
border+padding box. This reduced the effective content width by 4
characters compared to the browser pane's innerWidth (width-2), causing
column misalignment where the date column would wrap to the next line.
Fix: For directory previews, directly render entries using the same
renderPaneRows() and renderColumnsHeader() functions as the browser
pane, at the same innerWidth. A temporary BrowserPane is created with
the directory entries, and renderPaneRows() is called with active=false,
producing pixel-identical output to the main file browser.
Removed renderDirectoryPreviewBody() which is no longer needed.
- Reset cursor to '..' (position 0) when entering an archive so it
doesn't stay at the previous directory's cursor position
- Fix archive exit landing in /tmp when pressing Enter on '..' inside
an archive by delegating to archive-aware goParent() in enterSelected()
- Fix goParent() to handle ALL navigation within archive mounts
explicitly instead of falling through to filepath.Dir which may
navigate outside the mount to /tmp
- Fix format cycling text color reset by preserving '(f to change)'
hint in modal note, ensuring renderModalNoteLine applies proper
key/action styling via the '(' separator match
Replace full ANSI reset sequences (\x1b[0m) added by lipgloss.Render() for
gutter markers, cursor highlights, and selection highlights with
background-preserving resets (\x1b[39;22;23;24;59;48;2;R;G;Bm) that restore
the panel background color.
Without this fix, every \x1b[0m from lipgloss-styled elements in
renderTextCursorContent resets the panel background (set by the outer
renderPreviewContent wrapper), causing some letters to have incorrect or
missing background color in caret mode.