Files
md-manuscript/README.md
T
landon c926b6696f Include the POV field in the file-list hover tooltip
Header parsing now also lifts a `POV:` line (any heading level). The
file-list tooltip combines it with the slug — "POV: <name>" then the
slug synopsis — cached per file alongside the slug (HoverInfo) and read
live from the buffer for the selected file. Adds a POV parse test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
2026-08-14 06:49:53 -05:00

252 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# md-manuscript
A small desktop application for Debian that treats a directory of markdown files
as an ordered manuscript.
* **Left pane** — every `*.md` file in the workspace directory, in a manual order
you set by **dragging the `⠿` handle** up and down. **Hover a file** to see a
tooltip built from its header — the `POV:` line and the `# Slug:` synopsis —
a quick at-a-glance summary of each chapter.
* **Right pane** — a markdown text editor for the selected file (optional live
preview via the *Preview* checkbox).
* **Order + contents are saved and version-controlled with git.** The order lives
in `order.json` inside the workspace, so it syncs along with the files.
* **Export** the whole manuscript as a single concatenated `.odt` file, with a
chapter heading before each file's content.
Built in Rust with [`egui`](https://github.com/emilk/egui)/`eframe`. The ODT
writer is native (no `pandoc`/LibreOffice needed at runtime).
## Build
```sh
cargo build --release
# binary: target/release/md-manuscript
```
Requires a Rust toolchain. The GUI needs a normal desktop session (X11 or
Wayland) with OpenGL — the usual runtime libraries on Debian:
```sh
sudo apt install libgl1 libxkbcommon0 libwayland-client0 libx11-6
```
(Only needed to *run* the GUI; building does not require them.)
## Install
```sh
install -Dm755 target/release/md-manuscript ~/.local/bin/md-manuscript
```
Optionally add a desktop launcher at
`~/.local/share/applications/md-manuscript.desktop`:
```ini
[Desktop Entry]
Type=Application
Name=md-manuscript
Exec=md-manuscript
Categories=Office;TextEditor;
Terminal=false
```
## Use
1. Set the **Workspace** path in the top bar (default: `~/Manuscript`) — type it,
or click **📂** to pick a folder in a native file browser — and press **Open**.
The directory is created if it does not exist.
2. Press **Init git** once to make the workspace a git repository. To sync with
another machine, add a remote yourself:
```sh
cd ~/Manuscript
git remote add origin <url>
git push -u origin main
```
After that, the **⟳ Sync (git)** button commits all changes, `pull --rebase`s,
and pushes.
If you open a folder that is *not* itself a repository but sits inside one
(a parent directory is a git work tree), the app asks whether to **use the
enclosing repository**. Accept it to have **⟳ Sync** commit and push to that
repo; decline to leave the workspace on its own, where **Init git** creates a
separate repository in the folder.
3. Create files with ** New**, edit on the right, `Ctrl+S` (or the Save button)
to write to disk. Drag the `` handles to reorder. Use the **Zoom** slider
above the editor to scale the editor text (0% = default; **Reset** returns to
it), and the **Contrast** slider to brighten dim editor text (0% = theme
default; higher pushes the text toward white in dark mode / black in light
mode). Text always wraps to the pane width, so there is no horizontal
scrolling. Inline markdown has keyboard shortcuts, applied to the current
selection (press again on wrapped text to remove the markers):
| Shortcut | Effect |
| --- | --- |
| `Ctrl/Cmd + B` | **bold** (`**…**`) |
| `Ctrl/Cmd + I` | *italic* (`*…*`) |
| `Ctrl/Cmd + E` | `inline code` |
| `Ctrl/Cmd + Shift + X` | ~~strikethrough~~ (`~~…~~`) |
| `Ctrl/Cmd + K` | link — `[selection](url)`, with `url` selected to replace |
Press **`Ctrl/Cmd + F`** (or **Edit ▸ Find / Replace…**) to open the
find/replace bar above the editor; **`Ctrl/Cmd + H`** opens it with the
replace field focused. Type a search term to shade every match in the editor
(the current one brighter); **Enter** / **Shift+Enter** (or **▼** / **▲**)
step through them, wrapping around, and scroll the match into view. **Aa**
toggles case sensitivity. **Replace** swaps the current match and advances;
**Replace all** swaps every match in the file at once. **Esc** closes the bar.
4. Set the **Chapter title** for the open file if you want to override the
auto-derived one (the field shows the automatic title as a hint). Leaving it
blank falls back to a `# Title:` line in the header, and if there is none, to
the chapter's **position in the left pane** followed by a period (`1.`, `2.`,
…). Tick **Zero-pad #** in the top bar to pad those numbered titles with
leading zeros to a uniform width (e.g. `03.` when there are 12 chapters).
Titles are saved to `titles.json` and sync with git.
5. Set the **Export** path (type it, or click **📂** to choose the `.odt` file in a
native save dialog) and press **Export ODT** to produce the concatenated
document. Each file becomes a chapter headed by its title, optionally followed
by a caption from its `# Slug:` line. **Every chapter starts on a new page**
(the first chapter excepted, so there is no blank leading page).
The status bar shows a live **word count** for the current file, plus the net
words added (or removed) to it since the current session started. The count
updates as you type, including unsaved edits.
### Per-file word-count targets
Give a file a target by adding a **`Word Count Target:`** line to its header (at
any heading level):
```markdown
## Word Count Target: 1500 - 2000
```
A **progress bar** then appears in the status bar, comparing the file's **prose**
word count (the body below the draft marker, so header metadata isn't counted)
against the target: **amber** while under the target, **green** once inside the
range, **blue** when over it. A single number (`## Word Count Target: 1800`) sets
a point goal; ranges accept `-`, ``, `to`, and grouped digits (`1,500`). Like
the other header lines, the target is stripped from the exported document.
## Spelling (offline)
Spelling is checked **live as you type**, entirely offline — no server, no
network. Misspelled words get a red underline; **right-click** one for a menu of
suggested corrections (click a suggestion to apply it). It uses Hunspell
dictionaries read by the pure-Rust [`spellbook`](https://crates.io/crates/spellbook)
crate, so the binary stays self-contained.
* **Two dictionaries are built in** — Canadian English (`en-CA`, the default)
and British English (`en-GB`). Pick one from the **Dictionary** dropdown on
the *Spelling* row of the top bar; the choice is remembered.
* **More languages** are discovered automatically from the usual Hunspell
folders (`/usr/share/hunspell`, `/usr/share/myspell`, …) and from a per-user
folder, `~/.config/md-manuscript/dictionaries/`. Drop a matching
`xx_YY.aff` + `xx_YY.dic` pair in there (e.g. from your distro's
`hunspell-de-de` package) and it appears in the dropdown.
* **Untick “Check as I type”** on the Spelling row to turn the underlines off.
* Code spans, fenced/indented code blocks and link targets are skipped, and
ALL-CAPS initialisms (ODT, HTTP) are left alone, to cut false positives.
When you run a **LanguageTool** check (below), its richer spelling-and-grammar
results take over the underlines until you next edit the text — at which point
the live offline checker resumes. In other words, LanguageTool is used when it's
available and current; the offline checker is the always-on default.
The bundled dictionaries live under `dictionaries/` and are derived from
[SCOWL](http://wordlist.sourceforge.net/) under a permissive license (kept
alongside them in each `license` file).
## Grammar & spelling (LanguageTool)
The editor can *additionally* check the current file against a
[LanguageTool](https://languagetool.org/) server — typically a **local
instance**, so your manuscript never leaves your machine.
1. Run a LanguageTool server. The standalone server listens on
`http://localhost:8010` by default (the official Docker image uses `:8081` —
just point the app at whichever you run):
```sh
# Standalone (needs Java):
java -cp 'languagetool-server.jar' org.languagetool.server.HTTPServer --port 8010
# or via Docker:
docker run -d -p 8010:8010 --entrypoint java \
erikvl87/languagetool -cp languagetool-server.jar \
org.languagetool.server.HTTPServer --port 8010 --allow-origin '*'
```
2. Open **Settings ▸ LanguageTool…** (from the menu bar, or the **⚙** button on
the Grammar row) and set the connection:
* **Scheme** — `http` for a local server, `https` for a remote one (TLS is
supported).
* **Host / domain** — an IP address or hostname (default `localhost`).
* **Port** — the server's port (default `8010`).
* **Token** — optional; sent as an `Authorization: Bearer` header for a server
behind an auth proxy, or a premium API key. Leave blank for an
unauthenticated local server.
* **Language** — `auto` to detect, or a code like `en-US`, `en-GB`, `de-DE`.
Press **Test connection** to confirm the settings reach a working server. All
fields are remembered in the config.
3. Open a file and press **✓ Check**. The check runs in the background (the UI
stays responsive) and issues appear two ways:
* **Underlines in the editor** — red for spelling, blue for grammar/style.
* **A "Grammar & spelling" panel** at the bottom listing each issue with its
explanation and suggested fixes. Click a suggestion to apply it; the
remaining underlines re-align automatically.
Editing the text after a check hides the underlines (their positions would no
longer be accurate) and disables the fix buttons until you re-check. The checker
sends the raw file text, so occasional markdown tokens (e.g. a `#` heading
marker) may be flagged.
## Header stripping on export
Manuscript files often carry editorial notes and metadata above the prose that
should not appear in the finished document. On export, each file is cleaned up:
* **HTML comments** (`<!-- ... -->`, including multi-line ones) are removed.
* **The header block** — everything above the **Draft marker** line (set in the
top bar; default `### Rough Draft:`) — is dropped. From that header:
* `# Title:` becomes the chapter heading (unless a manual override is set), and
* `# Slug:` becomes an italic caption printed beneath the heading.
Any other header text is discarded. Files with no marker line are exported
whole, but stray `# Title:` / `# Slug:` lines are still lifted out. Clear the
Draft marker field to disable header splitting entirely.
For example, this source file:
```markdown
# Title: The Gate
# Slug: in which the door will not open
<!-- remember to foreshadow the key -->
outline: she arrives, she knocks, nothing
### Rough Draft:
The prose that actually gets exported begins here.
```
exports as a chapter titled **The Gate**, captioned *in which the door will not
open*, containing only the prose below the marker.
## Files the app writes
| Location | Purpose |
|---|---|
| `<workspace>/*.md` | your manuscript files |
| `<workspace>/order.json` | the manual ordering (committed to git) |
| `<workspace>/titles.json` | chapter-title overrides (committed to git) |
| `~/.config/md-manuscript/config.json` | last workspace, export path, prefs |
## Markdown supported in ODT export
Headings, paragraphs, **bold**, *italic*, ***both***, `inline code`, fenced/indented
code blocks (monospace, whitespace preserved), ~~strikethrough~~, ordered and
unordered (nested) lists, block quotes, horizontal rules, and links. Images are
rendered as a text placeholder (not embedded).
A quick reference for all of this — plus the chapter metadata, draft-marker
header, and comment conventions — is built in: open **Help ▸ Markdown cheatsheet**
from the menu bar.