Check the current file against a LanguageTool server (typically a local instance) from a background thread. Issues are underlined in the editor (red spelling, blue grammar) and listed in a bottom panel with one-click fixes that re-align the remaining matches. A menu bar (File / View / Settings) exposes a LanguageTool settings dialog with discrete scheme / host / port / token / language fields, an endpoint preview, and a Test connection button. Config stores the parts and assembles the base URL. The token is sent as a bearer header. Uses ureq with a statically-linked rustls backend, so both http and https endpoints work with no new runtime library dependencies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.3 KiB
md-manuscript
A small desktop application for Debian that treats a directory of markdown files as an ordered manuscript.
- Left pane — every
*.mdfile in the workspace directory, in a manual order you set by dragging the⠿handle up and down. - 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.jsoninside the workspace, so it syncs along with the files. - Export the whole manuscript as a single concatenated
.odtfile, with a chapter heading before each file's content.
Built in Rust with egui/eframe. The ODT
writer is native (no pandoc/LibreOffice needed at runtime).
Build
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:
sudo apt install libgl1 libxkbcommon0 libwayland-client0 libx11-6
(Only needed to run the GUI; building does not require them.)
Install
install -Dm755 target/release/md-manuscript ~/.local/bin/md-manuscript
Optionally add a desktop launcher at
~/.local/share/applications/md-manuscript.desktop:
[Desktop Entry]
Type=Application
Name=md-manuscript
Exec=md-manuscript
Categories=Office;TextEditor;
Terminal=false
Use
- 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. - Press Init git once to make the workspace a git repository. To sync with
another machine, add a remote yourself:
After that, the ⟳ Sync (git) button commits all changes,
cd ~/Manuscript git remote add origin <url> git push -u origin mainpull --rebases, and pushes. - 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). Text always wraps to the pane width, so there is no horizontal scrolling. - 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 totitles.jsonand sync with git. - Set the Export path (type it, or click 📂 to choose the
.odtfile 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.
Grammar & spelling (LanguageTool)
The editor can check the current file against a LanguageTool server — typically a local instance, so your manuscript never leaves your machine.
-
Run a LanguageTool server. The standalone server listens on
http://localhost:8010by default (the official Docker image uses:8081— just point the app at whichever you run):# 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 '*' -
Open Settings ▸ LanguageTool… (from the menu bar, or the ⚙ button on the Grammar row) and set the connection:
- Scheme —
httpfor a local server,httpsfor 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: Bearerheader for a server behind an auth proxy, or a premium API key. Leave blank for an unauthenticated local server. - Language —
autoto detect, or a code likeen-US,en-GB,de-DE.
Press Test connection to confirm the settings reach a working server. All fields are remembered in the config.
- Scheme —
-
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:
# 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).