Add offline live spell checking (Hunspell via spellbook)

Spelling is now checked live as you type, fully offline, using the
pure-Rust `spellbook` crate to read Hunspell .aff/.dic dictionaries (no C
libhunspell — the binary stays self-contained; ldd unchanged).

- Canadian (en-CA, default) and British (en-GB) English dictionaries are
  compiled in; more languages are auto-discovered from system Hunspell
  folders and ~/.config/md-manuscript/dictionaries/. A "Spelling" row in
  the top bar toggles live checking and picks the dictionary.
- Misspellings are underlined in red; right-clicking a word opens a menu
  of suggested corrections. A results-panel and one-click fixes work too.
- Checks run on a background thread, debounced ~400ms after the last edit.
  Prose is extracted with pulldown-cmark so code spans, code blocks and
  link targets are skipped; ALL-CAPS initialisms are ignored.
- LanguageTool still layers on top: when its results are fresh they own
  the underlines (spelling + grammar); once you edit, the offline checker
  resumes. The editor underlines, issues panel and fix-apply logic are
  now shared between the two sources.

Bundled dictionaries are SCOWL-derived under a permissive license (kept
in dictionaries/<lang>/license). Adds spell-check unit tests (tokenizer,
code-block skipping, offset mapping, en-CA/en-GB spelling); 40 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9kRuP7JvXoUGdNNeg5ZSs
This commit is contained in:
landon
2026-08-14 06:25:08 -05:00
parent 691d1a0515
commit 19dddf30c7
14 changed files with 101168 additions and 69 deletions
+3
View File
@@ -18,6 +18,9 @@ rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "a
# backend links statically (no new runtime .so deps), so both a plain-HTTP local
# server and an HTTPS domain work.
ureq = { version = "2", default-features = false, features = ["tls"] }
# Pure-Rust reader of Hunspell .aff/.dic dictionaries for offline spell
# checking, so the binary stays self-contained (no C libhunspell to link).
spellbook = "0.4.2"
[profile.release]
opt-level = 2