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:
@@ -45,6 +45,24 @@ pub struct Config {
|
||||
/// Language passed to LanguageTool (`auto` to detect, or a code like `en-US`).
|
||||
#[serde(default = "default_languagetool_language")]
|
||||
pub languagetool_language: String,
|
||||
/// Whether the offline (Hunspell) live spell checker underlines misspellings
|
||||
/// as you type. Used whenever LanguageTool results aren't current.
|
||||
#[serde(default = "default_spell_check")]
|
||||
pub spell_check: bool,
|
||||
/// Id of the spell-check dictionary to use (e.g. `en-CA`, `en_GB`), matching
|
||||
/// a [`crate::spell::DictEntry::id`].
|
||||
#[serde(default = "default_spell_language")]
|
||||
pub spell_language: String,
|
||||
}
|
||||
|
||||
/// Live spell checking is on by default.
|
||||
pub fn default_spell_check() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Default dictionary: the built-in Canadian English one.
|
||||
pub fn default_spell_language() -> String {
|
||||
crate::spell::DEFAULT_LANGUAGE.to_string()
|
||||
}
|
||||
|
||||
/// Default scheme: plain HTTP, matching a local server.
|
||||
@@ -89,6 +107,8 @@ impl Default for Config {
|
||||
languagetool_port: default_languagetool_port(),
|
||||
languagetool_token: String::new(),
|
||||
languagetool_language: default_languagetool_language(),
|
||||
spell_check: default_spell_check(),
|
||||
spell_language: default_spell_language(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user