d310635b65
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.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
/**
|
|
* 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[]>;
|
|
}
|