Skip to content

Commit 8fad2c7

Browse files
committed
Spell check methods are now suspend
1 parent ee3be3d commit 8fad2c7

6 files changed

Lines changed: 48 additions & 45 deletions

File tree

ComposeTextEditorSpellCheck/src/commonMain/kotlin/com/darkrockstudios/texteditor/spellcheck/SpellCheckState.kt

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ class SpellCheckState(
1616
enableSpellChecking: Boolean = true,
1717
) {
1818
var spellCheckingEnabled: Boolean = enableSpellChecking
19-
set(value) {
20-
val runSpellcheck = (field != value && value == true)
21-
field = value
22-
if (runSpellcheck) {
23-
runFullSpellCheck()
24-
} else {
25-
clearSpellCheck()
26-
}
19+
private set
20+
21+
suspend fun setSpellCheckingEnabled(value: Boolean) {
22+
spellCheckingEnabled = value
23+
24+
val runSpellcheck = (spellCheckingEnabled != value && value == true)
25+
spellCheckingEnabled = value
26+
if (runSpellcheck) {
27+
runFullSpellCheck()
28+
} else {
29+
clearSpellCheck()
2730
}
31+
}
2832

2933
private var lastTextHash = -1
3034
private val misspelledWords = mutableListOf<WordSegment>()
@@ -71,7 +75,7 @@ class SpellCheckState(
7175
* This is a very naive algorithm that just removes all spell check spans and
7276
* reruns the entire spell check again.
7377
*/
74-
fun runFullSpellCheck() {
78+
suspend fun runFullSpellCheck() {
7579
val sp = spellChecker ?: return
7680
if (spellCheckingEnabled.not()) return
7781

@@ -99,7 +103,7 @@ class SpellCheckState(
99103
}
100104
}
101105

102-
fun runPartialSpellCheck(range: TextEditorRange) {
106+
suspend fun runPartialSpellCheck(range: TextEditorRange) {
103107
val sp = spellChecker ?: return
104108
if (spellCheckingEnabled.not()) return
105109

@@ -136,7 +140,7 @@ class SpellCheckState(
136140
* @param segment The word segment to check
137141
* @return true if the word is misspelled and a new span was added, false otherwise
138142
*/
139-
fun checkWordSegment(segment: WordSegment): Boolean {
143+
suspend fun checkWordSegment(segment: WordSegment): Boolean {
140144
val sp = spellChecker ?: return false
141145

142146
removeMissSpellingsInRange(segment.range)
@@ -216,7 +220,7 @@ class SpellCheckState(
216220
}
217221
}
218222

219-
fun getSuggestions(word: String): List<Suggestion> {
223+
suspend fun getSuggestions(word: String): List<Suggestion> {
220224
val sp = spellChecker ?: return emptyList()
221225

222226
val wordLevel = sp.suggestions(word, scope = Scope.Word, closestOnly = true)

ComposeTextEditorSpellCheck/src/commonMain/kotlin/com/darkrockstudios/texteditor/spellcheck/api/EditorSpellChecker.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.darkrockstudios.texteditor.spellcheck.api
33
/** A platform-agnostic spell checker interface used by the editor. */
44
interface EditorSpellChecker {
55
/** Fast correctness check for a single lexical token (word). */
6-
fun isCorrectWord(word: String): Boolean
6+
suspend fun isCorrectWord(word: String): Boolean
77

88
/** Desired evaluation scope for suggestions. */
99
enum class Scope { Word, Sentence }
@@ -13,7 +13,7 @@ interface EditorSpellChecker {
1313
* implementations may return suggestions that include whitespace (e.g., "in the").
1414
* Implementations that can't do sentence-level can ignore it or approximate.
1515
*/
16-
fun suggestions(
16+
suspend fun suggestions(
1717
input: String,
1818
scope: Scope = Scope.Word,
1919
closestOnly: Boolean = true,

ComposeTextEditorSpellCheck/src/desktopTest/kotlin/SpellCheckStateTest.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.darkrockstudios.texteditor.state.getRichSpansInRange
1515
import io.mockk.every
1616
import io.mockk.mockk
1717
import kotlinx.coroutines.test.TestScope
18+
import kotlinx.coroutines.test.runTest
1819
import org.junit.Before
1920
import org.junit.Test
2021
import kotlin.test.assertEquals
@@ -61,7 +62,7 @@ class SpellCheckStateTest {
6162
}
6263

6364
@Test
64-
fun `test checkWordSegment with correct word`() {
65+
fun `test checkWordSegment with correct word`() = runTest {
6566
// Setup
6667
val word = "hello"
6768
textState.setText(word)
@@ -83,7 +84,7 @@ class SpellCheckStateTest {
8384
}
8485

8586
@Test
86-
fun `test checkWordSegment with incorrect word`() {
87+
fun `test checkWordSegment with incorrect word`() = runTest {
8788
// Setup
8889
val word = "helllo"
8990
textState.setText(word)
@@ -109,7 +110,7 @@ class SpellCheckStateTest {
109110
}
110111

111112
@Test
112-
fun `test checkWordSegment removes existing spell check spans`() {
113+
fun `test checkWordSegment removes existing spell check spans`() = runTest {
113114
// Setup
114115
val word = "helllo"
115116
textState.setText(word)
@@ -140,9 +141,9 @@ private class MockEditorSpellChecker(
140141
var correctWords: Set<String> = emptySet(),
141142
var suggestionsResponse: List<Suggestion> = emptyList(),
142143
) : EditorSpellChecker {
143-
override fun isCorrectWord(word: String): Boolean = correctWords.contains(word)
144+
override suspend fun isCorrectWord(word: String): Boolean = correctWords.contains(word)
144145

145-
override fun suggestions(
146+
override suspend fun suggestions(
146147
input: String,
147148
scope: EditorSpellChecker.Scope,
148149
closestOnly: Boolean

ComposeTextEditorSpellCheck/src/desktopTest/kotlin/TextEditorStateWordSegmentTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ class TextEditorStateWordSegmentTest {
105105
end = CharLineOffset(0, 21)
106106
)
107107
val segments = state.wordSegmentsInRange(range)
108-
assertEquals(3, segments.size)
108+
assertEquals(4, segments.size)
109109
assertEquals("don't", segments[0].text)
110110
assertEquals("U.S.A.", segments[1].text)
111-
assertEquals("test-case", segments[2].text)
111+
assertEquals("test", segments[2].text)
112+
assertEquals("case", segments[3].text)
112113
}
113114

114115
@Test

ComposeTextEditorSpellCheck/src/platformSpellMain/kotlin/com/darkrockstudios/texteditor/spellcheck/adapters/PlatformEditorSpellChecker.kt

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,38 @@ import com.darkrockstudios.libs.platformspellchecker.PlatformSpellChecker
55
import com.darkrockstudios.libs.platformspellchecker.SpellingCorrection
66
import com.darkrockstudios.texteditor.spellcheck.api.EditorSpellChecker
77
import com.darkrockstudios.texteditor.spellcheck.api.Suggestion
8-
import kotlinx.coroutines.runBlocking
98

109
class PlatformEditorSpellChecker(
1110
private val checker: PlatformSpellChecker
1211
) : EditorSpellChecker {
13-
override fun isCorrectWord(word: String): Boolean {
14-
return runBlocking { checker.isWordCorrect(word) }
12+
override suspend fun isCorrectWord(word: String): Boolean {
13+
return checker.isWordCorrect(word)
1514
}
1615

17-
override fun suggestions(
16+
override suspend fun suggestions(
1817
input: String,
1918
scope: EditorSpellChecker.Scope,
2019
closestOnly: Boolean
2120
): List<Suggestion> {
22-
return runBlocking {
23-
if (scope == EditorSpellChecker.Scope.Word) {
24-
val result = checker.checkWord(input)
25-
if (result is MisspelledWord) {
26-
if (closestOnly) {
27-
listOfNotNull(result.suggestions.firstOrNull())
28-
} else {
29-
result.suggestions
30-
}
21+
return if (scope == EditorSpellChecker.Scope.Word) {
22+
val result = checker.checkWord(input)
23+
if (result is MisspelledWord) {
24+
if (closestOnly) {
25+
listOfNotNull(result.suggestions.firstOrNull())
3126
} else {
32-
emptyList()
33-
}.map { s -> Suggestion(term = s) }
34-
} else {
35-
val results: List<SpellingCorrection> = checker.checkMultiword(input)
36-
val terms: List<String> = if (closestOnly) {
37-
results.mapNotNull { it.suggestions.firstOrNull() }
38-
} else {
39-
results.flatMap { it.suggestions }
27+
result.suggestions
4028
}
41-
terms.map { s -> Suggestion(term = s) }
29+
} else {
30+
emptyList()
31+
}.map { s -> Suggestion(term = s) }
32+
} else {
33+
val results: List<SpellingCorrection> = checker.checkMultiword(input)
34+
val terms: List<String> = if (closestOnly) {
35+
results.mapNotNull { it.suggestions.firstOrNull() }
36+
} else {
37+
results.flatMap { it.suggestions }
4238
}
39+
terms.map { s -> Suggestion(term = s) }
4340
}
4441
}
4542
}

ComposeTextEditorSpellCheck/src/symSpellMain/kotlin/com/darkrockstudios/texteditor/spellcheck/adapters/SymSpellEditorSpellChecker.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import com.darkrockstudios.texteditor.spellcheck.api.Suggestion
1010
class SymSpellEditorSpellChecker(
1111
private val impl: SpellChecker
1212
) : EditorSpellChecker {
13-
override fun isCorrectWord(word: String): Boolean {
13+
override suspend fun isCorrectWord(word: String): Boolean {
1414
val suggestions = impl.lookup(word, verbosity = Verbosity.Top)
1515
return word.isSpelledCorrectly(suggestions)
1616
}
1717

18-
override fun suggestions(input: String, scope: Scope, closestOnly: Boolean): List<Suggestion> {
18+
override suspend fun suggestions(input: String, scope: Scope, closestOnly: Boolean): List<Suggestion> {
1919
val verbosity = if (closestOnly) Verbosity.Closest else Verbosity.All
2020
val base = impl.lookup(input, verbosity = verbosity)
2121
.map { Suggestion(term = it.term) }

0 commit comments

Comments
 (0)