mdview and the missing middle between less and Electron - Leon Becker's Blog
Skip to content
Leon Becker's Blog<br>Notes on manageable but capable software.
Toggle Menu
I wrote mdview because I wanted a fast Markdown viewer for the Wayland desktop. Just a quick way to open a README or a design note without breaking flow.
Developers spend a lot of time around Markdown files: READMEs, runbooks, changelogs, design notes, random NOTES.md files in project directories. Most of the time I am already in a terminal when I want to read one.
The usual options all work, but none of them feel quite right.
less and vim are fast, but they show raw text. That works, but I usually need longer to understand what I am looking at. Markdown is often written to be read as structured text. Headings, tables, links, anchors, images, and highlighted code blocks make it easier to work with.
Converting through pandoc or opening a browser works, but it is too much ceremony for a glance.
Electron-based viewers are comfortable, but they are too heavy for taking a quick look, especially when you do not have one open already. I do not want to launch a whole application stack just to read a README next to my terminal.
So mdview tries to sit in the missing middle: fast enough to feel like a small tool, but capable enough to actually read Markdown.
The project is available on Codeberg: codeberg.org/beleon/mdview.
Why I tried native Wayland
For this project, I wanted to see how far I could get without a general GUI toolkit or browser engine.
GTK would have been a perfectly reasonable choice. A toolkit would have solved many details I had to handle myself: input, clipboard, window decorations, scaling, cursors, gestures, and a lot of small desktop integration work.
I did not avoid GTK because I think it is bad. I avoided it because this project was small enough that the direct approach still felt manageable.
That is the important distinction for me. A larger application should probably make different choices. A small Markdown viewer with a narrow scope can sometimes afford to be more direct.
mdview talks to Wayland directly. It uses xdg-shell, shared-memory buffers, input handling, clipboard support, cursor shape, pointer gestures, window decorations, and toplevel icons.
mdview also does not include an XWayland fallback. It targets the environment I use it in directly, and adding another compatibility path would have made the project larger than I wanted it to be.
That is the trade-off: more code in some places, less machinery overall.
For this scope, that felt right.
The rendering pipeline
The architecture is simple enough to explain in one diagram:
.md file<br>md4c<br>Markdown → HTML<br>litehtml<br>HTML/CSS → layout<br>Cairo + Pango<br>drawing + text shaping<br>wl_shm buffer<br>Wayland window
Markdown parsing is handled by md4c. The HTML/CSS layout step is handled by litehtml. I do not need a browser engine, JavaScript, dynamic DOM behavior, or a network stack. I need enough layout to render Markdown as structured text.
Cairo and Pango handle drawing and text. The final result is drawn into a shared-memory buffer and presented through Wayland.
I used C++17 mostly because it fit this stack. md4c is C, litehtml is C++, and Cairo, Pango, and the Wayland protocol bindings are C. Rust would have been nicer for ownership and lifetime management, but for this set of libraries C++ was the path of least resistance.
That is the whole shape of the application. Markdown in, pixels out.
It is not the most flexible architecture. It is not trying to be. But it is understandable, and that matters.
What the measurements showed
I wanted mdview to feel like something you can use while working in the terminal.
That does not mean it has to be tiny in the cat or less sense. It is doing real Markdown rendering, layout, text shaping, image loading, syntax highlighting, text selection, and UI interaction. But it should still feel like a quick viewer, not like launching a whole application.
The stripped binary is 2.6 MB. That does not make the whole runtime tiny, because the rendering stack and desktop libraries still matter, but it does say something about the shape of the tool itself.
These numbers are not meant as a universal benchmark. They are just the measurements that helped me understand where mdview spends its time and memory on my machine.
test.md (792 B, 125 spans) 267 ms RSS 34 MB<br>Nextcloud README (6.1 KB, 697 spans) 344 ms RSS 47 MB<br>mdview README (8.7 KB, 1420 spans) 426 ms RSS 67 MB
As a reference point, Typora from the .deb package takes about 2.5 seconds to open on the same machine. Typora is a full Markdown editor, not a quick-look viewer, so this is not a competition. It is just a useful number for the kind of startup delay I wanted to avoid when I only want to read a file.
The rough shape was consistent across those files:
file read :
File I/O and Markdown parsing are not the interesting costs. They are basically below the noise floor...