A minimal terminal Markdown browser written in pure Swift. It lists every
Markdown file under the current directory, lets you pick one with the arrow keys,
renders it natively in the terminal as styled ANSI text — including
Mermaid diagrams drawn as ASCII/Unicode art — and lets you edit blocks
inline (press e) and save back to disk, all without leaving the terminal.
demo.mp4
brew install dsaad68/termdown/termdownUpgrade later with brew upgrade termdown.
Requires a Swift 6.2+ toolchain (see Requirements).
swift build -c release
cp .build/release/termdown /usr/local/bin/termdown # or: just installjust install builds the release binary and symlinks it into ~/.local/bin
(no sudo required). To run straight from a clone without installing:
swift run termdown # scan the current directory
swift run termdown ~/notes # scan a specific directory- Recursively finds
.md/.markdown/.mdx… files in the current folder (skips.git,node_modules,.build, etc.). - Fuzzy file finder with real-time filtering and match highlighting.
- Full terminal rendering powered by Apple's swift-markdown parser:
- Headings with colored underlines
- Bold, italic,
strikethrough,inline code - Bullet / ordered / nested lists and
- [ ]task lists - Fenced code blocks with syntax highlighting across ~35 languages (via Chroma), drawn as a framed card and mapped onto the matte palette
- Mermaid diagrams:
```mermaidblocks render as ASCII/Unicode art (flowcharts and sequence diagrams) via a native Swift port of mermaid-ascii — no external tools. Falls back to a highlighted code block for unsupported diagram types - GFM tables drawn with box-drawing borders and column alignment
- Block quotes (including nested)
- GitHub alerts:
> [!NOTE],> [!TIP],> [!WARNING], etc. as colored callouts - YAML frontmatter displayed as a metadata panel
- Thematic breaks (horizontal rules)
- Links as clickable OSC 8 hyperlinks
- Footnotes rendered in a dedicated section
- Math (
$…$/$$…$$) converted to Unicode: Greek, super/subscripts,\frac,\sqrt, accents and common operators
- Tabs: open multiple documents side by side.
Topens the file finder in a new tab,Shift-Enter(orO) opens the focused link in a new tab. Switch with1–9/}/{and close withx. Once a second tab is open the footer shows a tab strip (active tab highlighted). - Navigation:
topens a Contents / Open Tabs overlay: the document outline plus the list of open tabs; presstagain to switch between the two panes- Toggleable outline sidebar (press
s) that highlights the section you're currently reading as you scroll - Collapsible sections:
zfolds/unfolds the current section (or the one selected in the sidebar);Zcollapses to a top-level outline - Heading-to-heading navigation (
]/[) - In-document incremental search (
/to search live,n/Nfor next/previous)
- Copy to clipboard (works over SSH via OSC 52):
yyanks the code block nearest the cursor,Yyanks the focused link's URL - Doc browser / link navigation:
Tab/Shift-Tabcycle between links in the documentEnter/oopens the focused link: external URLs launch in your browser, while relative links to other Markdown files open in-app- Wikilinks:
[[Page]],[[Page|alias]]and[[Page#Heading]]resolve to a discovered Markdown file (matched by name) and open in-app;[[Page#Heading]]jumps to that section after loading Backspacewalks back through your navigation history
- Project-wide search (
\): a live "grep" across every discovered Markdown file, showingfile:linematches with a preview;Enterjumps straight into that file at the match - Runtime layout controls:
wtoggles line wrapping (chop long lines;←/→scroll horizontally)+/-adjust the text column width for comfortable readingFtoggles follow mode (tail -f-style auto-scroll to the bottom on reload)
- Cursor mode, selection & inline editing: press
vto toggle a line cursor (off by default —j/kscroll as usual until then). With it shown,j/kmove the highlighted line (its source line shows asL42), andShift+↑/↓(orShift+J/Shift+K) select multiple lines —ycopies the selection as raw markdown,Yas rendered text. Presseto edit the block under the cursor (paragraph, heading, list item, table row, …) as its raw markdown in place while the rest stays rendered.Entercommits the edit to the buffer and marks the document unsaved (●);Ctrl-Swrites it to disk, and quitting with unsaved changes prompts to Save / Discard / Cancel. - Live reload: automatically reloads when file changes
- Color themes (17): dark, light, mono; popular ports (catppuccin, rose-pine,
nord, tokyo-night, gruvbox, dracula); and custom true-color pastels across matte,
cold and warm families (matte-rose, matte-slate, frost, mint, dusk, blossom,
sand, coral). Press
pin the viewer for a live-preview theme selector that saves your pick. 24-bit color is used automatically when the terminal supports it (COLORTERM) - Configurable: supports
.termdown.yaml(project root or home dir) for default settings - Tests: comprehensive test coverage for core functionality
-
macOS 13+ (Swift 6.2+ / Xcode 16+), or
-
Linux with a Swift 6.2+ toolchain
(the Swift floor is set by the Chroma syntax-highlighting dependency; the package manifest itself still declares
swift-tools-version:5.9.)
| Context | Key | Action |
|---|---|---|
| File list | Type text | fuzzy filter files |
| File list | ↑/↓ or k/j |
move selection |
| File list | g / G |
jump to first / last |
| File list | Enter |
open the selected file |
| File list | \ |
project-wide search (live grep) |
| File list | q / Esc |
quit |
| Viewer (pager) | ↑/↓ or k/j |
scroll one line |
| Viewer (pager) | Space/PgDn, b/PgUp |
scroll one page |
| Viewer (pager) | d / u |
scroll half a page |
| Viewer (pager) | ←/→ or h/l |
scroll horizontally (no-wrap) |
| Viewer (pager) | g/Home, G/End |
top / bottom |
| Viewer (pager) | :+number+Enter |
jump to line N |
| Viewer (pager) | Ctrl-L |
force redraw |
| Viewer (pager) | / |
incremental search (live) |
| Viewer (pager) | n / N |
next / previous search match |
| Viewer (pager) | \ |
project-wide search (live grep) |
| Viewer (pager) | Tab / Shift-Tab |
cycle to next / previous link |
| Viewer (pager) | Enter / o |
open the focused link in place |
| Viewer (pager) | O / Shift-Enter |
open the focused link in a new tab |
| Viewer (pager) | T |
open a document in a new tab |
| Viewer (pager) | 1–9 |
jump to tab N |
| Viewer (pager) | } / { |
next / previous tab |
| Viewer (pager) | x |
close current tab |
| Viewer (pager) | y |
copy code block nearest cursor |
| Viewer (pager) | Y |
copy focused / nearest link URL |
| Viewer (pager) | Backspace |
navigate back (history) |
| Viewer (pager) | t |
Contents / Open Tabs overlay (t switches panes) |
| Viewer (pager) | s |
toggle outline sidebar |
| Viewer (pager) | s (sidebar open) |
focus sidebar (↑↓ move, Enter jump, z fold, Esc unfocus, q close) |
| Viewer (pager) | z |
fold / unfold current section |
| Viewer (pager) | Z |
fold all / unfold all sections |
| Viewer (pager) | ] / [ |
next / previous heading |
| Viewer (pager) | v |
show/hide the line cursor (cursor mode) |
| Viewer (pager) | Shift-↑/↓, J/K |
select lines (cursor mode); y copies as markdown, Y as rendered text |
| Viewer (pager) | e |
edit the block under the cursor (raw markdown); Enter commits to buffer, Esc cancels |
| Viewer (pager) | Ctrl-S |
save unsaved edits to the file |
| Viewer (pager) | w |
toggle line wrap |
| Viewer (pager) | + / - |
widen / narrow text column |
| Viewer (pager) | F |
toggle follow mode (tail) |
| Viewer (pager) | B |
toggle heading banners (h1–h4 as filled color blocks) |
| Viewer (pager) | p |
theme selector (live preview, Enter saves to config) |
| Viewer (pager) | q / Esc |
close sidebar, else extra tab, else back to the file list |
| Viewer (pager) | ? |
show help |
termdown [options] [directory]
termdown render <file.md>
termdown - # read from stdin
Options:
--width N Set terminal width (default: auto-detect)
--theme NAME Set color theme: dark, light, mono, catppuccin, rose-pine,
nord, tokyo-night, gruvbox, dracula, matte-rose, matte-slate,
frost, mint, dusk, blossom, sand, coral
--no-color Disable ANSI colors
--mouse Enable mouse scroll
--no-mouse Disable mouse scroll
--version, -V Show version information
--help, -h Show help messageOn first run termdown automatically creates a global config file at
~/.config/termdown/config.yaml with commented defaults:
# termdown configuration
theme: dark # see the full theme list below
# width: 80 # uncomment to fix column width
no-color: false
mouse: false # true to enable mouse scroll
# ignore-patterns: [vendor, "*.snap", archive] # extra paths to skip| Key | Type | Values | Effect |
|---|---|---|---|
theme |
string | see Themes below | Content color palette (unknown values fall back to dark) |
width |
int | e.g. 80 |
Fixes the text column width; omit for auto-detect |
no-color |
bool | true/false |
Disables all ANSI color |
mouse |
bool | true/false |
Mouse scroll in the finder and pager |
ignore-patterns |
list | [a, b, c] |
Extra path patterns to skip during file discovery (beyond the built-in .git/node_modules/.build skips) |
mermaid |
bool | true/false |
Render ```mermaid blocks as diagrams (default true; falls back to a code block on parse failure) |
mermaid-charset |
string | unicode/ascii |
Box-drawing character set for diagrams (default unicode) |
Themes: dark, light, mono; ports: catppuccin, rose-pine, nord,
tokyo-night, gruvbox, dracula; custom pastels: matte (matte-rose,
matte-slate), cold (frost, mint, dusk), warm (blossom, sand, coral).
Press p in the viewer to preview/switch live.
Custom viewer keys. Bind a key to a viewer action with key-<action>: <char>
(the action's default key keeps working; overrides add a key). This applies to
the pager only, not text-entry contexts (search / the fuzzy file list).
key-scroll-down: e # 'e' now scrolls down too (j still works)
key-theme: _ # open the theme selector with '_'Rebindable actions: scroll-down, scroll-up, page-down, page-up,
half-down, half-up, top, bottom, search, next-match, prev-match,
project-search, open-link, new-tab, theme, sidebar, wrap, follow,
banner, fold, fold-all, next-heading, prev-heading, edit, cursor, contents, help, quit.
Booleans accept true/yes/on/1 as true. The config reader is flat:
key: value lines only, so ignore-patterns must be inline ([...] or a
comma-separated list), not a multi-line - item block. Aliases: no-color =
nocolor = no_color; ignore-patterns = ignorepatterns = ignore_patterns.
To override settings for a specific project, create a .termdown.yaml in that
directory. It is merged on top of the global config: only the keys you set
take effect, everything else falls back to the global config:
# .termdown.yaml (project root)
mouse: true
width: 100Priority order (highest to lowest):
- CLI flags (
--mouse,--no-color, etc.) - Project-local
.termdown.yamlin the current directory - Global
~/.config/termdown/config.yaml
Useful for piping or quick previews:
swift run termdown render path/to/file.md
swift run termdown render README.md | less -R- Math (
$…$inline /$$…$$display) is converted to Unicode: Greek letters, super-/subscripts,\frac,\sqrt, accents and the common operators/relations. Commands without a Unicode form (and spacing macros markdown strips, like\,) degrade gracefully rather than disappearing. - Tabs:
Topens the file finder and loads the chosen document in a new tab;Shift-Enter(orO) opens the focused link in a new tab.1–9jump to a tab,}/{cycle,xcloses the current one, andqpeels back a layer at a time (sidebar → extra tab → file list). The tab strip is shown only when 2+ tabs are open. Each tab keeps its own scroll position, folds, sidebar state and search. Note: most terminals (Apple Terminal, default iTerm2) send the same bytes forEnterandShift-Enter, soShift-Enteronly opens a new tab on terminals that report it distinctly (kitty keyboard protocol / xtermmodifyOtherKeys); useOas the universal equivalent. - Copy to clipboard:
ycopies the code block nearest the cursor;Ycopies the focused (or nearest visible) link URL. Uses OSC 52 so it works over SSH, with apbcopyfallback on macOS. - Folding:
zcollapses/expands the section the cursor is in (or the selected heading in the outline sidebar);Zcollapses the whole document to a top-level outline, or expands it again. - Clickable links use the OSC 8 escape; terminals that don't support it simply show the underlined link text.
- Link navigation:
Tabcycles links;Enter/oopens the focused one. Relative links to other Markdown files are followed in-app (useBackspaceto go back); everything else is handed to the system (open) to launch in your browser. Link cycling requires colors (it relies on OSC 8 markers, so it is disabled under--no-color). - Outline sidebar (
s) and wrap/width/follow controls (w,+/-,F) only affect the interactive viewer, notrenderoutput. - Mouse is off by default. Enable it with
--mouseon the CLI ormouse: truein.termdown.yaml. It works in both the file list and the viewer (pager): the wheel scrolls, and a click follows the link under the cursor (pager) or selects a file, and clicking the highlighted file opens it (list). Mouse reporting uses SGR 1006 mode; terminals that don't support it will just ignore the escape sequences. - Live reload monitors the file modification time and reloads when changed.
See CONTRIBUTING.md for the full workflow. In short:
just test # swift test
just check # format-check + lint (strict) + test; run before a PR
just format # apply SwiftFormat; just lint runs SwiftLint --strictThe project is structured as a Swift Package with a library target (termdownCore),
an executable target (termdown), and two test targets: termdownCoreTests (the
library) and termdownTests (the executable's UI logic, via @testable import).
Source files are kept small and single-purpose (≤300 lines); larger types are split
across Type+Concern.swift extensions. Linting/formatting is configured in
.swiftlint.yml and .swiftformat, and CI runs the same checks on macOS and Linux.
SnapshotTests renders the fixtures under Tests/Fixtures/*.md to ANSI and compares
them against committed .ansi golden files, so any change in rendered output is
caught. After an intentional rendering change, regenerate the goldens:
TD_UPDATE_SNAPSHOTS=1 swift testReview the resulting diff before committing.
- Mermaid rendering is inspired by and ported from
mermaid-ascii by Alexander
Grooff (MIT) — the terminal-diagram approach and rendering are its work, reimplemented
natively in Swift. The ported engine lives in
Sources/MermaidRenderer(see itsNOTICE); the diagram fidelity fixtures underTests/MermaidRendererTests/testdataare copied verbatim from that project.