feat(editor): add spell & grammar check engines with decorations
Wire real spell-checking into the editor via hunspell-wasm (bundled en_US dictionary) and an optional LanguageTool grammar client. - spell/: HunspellSpellEngine wrapper, pure spell logic, base64 en_US dict - grammar/: LanguageToolEngine grammar client and pure grammar logic - decorations/: collectBlock text assembly, offset-mapped CheckService, buildCheckDecorations (flat DecorationSet, inline spell/grammar marks) - export engines and types from editor index; add hunspell-wasm dep Verified: typecheck (6 pkgs), tests (all pass), lint (6 pkgs), and web build all green.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Grammar-check core logic — framework-agnostic and pure.
|
||||
*
|
||||
* The network client lives in `languagetool.ts`. This module owns the shape of
|
||||
* a grammar error and the parser that turns a LanguageTool API response into a
|
||||
* plain list of errors with offsets relative to the checked text. Keeping the
|
||||
* parser pure makes the response-shaping logic fully unit-testable.
|
||||
*/
|
||||
|
||||
/** A single grammar/punctuation error with its offset into the checked text. */
|
||||
export interface GrammarError {
|
||||
/** Zero-based offset of the flagged span within the checked text. */
|
||||
offset: number;
|
||||
/** Character length of the flagged span. */
|
||||
length: number;
|
||||
/** Human-readable explanation of the problem. */
|
||||
message: string;
|
||||
/** Suggested replacements, best first. */
|
||||
replacements: string[];
|
||||
/** LanguageTool rule id (e.g. `EN_A_VS_AN`), when available. */
|
||||
ruleId?: string;
|
||||
/** LanguageTool category id (e.g. `TYPOSIS`), when available. */
|
||||
category?: string;
|
||||
}
|
||||
|
||||
/** Minimal shape of a LanguageTool `/v2/check` response that we consume. */
|
||||
export interface LTResponse {
|
||||
issues?: Array<{
|
||||
offset: number;
|
||||
length: number;
|
||||
message: string;
|
||||
replacements?: Array<{ value: string; length?: number; quality?: number }>;
|
||||
rule?: { id?: string; category?: { id?: string } };
|
||||
}>;
|
||||
}
|
||||
|
||||
/** Parse a LanguageTool response into a flat list of {@link GrammarError}. */
|
||||
export function parseGrammarResponse(response: LTResponse): GrammarError[] {
|
||||
const issues = response.issues ?? [];
|
||||
const errors: GrammarError[] = [];
|
||||
for (const issue of issues) {
|
||||
const replacements = (issue.replacements ?? []).map((r) => r.value);
|
||||
const error: GrammarError = {
|
||||
offset: issue.offset,
|
||||
length: issue.length,
|
||||
message: issue.message,
|
||||
replacements,
|
||||
};
|
||||
if (issue.rule?.id) error.ruleId = issue.rule.id;
|
||||
if (issue.rule?.category?.id) error.category = issue.rule.category.id;
|
||||
errors.push(error);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* A grammar-check engine. Implementations accept a block of text and a language
|
||||
* tag and return grammar/punctuation errors.
|
||||
*/
|
||||
export interface GrammarEngine {
|
||||
/** Check a block of text and return grammar errors. */
|
||||
check(text: string, language?: string): Promise<GrammarError[]>;
|
||||
}
|
||||
Reference in New Issue
Block a user