Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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:

Expand Down
28 changes: 25 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//!
//! ```
Expand All @@ -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,
}

Expand Down Expand Up @@ -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<Self, Error> {
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<Self, Error> {
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<String> {
self.0.suggest(word)
}
Expand Down Expand Up @@ -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()
}
Expand Down
4 changes: 3 additions & 1 deletion src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading