mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-11 22:51:26 +00:00
refactor: switch to native browser spell check
- Remove custom spell check implementation (spellCheck.ts and tests) - Use native spellcheck attribute on textarea element - Remove custom spell check CSS styles - Update translation to reflect native browser spell check usage - Simplify ChatTextArea component by removing spell check logic As requested by @hannesrudolph, this implementation now uses the system's native spell check service instead of a custom dictionary-based approach.
This commit is contained in:
parent
8814688861
commit
2142c198d9
5 changed files with 2 additions and 681 deletions
|
|
@ -11,7 +11,6 @@ import { EXPERIMENT_IDS, experiments as experimentsLib } from "@roo/experiments"
|
|||
import { vscode } from "@/utils/vscode"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { checkSpelling, debounce, SpellCheckResult } from "@/utils/spellCheck"
|
||||
import {
|
||||
ContextMenuOptionType,
|
||||
getContextMenuOptions,
|
||||
|
|
@ -183,8 +182,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [isEnhancingPrompt, setIsEnhancingPrompt] = useState(false)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const [spellCheckResults, setSpellCheckResults] = useState<SpellCheckResult[]>([])
|
||||
const spellCheckLayerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Use custom hook for prompt history navigation
|
||||
const { handleHistoryNavigation, resetHistoryNavigation, resetOnInputChange } = usePromptHistory({
|
||||
|
|
@ -226,31 +223,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
return experiments && experimentsLib.isEnabled(experiments, EXPERIMENT_IDS.SPELL_CHECK)
|
||||
}, [experiments])
|
||||
|
||||
// Debounced spell check function
|
||||
const performSpellCheck = useMemo(
|
||||
() =>
|
||||
debounce(async (text: string) => {
|
||||
if (!isSpellCheckEnabled || !text.trim()) {
|
||||
setSpellCheckResults([])
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await checkSpelling(text)
|
||||
setSpellCheckResults(results)
|
||||
} catch (error) {
|
||||
console.error("Spell check error:", error)
|
||||
setSpellCheckResults([])
|
||||
}
|
||||
}, 300),
|
||||
[isSpellCheckEnabled],
|
||||
)
|
||||
|
||||
// Perform spell check when input changes
|
||||
useEffect(() => {
|
||||
performSpellCheck(inputValue)
|
||||
}, [inputValue, performSpellCheck])
|
||||
|
||||
const allModes = useMemo(() => getAllModes(customModes), [customModes])
|
||||
|
||||
const queryItems = useMemo(() => {
|
||||
|
|
@ -709,52 +681,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}
|
||||
}, [])
|
||||
|
||||
const updateSpellCheckHighlights = useCallback(() => {
|
||||
if (!spellCheckLayerRef.current || !isSpellCheckEnabled) return
|
||||
|
||||
const text = inputValue
|
||||
let html = ""
|
||||
let lastIndex = 0
|
||||
|
||||
// Sort spell check results by start index
|
||||
const sortedResults = [...spellCheckResults].sort((a, b) => a.startIndex - b.startIndex)
|
||||
|
||||
sortedResults.forEach((result) => {
|
||||
// Add text before the misspelled word
|
||||
const beforeText = text.slice(lastIndex, result.startIndex)
|
||||
html += beforeText
|
||||
.replace(/\n$/, "\n\n")
|
||||
.replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c] || c)
|
||||
|
||||
// Add the misspelled word with highlighting
|
||||
const misspelledWord = text.slice(result.startIndex, result.endIndex)
|
||||
html += `<span class="spell-check-error">${misspelledWord.replace(
|
||||
/[<>&]/g,
|
||||
(c) => ({ "<": "<", ">": ">", "&": "&" })[c] || c,
|
||||
)}</span>`
|
||||
|
||||
lastIndex = result.endIndex
|
||||
})
|
||||
|
||||
// Add remaining text
|
||||
const remainingText = text.slice(lastIndex)
|
||||
html += remainingText
|
||||
.replace(/\n$/, "\n\n")
|
||||
.replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c] || c)
|
||||
|
||||
spellCheckLayerRef.current.innerHTML = html
|
||||
|
||||
// Sync scroll position
|
||||
if (textAreaRef.current) {
|
||||
spellCheckLayerRef.current.scrollTop = textAreaRef.current.scrollTop
|
||||
spellCheckLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
|
||||
}
|
||||
}, [inputValue, spellCheckResults, isSpellCheckEnabled])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateSpellCheckHighlights()
|
||||
}, [inputValue, spellCheckResults, updateSpellCheckHighlights])
|
||||
|
||||
const handleKeyUp = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)) {
|
||||
|
|
@ -1125,30 +1051,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
color: "transparent",
|
||||
}}
|
||||
/>
|
||||
{isSpellCheckEnabled && (
|
||||
<div
|
||||
ref={spellCheckLayerRef}
|
||||
className={cn(
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"pointer-events-none",
|
||||
"whitespace-pre-wrap",
|
||||
"break-words",
|
||||
"text-transparent",
|
||||
"overflow-hidden",
|
||||
"font-vscode-font-family",
|
||||
"text-vscode-editor-font-size",
|
||||
"leading-vscode-editor-line-height",
|
||||
"py-2",
|
||||
"px-[9px]",
|
||||
"z-[5]",
|
||||
"forced-color-adjust-none",
|
||||
)}
|
||||
style={{
|
||||
color: "transparent",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DynamicTextArea
|
||||
ref={(el) => {
|
||||
if (typeof ref === "function") {
|
||||
|
|
@ -1181,6 +1083,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
minRows={3}
|
||||
maxRows={15}
|
||||
autoFocus={true}
|
||||
spellCheck={isSpellCheckEnabled}
|
||||
className={cn(
|
||||
"w-full",
|
||||
"text-vscode-input-foreground",
|
||||
|
|
@ -1213,7 +1116,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
)}
|
||||
onScroll={() => {
|
||||
updateHighlights()
|
||||
updateSpellCheckHighlights()
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -651,7 +651,7 @@
|
|||
},
|
||||
"SPELL_CHECK": {
|
||||
"name": "Enable spell check in chat",
|
||||
"description": "When enabled, misspelled words in the chat input will be underlined in red. This helps catch typos before sending messages to Roo."
|
||||
"description": "When enabled, uses the browser's native spell check to underline misspelled words in the chat input. This helps catch typos before sending messages to Roo."
|
||||
}
|
||||
},
|
||||
"promptCaching": {
|
||||
|
|
|
|||
|
|
@ -386,14 +386,6 @@ vscode-dropdown::part(listbox) {
|
|||
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
||||
}
|
||||
|
||||
/* Spell check styles */
|
||||
.spell-check-error {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #ff0000;
|
||||
text-decoration-style: wavy;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* vscrui Overrides / Hacks
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import { checkSpelling, debounce, isSpellCheckSupported } from "../spellCheck"
|
||||
|
||||
describe("spellCheck", () => {
|
||||
describe("isSpellCheckSupported", () => {
|
||||
it("should return true when spellcheck property exists", () => {
|
||||
// Mock document.createElement
|
||||
const mockElement = { spellcheck: true }
|
||||
vi.spyOn(document, "createElement").mockReturnValue(mockElement as any)
|
||||
|
||||
expect(isSpellCheckSupported()).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when spellcheck property does not exist", () => {
|
||||
// Mock document.createElement
|
||||
const mockElement = {}
|
||||
vi.spyOn(document, "createElement").mockReturnValue(mockElement as any)
|
||||
|
||||
expect(isSpellCheckSupported()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("checkSpelling", () => {
|
||||
it("should return empty array for empty text", async () => {
|
||||
const results = await checkSpelling("")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should return empty array for text with only common words", async () => {
|
||||
const results = await checkSpelling("the quick brown fox jumps over the lazy dog")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should detect misspelled words", async () => {
|
||||
const results = await checkSpelling("This is a tset of speling")
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results[0]).toEqual({
|
||||
word: "tset",
|
||||
startIndex: 10,
|
||||
endIndex: 14,
|
||||
})
|
||||
expect(results[1]).toEqual({
|
||||
word: "speling",
|
||||
startIndex: 18,
|
||||
endIndex: 25,
|
||||
})
|
||||
})
|
||||
|
||||
it("should ignore words with numbers", async () => {
|
||||
const results = await checkSpelling("test123 abc456")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should ignore words that are all uppercase", async () => {
|
||||
const results = await checkSpelling("API URL HTTP")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should ignore words starting with @ or /", async () => {
|
||||
const results = await checkSpelling("@mention /command")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should ignore very short words", async () => {
|
||||
const results = await checkSpelling("a I to")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle contractions correctly", async () => {
|
||||
const results = await checkSpelling("don't can't won't")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should detect misspelled words in a longer text", async () => {
|
||||
const text = "The quick brown fox jumps over the lazy dog. This sentense has a mispelling."
|
||||
const results = await checkSpelling(text)
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results[0].word).toBe("sentense")
|
||||
expect(results[1].word).toBe("mispelling")
|
||||
})
|
||||
|
||||
it("should handle technical terms correctly", async () => {
|
||||
const results = await checkSpelling("function variable const class method async await promise")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle Roo-specific terms correctly", async () => {
|
||||
const results = await checkSpelling("roo chat message task workspace api token context prompt")
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("debounce", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("should debounce function calls", () => {
|
||||
const mockFn = vi.fn()
|
||||
const debouncedFn = debounce(mockFn, 100)
|
||||
|
||||
// Call multiple times quickly
|
||||
debouncedFn("arg1")
|
||||
debouncedFn("arg2")
|
||||
debouncedFn("arg3")
|
||||
|
||||
// Function should not have been called yet
|
||||
expect(mockFn).not.toHaveBeenCalled()
|
||||
|
||||
// Fast forward time
|
||||
vi.advanceTimersByTime(100)
|
||||
|
||||
// Function should have been called once with the last arguments
|
||||
expect(mockFn).toHaveBeenCalledTimes(1)
|
||||
expect(mockFn).toHaveBeenCalledWith("arg3")
|
||||
})
|
||||
|
||||
it("should reset timer on each call", () => {
|
||||
const mockFn = vi.fn()
|
||||
const debouncedFn = debounce(mockFn, 100)
|
||||
|
||||
debouncedFn("arg1")
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
debouncedFn("arg2")
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
// Function should not have been called yet
|
||||
expect(mockFn).not.toHaveBeenCalled()
|
||||
|
||||
vi.advanceTimersByTime(50)
|
||||
|
||||
// Function should have been called once with the last arguments
|
||||
expect(mockFn).toHaveBeenCalledTimes(1)
|
||||
expect(mockFn).toHaveBeenCalledWith("arg2")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,431 +0,0 @@
|
|||
/**
|
||||
* Simple spell check utility for detecting misspelled words
|
||||
* Uses the browser's built-in spell check API when available
|
||||
*/
|
||||
|
||||
export interface SpellCheckResult {
|
||||
word: string
|
||||
startIndex: number
|
||||
endIndex: number
|
||||
suggestions?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the browser supports the native spell check API
|
||||
*/
|
||||
export const isSpellCheckSupported = (): boolean => {
|
||||
// Check if we're in a browser environment and if the spell check API is available
|
||||
return typeof window !== "undefined" && "spellcheck" in document.createElement("textarea")
|
||||
}
|
||||
|
||||
/**
|
||||
* Common English words that should not be marked as misspelled
|
||||
* This is a basic dictionary for fallback when native spell check is not available
|
||||
*/
|
||||
const commonWords = new Set([
|
||||
// Common words
|
||||
"the",
|
||||
"be",
|
||||
"to",
|
||||
"of",
|
||||
"and",
|
||||
"a",
|
||||
"in",
|
||||
"that",
|
||||
"have",
|
||||
"i",
|
||||
"it",
|
||||
"for",
|
||||
"not",
|
||||
"on",
|
||||
"with",
|
||||
"he",
|
||||
"as",
|
||||
"you",
|
||||
"do",
|
||||
"at",
|
||||
"this",
|
||||
"but",
|
||||
"his",
|
||||
"by",
|
||||
"from",
|
||||
"they",
|
||||
"we",
|
||||
"say",
|
||||
"her",
|
||||
"she",
|
||||
"or",
|
||||
"an",
|
||||
"will",
|
||||
"my",
|
||||
"one",
|
||||
"all",
|
||||
"would",
|
||||
"there",
|
||||
"their",
|
||||
"what",
|
||||
"so",
|
||||
"up",
|
||||
"out",
|
||||
"if",
|
||||
"about",
|
||||
"who",
|
||||
"get",
|
||||
"which",
|
||||
"go",
|
||||
"me",
|
||||
"when",
|
||||
"make",
|
||||
"can",
|
||||
"like",
|
||||
"time",
|
||||
"no",
|
||||
"just",
|
||||
"him",
|
||||
"know",
|
||||
"take",
|
||||
"people",
|
||||
"into",
|
||||
"year",
|
||||
"your",
|
||||
"good",
|
||||
"some",
|
||||
"could",
|
||||
"them",
|
||||
"see",
|
||||
"other",
|
||||
"than",
|
||||
"then",
|
||||
"now",
|
||||
"look",
|
||||
"only",
|
||||
"come",
|
||||
"its",
|
||||
"over",
|
||||
"think",
|
||||
"also",
|
||||
"back",
|
||||
"after",
|
||||
"use",
|
||||
"two",
|
||||
"how",
|
||||
"our",
|
||||
"work",
|
||||
"first",
|
||||
"well",
|
||||
"way",
|
||||
"even",
|
||||
"new",
|
||||
"want",
|
||||
"because",
|
||||
"any",
|
||||
"these",
|
||||
"give",
|
||||
"day",
|
||||
"most",
|
||||
"us",
|
||||
"is",
|
||||
"was",
|
||||
"are",
|
||||
"been",
|
||||
"has",
|
||||
"had",
|
||||
"were",
|
||||
"said",
|
||||
"did",
|
||||
"getting",
|
||||
"made",
|
||||
"find",
|
||||
"where",
|
||||
"much",
|
||||
"too",
|
||||
"very",
|
||||
"still",
|
||||
"being",
|
||||
"going",
|
||||
"why",
|
||||
"before",
|
||||
"never",
|
||||
"here",
|
||||
"more",
|
||||
"always",
|
||||
"those",
|
||||
"tell",
|
||||
"really",
|
||||
"something",
|
||||
"nothing",
|
||||
"everything",
|
||||
"anything",
|
||||
// Additional common words
|
||||
"quick",
|
||||
"brown",
|
||||
"fox",
|
||||
"jumps",
|
||||
"lazy",
|
||||
"dog",
|
||||
"sentence",
|
||||
"misspelling",
|
||||
// Contractions
|
||||
"don't",
|
||||
"can't",
|
||||
"won't",
|
||||
"isn't",
|
||||
"aren't",
|
||||
"wasn't",
|
||||
"weren't",
|
||||
"hasn't",
|
||||
"haven't",
|
||||
"hadn't",
|
||||
"doesn't",
|
||||
"didn't",
|
||||
"wouldn't",
|
||||
"shouldn't",
|
||||
"couldn't",
|
||||
"mightn't",
|
||||
"mustn't",
|
||||
"would've",
|
||||
"should've",
|
||||
"could've",
|
||||
"might've",
|
||||
"must've",
|
||||
"i'll",
|
||||
"you'll",
|
||||
"he'll",
|
||||
"she'll",
|
||||
"we'll",
|
||||
"they'll",
|
||||
"i'd",
|
||||
"you'd",
|
||||
"he'd",
|
||||
"she'd",
|
||||
"we'd",
|
||||
"they'd",
|
||||
"i've",
|
||||
"you've",
|
||||
"we've",
|
||||
"they've",
|
||||
"i'm",
|
||||
"you're",
|
||||
"he's",
|
||||
"she's",
|
||||
"it's",
|
||||
"we're",
|
||||
"they're",
|
||||
"let's",
|
||||
"that's",
|
||||
"who's",
|
||||
"what's",
|
||||
"where's",
|
||||
"when's",
|
||||
"why's",
|
||||
"how's",
|
||||
"here's",
|
||||
"there's",
|
||||
// Tech-related words
|
||||
"code",
|
||||
"function",
|
||||
"variable",
|
||||
"const",
|
||||
"let",
|
||||
"var",
|
||||
"class",
|
||||
"method",
|
||||
"property",
|
||||
"object",
|
||||
"array",
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"null",
|
||||
"undefined",
|
||||
"true",
|
||||
"false",
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"switch",
|
||||
"case",
|
||||
"break",
|
||||
"continue",
|
||||
"return",
|
||||
"try",
|
||||
"catch",
|
||||
"finally",
|
||||
"throw",
|
||||
"new",
|
||||
"this",
|
||||
"super",
|
||||
"extends",
|
||||
"implements",
|
||||
"interface",
|
||||
"enum",
|
||||
"type",
|
||||
"namespace",
|
||||
"module",
|
||||
"import",
|
||||
"export",
|
||||
"default",
|
||||
"from",
|
||||
"as",
|
||||
"async",
|
||||
"await",
|
||||
"promise",
|
||||
"then",
|
||||
"catch",
|
||||
"finally",
|
||||
"callback",
|
||||
"error",
|
||||
"debug",
|
||||
"console",
|
||||
"log",
|
||||
"warn",
|
||||
"info",
|
||||
"trace",
|
||||
"assert",
|
||||
"clear",
|
||||
"count",
|
||||
"group",
|
||||
"time",
|
||||
"profile",
|
||||
// Roo-specific words
|
||||
"roo",
|
||||
"chat",
|
||||
"message",
|
||||
"task",
|
||||
"file",
|
||||
"directory",
|
||||
"workspace",
|
||||
"project",
|
||||
"api",
|
||||
"model",
|
||||
"token",
|
||||
"context",
|
||||
"prompt",
|
||||
"response",
|
||||
"request",
|
||||
"approve",
|
||||
"reject",
|
||||
"execute",
|
||||
"command",
|
||||
"terminal",
|
||||
"browser",
|
||||
"search",
|
||||
"replace",
|
||||
"edit",
|
||||
"create",
|
||||
"delete",
|
||||
"read",
|
||||
"write",
|
||||
"list",
|
||||
"view",
|
||||
"open",
|
||||
"close",
|
||||
"save",
|
||||
"load",
|
||||
"refresh",
|
||||
"update",
|
||||
"install",
|
||||
"uninstall",
|
||||
"build",
|
||||
"test",
|
||||
"run",
|
||||
"start",
|
||||
"stop",
|
||||
"restart",
|
||||
"deploy",
|
||||
"commit",
|
||||
"push",
|
||||
"pull",
|
||||
"merge",
|
||||
"branch",
|
||||
"checkout",
|
||||
"clone",
|
||||
"fork",
|
||||
])
|
||||
|
||||
/**
|
||||
* Check if a word is likely misspelled using a basic dictionary
|
||||
* This is a fallback for when native spell check is not available
|
||||
*/
|
||||
const isLikelyMisspelled = (word: string): boolean => {
|
||||
// Ignore very short words
|
||||
if (word.length <= 2) return false
|
||||
|
||||
// Ignore words with numbers
|
||||
if (/\d/.test(word)) return false
|
||||
|
||||
// Ignore words that are all uppercase (likely acronyms)
|
||||
if (word === word.toUpperCase()) return false
|
||||
|
||||
// Don't check words that start with @ or / (mentions and commands)
|
||||
// Extract the actual word without the prefix for checking
|
||||
const wordToCheck = word.startsWith("@") || word.startsWith("/") ? word.substring(1) : word
|
||||
|
||||
// Check against common words dictionary
|
||||
return !commonWords.has(wordToCheck.toLowerCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract words from text with their positions
|
||||
*/
|
||||
const extractWords = (text: string): Array<{ word: string; start: number; end: number }> => {
|
||||
const words: Array<{ word: string; start: number; end: number }> = []
|
||||
// Match words (including contractions like "don't", "it's") but exclude @ and / mentions
|
||||
// This regex will not match words that are part of @mentions or /commands
|
||||
const wordRegex = /(?<![@/])\b[\w']+\b/g
|
||||
let match
|
||||
|
||||
while ((match = wordRegex.exec(text)) !== null) {
|
||||
// Double check that this word is not part of a mention or command
|
||||
const charBefore = text[match.index - 1]
|
||||
if (charBefore !== "@" && charBefore !== "/") {
|
||||
words.push({
|
||||
word: match[0],
|
||||
start: match.index,
|
||||
end: match.index + match[0].length,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform spell check on the given text
|
||||
* Returns an array of potentially misspelled words with their positions
|
||||
*/
|
||||
export const checkSpelling = async (text: string): Promise<SpellCheckResult[]> => {
|
||||
const results: SpellCheckResult[] = []
|
||||
const words = extractWords(text)
|
||||
|
||||
// For now, use the basic dictionary check
|
||||
// In the future, this could be enhanced with a proper spell check API
|
||||
for (const { word, start, end } of words) {
|
||||
if (isLikelyMisspelled(word)) {
|
||||
results.push({
|
||||
word,
|
||||
startIndex: start,
|
||||
endIndex: end,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function to limit spell check frequency
|
||||
*/
|
||||
export const debounce = <T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number,
|
||||
): ((...args: Parameters<T>) => void) => {
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(() => func(...args), wait)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue