Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Marsdown

A native markdown reader. Deliberately not an editor. Free for noncommercial use — see Licence.

It exists so that several .md files can be read side by side without opening a browser or an editor: it starts instantly, keeps each file in a tab, and builds a navigable outline from the headings so long documents stay manageable. A ```mermaid block draws as a diagram rather than showing its source; one that fails to render falls back to that source.

Installing

With Homebrew, on macOS — no Rust toolchain needed:

brew tap gaborini/marsdown
brew trust --formula gaborini/marsdown/marsdown
brew install marsdown

All three commands are needed: Homebrew 6 ignores a third-party tap until it is trusted, so brew install on its own fails. Trusting the one formula rather than the whole tap is the narrower grant, and it does not extend to anything added here later.

That installs Marsdown.app and puts a marsdown command on the PATH. The binary inside is universal: Apple Silicon and Intel. There is no Gatekeeper prompt to click through — a formula install is not quarantined, so it opens on the first try.

The bundle lives in Homebrew's Cellar rather than /Applications, so it has a proper dock icon and answers to open -a Marsdown, but it will not appear in Launchpad or Spotlight. That is the trade for not signing the app with a paid Apple Developer ID.

With a Rust toolchain, from crates.io:

cargo install marsdown-reader     # installs a command called `marsdown`

The crate is marsdown-reader because plain marsdown on crates.io belongs to an unrelated markdown-to-HTML parser. The command it installs is marsdown.

Or from a checkout:

cargo run -- README.md
cargo build --release

Both cargo routes need a stable Rust toolchain, and neither builds an application bundle — see the platform note below about what that costs on macOS.

Tested on macOS only. There is no platform-specific code and every dependency is cross-platform, so Linux and Windows are expected to work — but expected is not the same as tried, and nobody has tried. Two known platform differences: macOS takes an application's icon from its bundle rather than from the window icon Marsdown sets at runtime, so the Homebrew install — which ships a bundle — has an icon while a cargo-built binary does not; and the file-open dialog goes through the XDG desktop portal on Linux, so a xdg-desktop-portal service has to be running there.

Using it

marsdown a.md b.md
Key Does
Ctrl/Cmd+O Open files — several at once
Ctrl/Cmd+W Close the current tab
Ctrl/Cmd+B Show or hide the outline
Ctrl/Cmd+F Find in the document
Enter, F3 / Shift+F3 Next / previous match
Escape Close the find bar
Ctrl+Tab, Ctrl+Shift+Tab Next / previous tab
Ctrl/Cmd+19 Jump to a tab
Ctrl/Cmd+= / - / 0 Larger / smaller / default text
Scroll a few lines
PageUp, PageDown, Space, Shift+Space Scroll a screenful
Home, End Start / end of the document

Clicking an outline entry scrolls to that heading. Middle-clicking a tab closes it. Scroll position is remembered per tab. http(s) links open in the system browser. Files dropped on the window are opened, and an open file that changes on disk is reloaded on its own — the point being to read a document beside the editor writing it.

Reading face

The status bar has a typeface picker: the system default plus six faces, serif first, each labelled with which it is because "Charter" and "Optima" do not say.

A word on how they were chosen, because "ergonomic" is a claim worth being careful with. There is very little rigorous evidence that any particular typeface measurably improves reading speed or comprehension for normally-sighted readers, and most assertions otherwise are marketing. What can honestly be said is which faces were designed for extended reading on screen, and what features serve that: a large x-height, open apertures, strokes that survive small sizes, and letterforms you cannot confuse.

Face Why it is here
Charter Matthew Carter, 1987, for low-resolution output. Narrow and sturdy.
Georgia Carter, 1993, drawn for the screen: large x-height, serifs that do not thin.
Palatino Zapf's humanist book face. Open counters, calm over long runs.
Verdana Carter, 1996, for legibility at small sizes. Keeps I, l and 1 apart.
Avenir Next Geometric humanist sans: even rhythm, clear apertures.
Optima Zapf's humanist sans, modulated strokes for continuous text.

These are named system fonts, not bundled files, so nothing is added to the binary and there is no font licence to honour. The cost is platform-dependence: a name the system does not have falls back. That was tested rather than assumed — a deliberately bogus name renders in a legible fallback face, not blank and not in tofu boxes.

Inline code stays in the reading face, at the reading weight and the paragraph's size. A monospaced word dropped into a serif paragraph reads as a foreign object — different letterforms, different rhythm, and a different apparent size even at the same point size. Like this is set apart by a tinted background alone, plus a deeper text colour where the palette leaves room for one.

That proviso is real rather than decorative. In the plain light theme the body text is already #000000, so code cannot be darker than it and the background does all the work. Solarized Light has room (#004658 against #3a606d prose) and so does Catppuccin Latte (#303256 against #4c4f69).

Code blocks do stay monospaced: there the column alignment is the point, and the syntax highlighting is built for it.

Colours

The status bar has a palette picker. It offers all 22 palettes iced ships — Solarized, Gruvbox, Catppuccin, Tokyo Night, Kanagawa, Nord, Dracula, Moonfly, Nightfly, Oxocarbon, Ferra and the plain light and dark — plus "Follow the system", which is the default.

Light palettes are listed before dark ones and each is marked with a sun or a moon, because light or dark is the first thing you decide and names like Ferra and Oxocarbon give no clue which they are.

ICED_THEME sets what "follow the system" resolves to, so ICED_THEME='Solarized Dark' marsdown README.md starts there. An explicit choice in the picker always wins over it.

Code blocks stay dark in every palette. That is not an oversight: the markdown widget bakes in a dark syntax-highlighting theme at parse time and does not let it be swapped, so a light code block would leave the code illegible. Inline code does follow the palette.

Inline code sits on the palette's weaker background step, which puts it at a contrast ratio of 1.23–1.30 against the page. The strong step it used before was at 1.58–1.83, which punched holes through paragraphs in a document where every other phrase is like this. The code text itself stays above 7.7:1 against its own fill in every palette, well past the 4.5:1 that WCAG AA asks for.

None of the three pickers is remembered between runs yet.

What it does not do

No editing, no saving, no export, no LaTeX, no plugins, one window. No recent-files list and no session restore. A mermaid diagram is drawn, not typeset: its text cannot be selected or searched.

Two limits in search are worth knowing, and both come from markdown::Row keeping its cells private with no accessor. Matches inside a table body are highlighted — the default renderer draws them through our viewer — but they are not counted and cannot be stepped to. Matches in image alt text are neither highlighted nor counted. Search navigation also lands on the block containing a match rather than the exact line, because the widget markdown text renders to reports no per-span position to any operation.

How it is put together

CommonMark and GFM come from iced's built-in markdown widget, so tables, strikethrough, task lists and syntax-highlighted code blocks come for free.

The interesting part is jumping to a heading, because iced 0.14 gives you no way to ask where a widget is. container is the only widget that both takes an Id and reports its bounds to an operation, and the text::Rich that markdown renders to reports nothing at all — so a custom markdown::Viewer wraps every heading in an identified container, and selector::find_all measures those anchors and the scrollable in a single pass.

Search highlights at span level, including matches that straddle a **bold** boundary or a wrapped line. Matches are identified by the address of the text run they live in rather than by a counter, because the default table renderer calls back into our viewer lazily for row cells — so anything counted during rendering drifts. src/search.rs explains it at length.

src/app.rs            state, messages, update and view
src/render.rs         the anchoring markdown viewer
src/measure.rs        widget measurement and scroll arithmetic
src/outline.rs        headings from the parsed document
src/search.rs         match indexing and span-level highlighting
src/stats.rs          words, characters, reading time
src/appearance.rs     text size and colour scheme
src/watch.rs          noticing on-disk changes
src/document.rs       a parsed document and what is derived from it
src/tab.rs            one open file
src/icon.rs           the window icon and bundle icon, drawn in code
src/ui/               tab bar, find bar, sidebar, status bar
examples/iconset.rs   renders src/icon.rs to the PNGs an .icns is built from

Licence

PolyForm Noncommercial 1.0.0. Copyright 2026 Gabor Lepsenyi.

Read it, learn from it, run it, change it, hand it to a friend — all free, for any noncommercial purpose. Charities, schools, public research bodies and government institutions count as noncommercial regardless of how they are funded.

Making money with it does not. If you want to use Marsdown commercially, ask for a licence.

This is deliberately not an open-source licence: OSI-approved terms all permit commercial use, and that is the one thing being held back. Practical consequences worth knowing:

  • crates.io accepts it. It does not require OSI approval, and hosts crates under this identifier today. cargo install marsdown-reader works today.
  • Marsdown is a binary, not a library, so nobody can take it as a dependency and end up bound by these terms without noticing. Installing it is a deliberate act.
  • Distributions have to carry LICENSE.md, including the Required Notice: line in it. That is the mechanism the licence uses to make the copyright notice travel with the code.
  • Corporate licence scanners flag noncommercial terms, and they are right to. If you are behind one, that flag is the licence working as intended — ask instead.
  • 614 dependencies, and all but four are permissive — MIT, Apache-2.0, BSD, Zlib, ISC, CC0 or a choice including one of those — so none of those constrains this choice or is violated by it. iced, pulldown-cmark and notify are MIT; rfd is CC0-1.0.
  • The other four — cssparser, cssparser-macros, selectors and dtoa-short, reached through lol_html, which merman-core requires — are MPL-2.0. That is file-level copyleft: it does not change Marsdown's own licensing, and the obligation it carries is a notice, which THIRD-PARTY-NOTICES.md satisfies.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages