# mw — a Markdown-source writing tool

A small, fast, terminal-first Markdown writing tool in Rust. **Plain UTF-8 text is
canonical**; the parse tree is a derived view, and layout is delegated to
pandoc → XeLaTeX rather than reimplemented. See
[`markdown-writing-tool-plan.md`](markdown-writing-tool-plan.md) for the full design
(and [`wp-rust-editor-plan.md`](wp-rust-editor-plan.md) for the deliberately *opposite*
WordPerfect-parity design, which is **not** what this builds).

Website: <https://mw.itys.net>

## Status

**Phases 0–8 — done. The plan is complete.**

- **0:** soft-wrapping rope buffer — type, move, select, undo/redo (typing coalesces
  into one undo unit; selection-replace is atomic), save/load with newline preservation.
- **1:** tree-sitter Markdown highlighting (headings, emphasis/strong, inline + fenced
  code, links, list markers, quotes, rules), an **outline sidebar** (F3), **heading
  navigation** (Ctrl-Up/Down), and **folding** of heading sections + code blocks
  (F4 toggle, F5 fold/unfold all).
- **2:** **instant live preview** (F6) — `pulldown-cmark` → HTML served on a loopback
  port and opened in your browser; it updates *as you type* (serves the in-memory
  buffer, not the file) and swaps content in without a full reload, preserving scroll.
- **3:** **export** — `mw build <file>` produces a PDF via pandoc → XeLaTeX (or runs
  `make` if the project has a Makefile); also HTML/DOCX/EPUB. In-editor build with **F7**
  (runs in the background; on failure, surfaces the parsed pandoc/XeLaTeX error and
  jumps toward the offending line).
- **4:** **multi-file projects** — an `mw.toml` manifest (compile order, title/author,
  template, bibliography, output) or convention (sorted `*.md`). Build assembles the
  whole book in order; project-wide **word count** and **search** (`mw wordcount`,
  `mw search`); the editor auto-detects the project and shows it in the status bar.
- **5:** **citations** — parses `.bib` / CSL-JSON; an **`@key` completion popup** while
  typing; **undefined-citation** validation (flagged red in the buffer, counted in the
  status bar, and via `mw check`); pandoc `--citeproc` resolves them at build.
- **6:** **writing quality** — ASCII-discipline lint (smart punctuation + non-ASCII,
  flagged yellow; `Ctrl-L` converts smart punctuation to ASCII); semantic line breaks
  (one sentence per line) via `mw fmt --sembr`; `mw lint`; **in-editor search** (`Ctrl-F`);
  snippets (`Ctrl-E`: `fm` frontmatter, `today`, `tbl`).
- **7:** **math & focus** — `$…$` / `$$…$$` render via **KaTeX** in the live preview
  (and XeLaTeX at build); **focus / typewriter mode** (`F8`) dims everything outside the
  current paragraph and keeps the caret line centered.
- **8:** **cross-references & LSP** — pandoc-crossref `@sec:`/`@fig:`/`@eq:` completion,
  undefined-cross-ref validation, and go-to-definition (`Ctrl-]` jumps `@sec:x` → its
  `{#sec:x}`); plus **`mw-lsp`**, a Markdown language server (diagnostics, document
  symbols/outline, `@key` completion, go-to-definition) so the same intelligence works
  in VS Code / Neovim / Helix — the TUI becomes one client among several.

Possible extras (beyond the plan): incremental tree-sitter re-parse, source-mapped
build-error jumpback (synctex), a GUI front-end.

## Build & run

```sh
cargo build --release
./target/release/mw path/to/file.md                 # or: cargo run -p mw-tui -- file.md
./target/release/mw --theme matrix path/to/file.md  # amber (default) | matrix | wp
```

### Keys

| Key | Action |
|---|---|
| arrows / Home / End / PgUp / PgDn | move (hold **Shift** to select) |
| **Ctrl-↑ / Ctrl-↓** | jump to previous / next heading |
| **Alt-↑ / Alt-↓** (or Ctrl-Shift-↑ / ↓) | move the current line up / down |
| Ctrl-A | select all |
| Ctrl-S | save (prompts for a path if the buffer is unnamed) · Ctrl-Z undo · Ctrl-Y/Ctrl-R redo |
| **Ctrl-K / Ctrl-U** | cut the current line or selection (consecutive cuts accumulate) / paste it back (nano-style) |
| **Ctrl-D** | delete the character under the cursor |
| **F2** | cycle color theme (amber → matrix → wp) |
| **F3** | toggle outline sidebar |
| **F4 / F5** | fold at cursor / fold-all toggle |
| **F6** | open / focus live browser preview |
| **F7** | build PDF (background; jumps to errors) |
| **F8** | focus / typewriter mode |
| typing `@` | citation / cross-ref completion (↑/↓ · Tab/Enter · Esc) |
| **Ctrl-]** | go to cross-reference definition (`@sec:x` → `{#sec:x}`) |
| **Ctrl-F** | incremental search (type · Enter/↓ next · ↑ prev · Esc done) |
| **Ctrl-E** | expand snippet (`fm`/`today`/`tbl`) · **Ctrl-L** ASCII-fix smart punctuation |
| Ctrl-Q | quit (prompts once if unsaved) |

### Headless commands

```sh
mw build <file|dir>        # PDF; whole book if an mw.toml project is found
mw pdf|html|docx|epub <p>  # export to a specific format
mw wordcount <file|dir>    # per-file + project total (or single-file counts)
mw search <pattern> [dir] [-i]   # project-wide substring search
mw check <file|dir>        # report undefined citation keys
mw lint  <file|dir>        # ASCII + semantic-line-break warnings
mw fmt   <file> [--sembr]  # convert smart punctuation -> ASCII (+ one sentence/line)

mw template [file]         # print a full-feature scaffold (or write it to <file>)

mw keygen                  # create a signing key pair (prints the public key, base64)
mw sign   <file>           # write a detached <file>.minisig
mw verify <file> [-P <b64>]# verify <file> against its .minisig (key file or inline key)
```

### Signing (minisign-compatible)

`mw` ships a small signing sidecar for documents and build artifacts. Signatures are
**Ed25519** in the **minisign** format — the same scheme as Frank Denis's `minisign` and
OpenBSD's `signify` — so `.minisig` files made by `mw` verify with the stock `minisign`
CLI and vice-versa. This is deliberately **not** ML-DSA / Dilithium (FIPS 204): the
["breakable keys"](https://cr.yp.to/papers/mldsa-20260601.pdf) weakness is a property of
ML-DSA's lattice keys and does not apply to Ed25519 (nor to hash-based SLH-DSA / SPHINCS+).
Ed25519 is classical, not post-quantum; if you also want PQ coverage later, a hash-based or
hybrid backend is the conservative add-on.

```sh
mw keygen                          # -> ~/.config/mw/minisign.{key,pub}; prints pubkey
mw sign report.pdf                 # -> report.pdf.minisig
mw verify report.pdf               # uses ~/.config/mw/minisign.pub
mw verify report.pdf -P RWQ…       # or an inline base64 public key
minisign -Vm report.pdf -P RWQ…    # stock minisign verifies it too

mw build book/ --sign              # build, then sign the artifact (book.pdf.minisig)
MW_SIGN_AUTO=1 mw pdf doc.md       # auto-sign every headless build
```

`--sign` (or `MW_SIGN_AUTO=1`) signs the build output after a successful `mw
build|pdf|html|docx|epub`; a signing failure is fatal. Auto-sign applies to headless
builds only (the in-editor **F7** build is unaffected).

Keys live in `$MW_SIGN_DIR` (default `~/.config/mw`); the secret key is written `0600` and
encrypted with a password. The password is read from `$MW_SIGN_PASSWORD` when set
(non-interactive / CI), otherwise prompted. Flags: `-s/--secret`, `-p/--public`,
`-P/--pubkey <base64>`, `--sig <path>`, `-c/--comment <text>`, `-f/--force`.

### New-document template

Opening a **new** file (`mw notes.md` where `notes.md` doesn't exist) seeds the buffer
with a small starter scaffold covering the most commonly used constructs (a YAML header,
headings, styled text, a link, bullet/numbered lists, a block quote, a fenced code block,
and a table). Three *discoverable* attributes are auto-filled:

- **Date** — today (`date:` in the header),
- **Name** — author from `$MW_AUTHOR`, else `git config user.name`, else `$USER`,
- **Filename** — the file's name (`filename:`), and the title is derived from it.

`mw template` prints the same scaffold to stdout; `mw template <file>` writes it (it won't
clobber an existing file without `-f/--force`). The scaffold builds to a PDF as-is.

### Persisted undo history

`mw` keeps your document **plain UTF-8 and canonical** — but it also persists the undo
stack *beside* the file so **`Ctrl-Z` survives closing and reopening**. Saving `report.md`
writes a sidecar `report.md.mwh`: a **line-oriented, human-readable** journal — a one-line
header anchoring the base text by content hash, then one line per undo unit
(`E <pos> <cursor_before> <cursor_after>` + the removed / inserted text):

```text
mwh1 cbf29ce484222325 0
E 0 0 19		The quick brown fox
E 19 19 20		\n
E 20 20 30		jumps over
```

- **Raw-readable** — the `.md` is untouched plain text; every other tool (git, pandoc,
  another editor) sees a normal Markdown file. The history is a separate, greppable text file.
- **Incremental** — the common "kept typing" save just *appends* the new edits; a rewrite
  (recomputing the base) happens only after an undo-driven divergence, so large documents
  never get rewritten just to record history.
- **Capped** — the journal keeps the last ~10 000 edits; older history folds into an
  advanced base (compaction), so the sidecar stays bounded.
- **Safe** — if the `.md` was edited by another tool, the journal no longer reconstructs its
  recorded base (hash mismatch) and is **discarded** rather than replayed at wrong offsets.

The status bar shows your position in the timeline as `↑{undo} ↓{redo}` — steps you can
**Ctrl-Z** back through and **Ctrl-Y/Ctrl-R** forward through — and it's populated straight
away on a reopened file, since the history was restored from the sidecar.

Set `MW_HISTORY=0` to turn the sidecar off. The `.mwh` files are derived artifacts — add
`*.mwh` to your document repo's `.gitignore` if you don't want them tracked.

### Projects

A project is a directory with an `mw.toml`, or just a folder of `*.md` (sorted):

```toml
[project]
title = "A Small Book"
author = "MJ"
files = ["intro.md", "ch1.md", "ch2.md"]   # compile order; omit to use sorted *.md
template = "template.tex"                   # optional (pandoc --template)
bibliography = "refs.bib"                   # optional (pandoc --citeproc)
output = "book.pdf"                          # optional default output
```

`mw` walks up from the file you open to find `mw.toml`; build/wordcount/search then
operate on the whole book.

### Language server

`mw-lsp` speaks LSP over stdio — point any client at the `mw-lsp` binary for Markdown
diagnostics (ASCII / semantic line breaks / undefined citations + cross-refs), an
outline (document symbols), `@key` completion, and go-to-definition.

### Themes

`--theme amber|matrix|wp` at launch, or **F2** to cycle in-session: **amber** (amber
phosphor on near-black, default), **matrix** (bright green on black), **wp** (white on
classic WordPerfect-DOS blue). Defined in `mw-tui/src/theme.rs`.

## Workspace

- `mw-core` — rope buffer, cursor/selection, undo, file I/O, **persisted undo (`.mwh` sidecar)** (UI-free, unit-tested)
- `mw-cite` — .bib/CSL-JSON parsing, @key completion + undefined-citation validation
- `mw-lint` — ASCII discipline + semantic line breaks (lint + fix, unit-tested)
- `mw-lsp` — Markdown language server (diagnostics/outline/completion/go-to-def)
- `mw-md` — tree-sitter Markdown highlighting + outline + folds (UI-free, unit-tested)
- `mw-preview` — CommonMark→HTML + live-reload preview server (unit-tested)
- `mw-project` — manifest, compile order, project-wide wordcount + search (unit-tested)
- `mw-render` — pandoc / make / XeLaTeX export engine (unit-tested, incl. real PDF build)
- `mw-sign` — minisign-compatible Ed25519 detached signatures (keygen/sign/verify)
- `mw-tui` — ratatui + crossterm front-end; ships the `mw` binary

[`ropey`]: https://crates.io/crates/ropey
