Add LanguageTool grammar/spell checking with a settings dialog

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>
This commit is contained in:
landon
2026-07-27 08:33:00 -05:00
parent 6bd44c2ac9
commit 5ecbf3ffbb
7 changed files with 1017 additions and 2 deletions
+54
View File
@@ -24,6 +24,42 @@ pub struct Config {
/// +100 = double, -50 = half).
#[serde(default)]
pub editor_zoom: f32,
/// URL scheme for the LanguageTool server: `http` (local) or `https`.
#[serde(default = "default_languagetool_scheme")]
pub languagetool_scheme: String,
/// Host of the LanguageTool server — an IP address or a domain name.
#[serde(default = "default_languagetool_host")]
pub languagetool_host: String,
/// TCP port of the LanguageTool server.
#[serde(default = "default_languagetool_port")]
pub languagetool_port: u16,
/// Optional bearer token sent in the `Authorization` header (for a server
/// behind an auth proxy, or the premium API key). Empty = no auth.
#[serde(default)]
pub languagetool_token: String,
/// Language passed to LanguageTool (`auto` to detect, or a code like `en-US`).
#[serde(default = "default_languagetool_language")]
pub languagetool_language: String,
}
/// Default scheme: plain HTTP, matching a local server.
pub fn default_languagetool_scheme() -> String {
"http".to_string()
}
/// Default host: the local machine.
pub fn default_languagetool_host() -> String {
"localhost".to_string()
}
/// Default port: the one the standalone LanguageTool server listens on.
pub fn default_languagetool_port() -> u16 {
8010
}
/// Default checking language: let the server auto-detect.
pub fn default_languagetool_language() -> String {
"auto".to_string()
}
/// Default header/body separator, matching the manuscript drafting convention.
@@ -42,6 +78,11 @@ impl Default for Config {
draft_marker: default_marker(),
zero_pad_index: false,
editor_zoom: 0.0,
languagetool_scheme: default_languagetool_scheme(),
languagetool_host: default_languagetool_host(),
languagetool_port: default_languagetool_port(),
languagetool_token: String::new(),
languagetool_language: default_languagetool_language(),
}
}
}
@@ -62,6 +103,19 @@ fn config_path() -> PathBuf {
}
impl Config {
/// Assemble the LanguageTool base URL (`scheme://host:port`) from its parts.
pub fn languagetool_base_url(&self) -> String {
let scheme = match self.languagetool_scheme.trim() {
"" => "http",
s => s,
};
format!(
"{scheme}://{}:{}",
self.languagetool_host.trim(),
self.languagetool_port
)
}
pub fn load() -> Config {
let path = config_path();
match std::fs::read_to_string(&path) {