Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ND Writer

A writing app in the spirit of iA Writer: one column of text, a warm page, and nothing else competing for attention. Underneath it there is a real spreadsheet engine, a typesetter that produces proper PDFs, and a document format that a browser can open.

Written in Rust. Built for Omarchy (Wayland/Hyprland), works on any Linux desktop.


What it does

Writing

  • Plain markdown, syntax-highlighted in place — the markers stay where they are, so nothing shifts under the cursor.
  • Focus mode dims everything but the current sentence, paragraph or line.
  • Typewriter scrolling keeps the caret at the middle of the window.
  • The text column is set by measure (characters per line), not pixels.
  • Live counts: words, characters, reading time, words written this session, and a progress ring against a word goal.
  • Outline, library and recents in a sidebar; a rendered preview beside the text.
  • Find and replace, full screen, light/dark/system themes.

Tables that calculate

Tables are objects in the document, placed in the prose with a {{table:ID}} marker and edited in a spreadsheet pane. They carry formulas, number formats and cell styling, and they render in the preview, the PDF, the HTML and the markdown export.

Typography, templates and page setup

  • Any font installed on the machine, chosen per document for body, headings and monospace. Size, line height, measure, paragraph spacing, letter spacing, indented or spaced paragraphs, justification.
  • Thirteen templates, from Blank to Invoice, Budget and Research Paper.
  • Page sizes A3–A5, B5, Letter, Legal, Tabloid or custom in millimetres; portrait or landscape; margins and page numbers.

Files

  • .nd — the native format. A ZIP holding the document, its tables, its full edit history and a rendered copy of itself.
  • Export to PDF, a standalone web page, or plain markdown.
  • Opens markdown files directly, front matter included.
  • Edit history on every save, browsable and restorable.
  • Sync: mirror a document to a folder another tool keeps in step, and incoming changes are three-way merged rather than overwriting your work.

Install

./install.sh              # builds release, installs into ~/.local, no root
./install.sh --uninstall

This registers the desktop entry, the icon and the application/vnd.nd-writer+zip MIME type, so .nd files open with a double-click.

For a system package on Arch/Omarchy, packaging/PKGBUILD is ready to use.

Dependencies. Rust (sudo pacman -S rust), and at build time the usual Wayland/X11 client libraries, all of which Omarchy already has. The defaults are tuned for the iA Writer typefaces — sudo pacman -S ttf-ia-writer, or an AUR build of them — but the app falls back to whatever is installed and every font is switchable.

Optional Hyprland window rules are in packaging/hyprland.conf.


Using it

Keys

Ctrl+N / Ctrl+Shift+N New / new from template
Ctrl+O Ctrl+S Ctrl+Shift+S Open, save, save as
Ctrl+P Export PDF
Ctrl+Shift+E Export web page
Ctrl+T Insert table
Ctrl+B Ctrl+I Ctrl+E Ctrl+K Bold, italic, code, link
Ctrl+F / Ctrl+H Find / find and replace
Ctrl+D Cycle focus mode
Ctrl+Shift+T Typewriter scrolling
Ctrl+R / Ctrl+L Preview / sidebar
Ctrl+Shift+H Edit history
Ctrl+, Preferences
Ctrl+ + / − / 0 Zoom
F11 Full screen
Esc Close whatever is open

In the table pane: arrows move, Shift+arrows extend the selection, Enter or F2 edits, Tab moves right, Delete clears, Ctrl+D fills down (rewriting relative references), Σ sums the run of cells above.

Command line

nd-writer notes.nd                  # open it
nd-writer report.nd --to-pdf out.pdf
nd-writer report.nd --to-html out.html
nd-writer notes.md   --to-nd notes.nd
nd-writer --selftest                # build every template and export it

Formulas

A cell whose text starts with = is a formula. References are A1-style (B7, $B$7), ranges are A1:C9. Operators: + - * / ^ %, & to join text, and = <> < <= > >=. Errors are the familiar ones — #DIV/0!, #VALUE!, #REF!, #NAME?, #N/A, #NUM! — plus #CYCLE! when references chase their own tail, and #PARSE! when a formula is malformed.

=SUM(B2:B5)
=(B6-C6)/B6
=IF(C7>B7, "Over", "Within")
=INDEX(B1:B9, MATCH("cherry", A1:A9, 0))
=VLOOKUP(A2, A1:C20, 3)         # VLOOKUP(lookup, range, column, [approximate])
=COUNTIF(E2:E9, ">10")
=ROUND(D6 * 0.1, 2)
=TEXT(A1, "0.00") & " kg"

Available: ABS AND AVERAGE AVERAGEIF CEILING CHOOSE CONCAT CONCATENATE COUNT COUNTA COUNTBLANK COUNTIF DATE DAY EXP FLOOR HLOOKUP IF IFERROR IFS INDEX INT ISBLANK ISERROR ISNUMBER ISTEXT LEFT LEN LN LOG LOG10 LOWER MATCH MAX MEDIAN MID MIN MOD MONTH NOT NOW OR PMT POWER PRODUCT PROPER RAND REPLACE REPT RIGHT ROUND ROUNDDOWN ROUNDUP SEARCH SIGN SQRT STDEV SUBSTITUTE SUM SUMIF SUMPRODUCT TEXT TODAY TRIM TRUNC UPPER VAR VLOOKUP XOR YEAR.

Notes on behaviour, since these differ between spreadsheets:

  • MATCH with type 0 is exact and accepts * and ? wildcards; 1 and -1 scan an ordered range for the nearest bound.
  • VLOOKUP/HLOOKUP default to exact match — the safe default, unlike Excel's legacy approximate one. Pass TRUE as the fourth argument for the old behaviour.
  • MOD takes the sign of its divisor, and ROUND rounds halves away from zero, both as in Excel.
  • Text and blanks inside a range are skipped by numeric aggregates.
  • Dates are serial numbers counted from 1899-12-30, so they interoperate with Excel and Sheets.
  • Inserting or deleting a row or column rewrites every formula so it keeps pointing at the same data; $ pins a reference against that.

Number formats: automatic, fixed decimals, thousands separators, currency, percent, scientific, dates (yyyy-mm-dd, d mmm yyyy, and so on), or forced text. Cells can be bold, italic, struck through, aligned, coloured and filled.


The .nd format

A .nd file is a ZIP, laid out the way ODF and EPUB are, so it is inspectable with tools you already have:

mimetype        application/vnd.nd-writer+zip   (stored first, uncompressed)
nd.json         format version, document id, SHA-256 of document.json
document.json   metadata, markdown body, tables
history.jsonl   one revision per line
content.md      plain markdown mirror — greppable, diffable
preview.html    a standalone rendering of the document
unzip -l notes.nd
unzip -p notes.nd content.md | less

Because content.md and preview.html are always current, a .nd file stays useful even without this app. Writes are atomic — a temp file and a rename — so a sync daemon watching the folder never sees a half-written document.

Opening one in a browser

web/viewer.html (installed to ~/.local/share/nd-writer/viewer.html) opens .nd files client-side: drop one in, or pass ?file=… to load one served alongside it. It unpacks the ZIP with the browser's own inflate, shows the rendered document, the metadata, and the full revision timeline — and replays any past revision from the stored patches. Nothing is uploaded and there are no dependencies; it is one HTML file.

Edit history

Every save records a revision: who, when, from which device, a one-line summary, and either a line patch against its parent or a full snapshot. Snapshots are interleaved every 25 revisions so replaying any point stays cheap. Restoring an old revision is itself an edit, so going back never loses what you went back from. Ctrl+Shift+H opens the timeline.


Sync

Turn on sync and point it at a folder that something else keeps in step — Syncthing, Nextcloud, Dropbox, an NFS mount. The document is mirrored there as <document-id>.nd, pushed after each save and pulled when the mirror changes.

The open file is watched too, so a change made by another machine, another window, or a sync daemon is noticed within about half a second.

Incoming changes are three-way merged against the last state both sides agreed on. Regions only one side touched are taken as they are; regions both sides changed differently are kept with <<<<<<< local / >>>>>>> remote markers rather than silently resolved. Tables merge per table: a table the local side has not touched takes the remote version. Revisions from the other side are folded into the local history, so both ends end up with the whole story.

A document server is also supported. The protocol is fixed:

GET  {endpoint}/doc/{id}/head   -> {"checksum","modified","revisions"}
GET  {endpoint}/doc/{id}        -> the .nd bytes
PUT  {endpoint}/doc/{id}        -> body is the .nd, If-Match: {base checksum}

The client, the merge path and the settings for it are all in place; the HTTP transport itself is not enabled in this build, and says so plainly instead of appearing to work. The folder mirror is the working path today.


Building from source

cargo build --release
cargo test            # formula engine, history replay, merge, .nd round-trip
./target/release/nd-writer --selftest

--selftest builds all thirteen templates, exports each to .nd, PDF, HTML and markdown, reopens the .nd, checks every formula still evaluates without error, and replays a forty-revision history to confirm each revision reproduces exactly.

Layout

crates/nd-core          no UI dependencies at all
  formula/              lexer, parser, evaluator, function library
  table.rs              sparse grid, recalculation, number formats
  doc.rs                document model, page setup, typography
  ndfile.rs             the .nd container
  history.rs            revisions and patches
  sync.rs               backends and the three-way merge
  export/               markdown, HTML, and the PDF typesetter
crates/nd-app           the editor
  editor/highlight.rs   the markdown scanner that colours the text
  ui/                   panels: tables, history, preferences, sync, preview
web/viewer.html         the browser-side .nd reader
packaging/              desktop entry, MIME type, icon, PKGBUILD, Hyprland rules

The PDF exporter is its own small typesetter: it measures text with ttf-parser against the very font files it embeds, so the line breaks in the output match the metrics of the font the reader sees. It handles headings, emphasis, code, quotes, lists, rules, page breaks, justification, hanging indents, page numbers and tables with their formatting.

Licence

MIT.

About

A distraction-free Markdown writing app in Rust, with calculating tables and real PDF typesetting. Built for Omarchy, runs on any Linux desktop.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages