mirror of
https://github.com/BigMarc/grammargem.git
synced 2026-10-13 17:01:55 +02:00
Replace the NSSpellChecker + regex-stub engine with harper-core 2.5.0: real subject-verb agreement, verb forms, punctuation, repetition and spelling. New harper-ffi crate (staticlib) exposes harper_check over a C ABI; the CHarper module bridges it to Swift; HarperEngine maps Harper's char spans onto UTF-16 ranges and de-dups overlapping lints. NSSpellChecker stays only as a case-preserving spelling supplement, which fixes the problemm->Problem mis-casing. Bulk apply now skips no-op suggestions. build.sh / scripts/build.sh build the universal static lib; target/ and lib/ are gitignored (regenerate via harper-ffi/build.sh). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
1.5 KiB
Swift
37 lines
1.5 KiB
Swift
import Foundation
|
|
|
|
/// Layer 1 — deterministic, instant, offline grammar checking.
|
|
///
|
|
/// In production this is backed by **Harper** (Rust, Apache-2.0) via a C-FFI
|
|
/// static lib (see `harper-ffi/`) or a bundled `harper-ls` subprocess. The
|
|
/// protocol keeps the rest of the app independent of that integration.
|
|
protocol GrammarEngine: AnyObject {
|
|
/// Return suggestions over `text` (non-destructive; UI draws underlines).
|
|
func check(_ text: String) -> [Suggestion]
|
|
|
|
/// Apply every suggestion to produce a corrected string (used by the
|
|
/// "fix in place" hotkey path).
|
|
func correct(_ text: String) -> String
|
|
|
|
/// Words the engine should never flag (names, brand terms, jargon).
|
|
var ignoreList: Set<String> { get set }
|
|
}
|
|
|
|
extension GrammarEngine {
|
|
/// Default correction: apply suggestions right-to-left so offsets stay valid.
|
|
///
|
|
/// No-op flags (where the replacement equals the original — e.g. a grammar
|
|
/// issue Harper can describe but not auto-fix) are skipped, so "Fix all" /
|
|
/// the in-place hotkey only ever apply real, text-changing corrections and
|
|
/// never silently rewrite a span to itself.
|
|
func correct(_ text: String) -> String {
|
|
let ns = NSMutableString(string: text)
|
|
for s in check(text).sorted(by: { $0.location > $1.location }) {
|
|
guard s.replacement != s.original else { continue }
|
|
guard s.location + s.length <= ns.length else { continue }
|
|
ns.replaceCharacters(in: s.range, with: s.replacement)
|
|
}
|
|
return ns as String
|
|
}
|
|
}
|