From f390594cefe1f68354bfe3c914e3a015a3f2f1a8 Mon Sep 17 00:00:00 2001 From: Robert Mongold Date: Sat, 22 Aug 2026 08:39:11 -0400 Subject: [PATCH] Document platform spell-check contract and stop Linux ignore panics Spellkit's per-OS defaults and failure modes were easy to get wrong from the README alone. Document Checker::new, locale errors, suggestions limits, and UTF-8 range in rustdoc, and treat invalid C strings as a no-op in Hunspell ignore instead of unwrapping. --- CHANGELOG.md | 9 ++++++++- README.md | 5 ++++- src/lib.rs | 28 +++++++++++++++++++++++++--- src/unix.rs | 4 +++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c845e..5087acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ This project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Changed +- Document platform defaults for `Checker::new()`, locale failure, suggestions cap, and UTF-8 error ranges in rustdoc +- README now states that macOS `Checker::new()` uses the system language + +### Fixed +- Linux `ignore` no longer panics when the word contains an interior NUL + ## [0.3.0] - 2026-08-07 ### Breaking @@ -50,7 +57,7 @@ This project follows [Semantic Versioning](https://semver.org/). ### Changed - Edition 2021; dropped `lazy_static` / `extern crate` -- macOS uses `OnceLock` for the shared `NSSpellChecker` +- macOS serializes access to the shared `NSSpellChecker` with a `Mutex` - Windows COM failures map to `Error` where creating the checker; UTF-16 indices convert to UTF-8 byte ranges - `hunspell-sys` 0.1.3 → 0.3.1 on Linux diff --git a/README.md b/README.md index f241bc9..b983758 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # spellkit +[![On crates.io](https://img.shields.io/crates/v/spellkit.svg)](https://crates.io/crates/spellkit) +![Downloads](https://img.shields.io/crates/d/spellkit?style=flat-square) [![CI](https://github.com/rtmongold/spellkit/actions/workflows/ci.yml/badge.svg)](https://github.com/rtmongold/spellkit/actions/workflows/ci.yml) +[![Docs](https://docs.rs/spellkit/badge.svg)](https://docs.rs/spellkit) Native spell checking with a small Rust API. @@ -36,7 +39,7 @@ fn main() -> Result<(), spellkit::Error> { } ``` -`Checker::new()` defaults to English (`en_US` / `en_GB` on Linux, `en-US` on Windows). Use `with_locale` for another language. Locales may be written as `en_US` or `en-US`. +`Checker::new()` uses a platform default: system language on macOS, `en-US` on Windows, and the first available of `en_US` / `en_GB` on Linux. Use `with_locale` for another language. Unknown or unsupported locales behave differently by platform: diff --git a/src/lib.rs b/src/lib.rs index 186f401..b1ecdfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,10 @@ //! This corresponds to [`ISpellChecker`] on Windows, [`NSSpellChecker`] on MacOS, and [`hunspell`] //! on other *nix platforms. //! +//! Spellkit does not bundle dictionaries or implement its own spelling algorithm. +//! It wraps the platform backend and uses system / installed dictionaries. +//! Behavior can differ across operating systems where the underlying APIs differ. +//! //! # Example //! //! ``` @@ -25,7 +29,8 @@ use std::fmt; #[derive(Debug)] pub enum Error { - /// No usable dictionary /backend (e.g. missing hunspell files on Linux). + /// No usable spell checker (e.g. missing Hunspell files on Linux, COM/create + /// failure on Windows, or an empty locale on macOS). Unavailable, } @@ -79,18 +84,31 @@ cfg_if! { pub struct Checker(imp::Checker); impl Checker { - /// Create an instance of the system spell checker. + /// Create a checker with a platform default locale. + /// + /// - **Linux:** first available of `en_US`, then `en_GB` under the Hunspell directories. + /// - **macOS:** the system default language + /// - **Windows:** `en-US` pub fn new() -> Result { Ok(Checker(imp::Checker::new()?)) } /// Create a checker for a specific locale (`en_US` or `en-US` both work). + /// + /// Unknown or unsupported locales behave differently by platform: + /// + /// - **Linux:** missing dictionary → [`Error::Unavailable`] + /// - **macOS:** empty locale → [`Error::Unavailable`]; unknown tags may still + /// succeed (system fallback) + /// - **Windows:** unsupported language tag → [`Error::Unavailable`] pub fn with_locale(locale: &str) -> Result { let (hunspell, bcp47) = normalize_locale(locale); Ok(Checker(imp::Checker::with_locale(&hunspell, &bcp47)?)) } - /// Spelling suggestions for `word` (may be empty). + /// Spelling suggestions for `word`. + /// + /// Returns at most 10 suggestions, or an empty list if none are available. pub fn suggest(&self, word: &str) -> Vec { self.0.suggest(word) } @@ -121,9 +139,13 @@ impl SpellingError { pub fn text(&self) -> &str { self.0.text() } + /// Inclusive start index of the misspelling, as a UTF-8 byte offset into the + /// original text. `&text[start()..end()]` is the misspelled word. pub fn start(&self) -> usize { self.0.start() } + /// Exclusive end index of the misspelling, as a UTF-8 byte offset into the + /// original text. `&text[start()..end()]` is the misspelled word. pub fn end(&self) -> usize { self.0.end() } diff --git a/src/unix.rs b/src/unix.rs index 4321253..95ebba9 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -116,7 +116,9 @@ impl Checker { } pub fn ignore(&mut self, word: &str) { - let cstr = CString::new(word).unwrap(); + let Ok(cstr) = CString::new(word) else { + return; + }; unsafe { Hunspell_add(