feat(muya): add spellcheck word-replacement API#4392
Conversation
Add `replaceCurrentWordInlineUnsafe(word, replacement)` to @muyajs/core, restoring the legacy muyajs `_replaceCurrentWordInlineUnsafe` API the desktop spell checker relies on. When the user right-clicks a misspelled word, Chromium selects the whole word; choosing a suggestion from the context menu replaces it inline. The new method finds the word at the cursor (using the VSCode-derived word boundaries ported from legacy muyajs), asserts it matches the expected `word`, replaces it through the Content text setter so the change dispatches a json edit op, and places the cursor after the replacement. It is a no-op when there is no active content block / cursor, or when the word at the cursor does not match (guards a Chromium selection mismatch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a public Muya.replaceCurrentWordInlineUnsafe(word, replacement) API to support the desktop spellchecker’s “replace misspelled word” context-menu action during the ongoing migration from legacy packages/muyajs to @muyajs/core.
Changes:
- Added
Muya.replaceCurrentWordInlineUnsafe(...)delegating to the activeContentblock. - Implemented
Content.replaceCurrentWordInlineUnsafe(...)using a legacy-portedextractWordhelper and updating text via the existingtextsetter (json1 op). - Added a new happy-dom Vitest spec covering success, cursor positioning, and no-op paths.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/muya/src/muya.ts | Exposes the new public spellcheck word-replacement API on Muya. |
| packages/muya/src/block/base/content.ts | Implements word extraction + replacement logic on the active content block. |
| packages/muya/src/tests/replaceCurrentWord.spec.ts | Adds test coverage for replacement behavior and no-op scenarios. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Word boundary regexes ported from legacy muyajs | ||
| // (lib/contentState/core.js), which in turn derive from VSCode's wordHelper. | ||
| // Used by `extractWord` to find the word at the cursor for spell-check | ||
| // replacement. |
There was a problem hiding this comment.
Good catch — fixed in 5273b02. You're right that the legacy extractWord and the WORD_SEPARATORS/WORD_DEFINITION regexes (carrying the VSCode wordHelper attribution) live in packages/muyajs/lib/marktext/spellchecker.js, not lib/contentState/core.js (which only holds the replaceWordInLine range-replacement helper). Both port-source comments at lines 24 and 33 now point to lib/marktext/spellchecker.js.
The `extractWord` helper and the `WORD_SEPARATORS`/`WORD_DEFINITION` regexes were ported from legacy muyajs `lib/marktext/spellchecker.js` (which carries the VSCode wordHelper attribution), not from `lib/contentState/core.js`. `core.js` only holds the `replaceWordInLine` range-replacement helper, so the previous comments pointed at the wrong file. Correct both references to ease future maintenance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Why
The desktop spell checker shows a context menu of suggestions for a misspelled word; choosing one calls the editor to replace the word at the cursor. Legacy
muyajsexposed_replaceCurrentWordInlineUnsafe(word, replacement)for exactly this.@muyajs/corehad no equivalent, which blocks the desktop migration off legacypackages/muyajs.What
Adds
Muya.replaceCurrentWordInlineUnsafe(word: string, replacement: string): boolean(packages/muya/src/muya.ts), delegating to a new method of the same name on the active content block (packages/muya/src/block/base/content.ts).Semantics (faithful to legacy muyajs)
extractWord, the VSCode-derived word-boundary helper ported from legacymuyajs(lib/contentState/core.js).word. When it does not match (e.g. a Chromium selection mismatch), the call is a safe no-op and returnsfalse.Contenttextsetter, so the edit dispatches a json1 op and stays in sync with document state (it does not bypass state sync).false) when there is no active content block or no cursor.The public method is inserted immediately after
Muya.format(type)to stay clear of parallel edits elsewhere inmuya.ts.Tests
New happy-dom vitest spec
packages/muya/src/__tests__/replaceCurrentWord.spec.tsboots a realMuya, sets the cursor inside a known misspelled word, calls the API, and assertsgetMarkdown()reflects the replacement (state flushes on rAF, awaited viavi.waitFor). Also covers cursor-after-replacement positioning and the no-op paths (word mismatch, no active block, no cursor).Verification
All green in
packages/muya:pnpm lint— 0 errorspnpm lint:types— cleanpnpm check-circular— no circular dependencypnpm test— 441 passedpnpm test:spec— 1347 passed🤖 Generated with Claude Code