A fast, keyboard-driven file explorer, code viewer and full git client in a single Herdr pane — written in Go with Bubble Tea. Think a tiny mash-up of a VS Code file tree, a fuzzy finder, ripgrep-style search, and lazygit — all in the terminal.
The four-panel browse screen — file tree + code view on top, the search panel and the git panel docked below:
Plain-text renders of other views live in
docs/screenshots/.
Three things in one pane:
- File browser — a navigable, lazily-expanded directory tree with git status decorations: modified, new, deleted and renamed files are colored and badged, and directories containing changes are tinted (VS Code style).
- Rendered markdown —
.mdfiles display as formatted documents with real box-drawn tables, framed code blocks that use the same syntax highlighter as the source view, task-list checkboxes and clickable links. Pressmto toggle to raw source. - Mermaid diagrams —
```mermaidfences are drawn directly in the terminal.sequenceDiagramandflowchart/graphare supported; any other dialect falls back to a highlighted code block, so nothing is ever lost. - Image preview — PNG, JPEG, GIF, BMP, TIFF and WebP, both as standalone files and inline inside a markdown document. Where the terminal speaks the Kitty graphics protocol the image is drawn as real pixels; everywhere else it degrades to half-block colour cells.
- PDF text —
.pdffiles open as readable documents. This is text extraction, not page rasterisation: see Built with. - Persistent search panel — always docked bottom-left.
Ctrl+Pfocuses it in fuzzy file-find mode,Ctrl+Fin content-search mode (with case-sensitiveAa, whole-wordaband regex.*toggles). Arrow keys preview each result live in the file view;Enteropens it. - Persistent git panel — always docked bottom-right, titled with the current
branch. Focus it with
gorTab; pressgagain to toggle between the commit history (Enter opens a commit's full multi-file diff) and the branch list (Enter switches to the selected branch). - Review / diff view (
d) — see a file's changes againstHEAD. Modified files render side-by-side (old on the left, new on the right, changed lines aligned) so you can see exactly what changed; brand-new files render inline as all-additions. Presssto toggle the layout; narrow panes fall back to inline automatically.
The search and fuzzy-find engines are pure Go standard library — no
ripgrep, no fzf. The only external tool is git, used solely for the status
decorations; without it (or outside a repo) the viewer still works, just without
decorations.
The browse screen is a fixed four-panel workspace:
┌───────────────┬─────────────────────────────┐
│ file tree │ file view (syntax / markdown)│
│ │ │
├───────────────┴──────────────┬──────────────┤
│ ── FIND FILE / SEARCH ── │ ── GIT LOG · branch ──
│ (fuzzy find / content search) │ (commit history) │
└───────────────────────────────┴──────────────┘
Tab cycles focus through the four panels: tree → file view → search → git log.
Alt+h/j/k/l (or Alt+arrows) moves focus by direction. The search and
git-log panels are always visible.
The git panel cycles through three views with g: staging (changed files),
branches, and commit history.
A lazygit-style directory tree of changed files, with common path chains
collapsed. Each row has a checkbox: [✓] staged (green), [~] partially staged,
[ ] unstaged. Directory rows aggregate their subtree.
| Key | Action |
|---|---|
space |
Stage / unstage the selected file — or a whole directory subtree |
A / U |
Stage all / unstage all |
c |
Commit the staged changes (prompts for a message) |
Enter |
View the selected file's diff |
From the branch view you can drive the whole git flow:
| Key | Action | |
|---|---|---|
Enter |
Switch to the selected branch | |
n |
Create a new branch (from the current one) | prompt |
c |
Commit staged changes (stage first with space/A) |
prompt |
a |
Amend the last commit with current changes | confirm |
u |
Undo the last commit (keeps changes staged) | confirm |
m |
Merge the selected branch into the current one | confirm |
r |
Rebase the current branch onto the selected one | confirm |
x |
Delete the selected branch | confirm |
A |
Stage all (git add -A) |
|
t |
Create a tag at HEAD | prompt |
f |
Fetch all remotes (prune) | |
p |
Pull | |
P |
Push | |
F |
Force-push (--force-with-lease) |
confirm |
s / S |
Stash / stash pop | |
H |
Reset --hard HEAD (discard all changes) |
confirm |
In the history view: y cherry-picks the selected commit onto the current
branch (confirm), and R resets --hard the current branch to the selected
commit (confirm).
Anything that rewrites history, deletes, discards, or force-pushes asks for confirmation first. Any git error (conflicts, refusals, no upstream) is shown in red inside the panel — nothing silently fails.
| Key | Action |
|---|---|
↑/k, ↓/j |
Move the cursor |
→/l, ←/h |
Expand / collapse a directory |
Enter / Space |
Open a file or toggle a directory |
Tab |
Cycle focus through the four panels |
Alt+h/j/k/l, Alt+arrows |
Move focus by direction |
Ctrl+P |
Focus the search panel in file-find mode |
Ctrl+F |
Focus the search panel in content-search mode |
L |
Locate the open file in the tree (reveal + focus it) |
o |
Open the file's location in the OS file manager |
e |
Edit the current file in your editor (configurable) |
E |
Open the whole project in your editor |
d |
Review the selected/open file's diff against HEAD |
g |
Focus the git-log panel (toggle back to the tree) |
m |
Toggle rendered document ↔ source (markdown and PDF) |
r |
Refresh git status and the file index (e.g. after a commit) |
q / Ctrl+C |
Quit |
Scroll with ↑/↓ / PgUp / PgDn; s toggles split ↔ inline; d, Esc
or q return to the browser.
| Badge | Color | Meaning |
|---|---|---|
M |
amber | Modified |
U |
green | Untracked (new) |
A |
green | Added (staged) |
D |
red | Deleted |
R |
blue | Renamed |
! |
pink | Conflicted |
Decorations appear only inside a git repository; elsewhere the tree renders plainly.
The search panel is always docked bottom-left. Ctrl+P / Ctrl+F focus it and
switch it between file-find and content-search; within it, ↑/↓ preview and
Enter opens. Press ? any time for the full keybinding reference.
The status line always shows each toggle's on/off state. Ctrl shortcuts work
everywhere (a terminal can't send Alt from macOS's Option key); the Alt
aliases work on Linux/Windows.
| Key | Toggle |
|---|---|
Ctrl+t (Alt+c) |
Case sensitive (Aa) |
Ctrl+w (Alt+w) |
Whole word (ab) |
Ctrl+r (Alt+r) |
Regular expression (.*) |
herdr plugin install ismaelosuna7824/herdr-file-viewerOn install the [[build]] step downloads a prebuilt binary for your platform
(macOS/Linux, amd64/arm64) from the GitHub release — no Go required. If a
prebuilt binary isn't available it falls back to go build (needs Go 1.25+).
This repo is tagged with the herdr-plugin topic, so it also shows up in Herdr's
plugin marketplace (/plugins/).
git clone https://github.com/ismaelosuna7824/herdr-file-viewer
herdr plugin link herdr-file-viewerThe plugin ships with two default keybindings (declared in its manifest):
| Keys | Opens |
|---|---|
<prefix> f |
The viewer in a split beside your work |
<prefix> Shift+f |
The viewer in its own tab |
(<prefix> is your Herdr prefix key.) You can also run it from the action menu:
Open File Viewer / Open File Viewer (tab).
To use different keys, add them to ~/.config/herdr/config.toml, invoking the
plugin's actions (open = split, open-tab = tab):
[[keys.command]] # open beside your work (split)
key = "prefix+f"
type = "shell"
command = "herdr plugin action invoke open --plugin ismaelosuna.file-viewer"
[[keys.command]] # …or in its own tab
key = "prefix+shift+f"
type = "shell"
command = "herdr plugin action invoke open-tab --plugin ismaelosuna.file-viewer"Swap prefix+f / prefix+shift+f for whatever keys you like. Reload Herdr (or
your config) for the changes to take effect. Once open, press ? for the full
in-app keybinding reference.
The viewer is read-only, but it opens your editor on demand — it never bundles one:
e— open the current fileE— open the whole project (the workspace directory)
Terminal editors open right in the pane and hand it back on exit; GUI editors open their own window. After you save and close, the viewer reloads the file and git status.
List one or more editors in a config file — pick a default, or get a picker to choose each time. The file lives at:
$HERDR_PLUGIN_CONFIG_DIR/editors
On macOS that's usually
~/.config/herdr/plugins/config/ismaelosuna.file-viewer/editors. One editor per
line, name = command. A leading * marks the default; the file or project
path is appended automatically, so don't add a trailing .:
# A leading * = default (e/E open in it directly, no prompt).
# No default → you're asked which editor to use.
* zed = open -a Zed
code = code
nvim = nvim
nano = nanomacOS tip: use open -a <App Name> for GUI editors — it finds the app by
name and needs no CLI on your PATH (a bare zed/code only works if you've
installed their command-line tool). If an editor fails to launch, the viewer now
shows the error in the footer instead of failing silently.
Linux/other: use the command directly, e.g. code, zed, nvim, subl.
No config file? It falls back to a single editor from the first of
$FILE_VIEWER_EDITOR, $VISUAL, or $EDITOR.
On startup the viewer checks GitHub for a newer release and shows a notice in the
header (e.g. ⬆ v0.1.4 available) when one exists. To update, re-run the install
command — it pulls the latest and rebuilds/downloads the binary:
herdr plugin install ismaelosuna7824/herdr-file-viewergo build ./... # compile everything
go test ./... # run the engine + UI composition tests
go build -o bin/file-viewer ./cmd/file-viewer # what the build step runsYou can also run the binary directly outside Herdr against any directory:
./bin/file-viewer /path/to/projectWith no argument it uses the workspace directory from Herdr's context, falling back to the current working directory.
cmd/file-viewer/ entrypoint — the binary Herdr launches in the pane
internal/
explorer/ navigable directory-tree model
finder/ fuzzy file-path matcher (right-to-left, basename-biased)
search/ pure-Go content search engine + .gitignore matcher
gitstatus/ git working-tree status (shells out to `git`)
gitdiff/ parsed diff (file vs HEAD, or a whole commit) via `git`
gitlog/ commit history + branch/git operations
reveal/ open a path in the OS file manager (cross-platform)
viewer/ read-only file content pane with line numbers
ui/ Bubble Tea app that composes the above (Model-Update-View)
- The
.gitignoresupport is pragmatic, not spec-complete: directory names,*.extpatterns and anchored paths are honoured; negation (!) and nested.gitignorefiles are not. A built-in list (.git,node_modules,dist, …) is always skipped. - Content search caps at 5000 matches and skips binary and very large files to stay responsive; the status line flags a truncated result.
- Syntax highlighting runs asynchronously (files open instantly as plain text, colours arrive a beat later) so scrolling the tree never stutters.
Images are drawn two ways, and the difference is large.
Half blocks (the universal fallback) print ▀ with the foreground as the
upper pixel and the background as the lower one: one sample per column, two per
row. A 170×45 pane is therefore a 170×90 thumbnail, no matter how big the
source file is. It needs nothing but ANSI, so it works in every terminal and
through every multiplexer.
Kitty graphics sends the real pixels and lets the terminal draw them at its
native resolution — around 1360×765 in that same pane, roughly seventy times
the samples. The viewer asks the terminal at startup whether it understands the
protocol and picks the better path automatically; TERM is not consulted,
because a multiplexer in the middle makes it meaningless.
To check what your setup supports:
file-viewer --probe-graphics
Inside Herdr this also needs the host to pass the escapes through:
# ~/.config/herdr/config.toml
[experimental]
kitty_graphics = trueRestart Herdr after changing it.
- Bubble Tea — TUI framework
- Lip Gloss — styling
- Chroma — syntax highlighting
- goldmark — CommonMark/GFM parsing
- x/image — WebP, BMP and TIFF decoding
- ledongthuc/pdf — PDF text extraction
Markdown presentation is this project's own: goldmark supplies the AST and
internal/markdown walks it, emitting ANSI instead of HTML. That is what makes
tables, code frames, diagrams and inline images possible in one renderer.
Two deliberate constraints shape the rest:
- Mermaid is drawn, not rasterised. The reference pipeline is
DSL → mermaid.js → SVG → browser, which would mean shipping Node and headless
Chromium.
internal/mermaidparses the DSL and lays it out on a character grid instead, so the binary stays self-contained. - PDFs are read, not rendered. Rasterising a page needs pdfium or mupdf,
both CGO. The release pipeline cross-compiles four targets from one runner
with
CGO_ENABLED=0; a C dependency would trade a readable document for a broken build matrix.
Issues and PRs welcome. go test ./... and gofmt -l . must stay clean (CI
enforces both across Linux and macOS).
MIT © ismaelosuna7824
