Просмотр исходного кода

feat(rule-edit): improve regex test execution with background processing

52fisher 4 месяцев назад
Родитель
Сommit
f5a8ff3a20

+ 42 - 16
app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt

@@ -39,7 +39,9 @@ import io.legado.app.ui.widget.components.button.series.MediumPlainButton
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import java.util.regex.Pattern
 import java.util.regex.PatternSyntaxException
 
@@ -90,6 +92,7 @@ fun <T> RuleEditSheet(
     // Test results state
     var testResults by remember(show, rule) { mutableStateOf<List<TestLineResult>?>(null) }
     var testError by remember(show, rule) { mutableStateOf<String?>(null) }
+    var testRunning by remember(show, rule) { mutableStateOf(false) }
 
     // Pre-resolve string resources at composable level
     val regexIsEmptyStr = stringResource(R.string.regex_is_empty)
@@ -99,6 +102,7 @@ fun <T> RuleEditSheet(
     fun getCurrentEntity() = fromFields(RuleEditFields(name, rule1, rule2), rule)
 
     fun runTest() {
+        if (testRunning) return
         if (rule1.isBlank()) {
             testError = regexIsEmptyStr
             testResults = null
@@ -119,25 +123,47 @@ fun <T> RuleEditSheet(
             return
         }
 
+        // Capture inputs for background processing
+        val capturedRule2 = rule2
+        testRunning = true
         testError = null
-        val lines = rule2.lines()
-        val fullText = "\n${rule2}"
-        val matcher = pattern.matcher(fullText)
+        testResults = null
 
-        // Collect all match ranges (character offsets in fullText)
-        val matchRanges = mutableListOf<IntRange>()
-        while (matcher.find()) {
-            matchRanges.add(matcher.start()..matcher.end())
-        }
+        scope.launch(Dispatchers.Default) {
+            val results = withContext(Dispatchers.Default) {
+                // Normalize line endings: \r\n and \r → \n to avoid offset misalignment
+                val normalized = capturedRule2.replace("\r\n", "\n").replace("\r", "\n")
+                val lines = normalized.lines()
+                val fullText = "\n$normalized"
+                val matcher = pattern.matcher(fullText)
+
+                // Collect all match ranges (character offsets in fullText)
+                val matchRanges = mutableListOf<IntRange>()
+                while (matcher.find()) {
+                    matchRanges.add(matcher.start()..matcher.end())
+                }
+
+                // Map each line: check if any match overlaps with this line's range
+                var offset = 1 // skip the leading \n
+                lines.map { line ->
+                    val lineStart = offset
+                    val lineEnd = offset + line.length
+                    val matched = matchRanges.any { range ->
+                        if (range.first == range.last) {
+                            // Zero-width match: check if it falls within [lineStart, lineEnd]
+                            range.first in lineStart..lineEnd
+                        } else {
+                            // Normal match: check overlap
+                            range.first < lineEnd && range.last > lineStart
+                        }
+                    }
+                    offset = lineEnd + 1 // +1 for \n
+                    TestLineResult(line = line, matched = matched, matchResult = if (matched) line else null)
+                }
+            }
 
-        // Map each line: check if any match overlaps with this line's range in fullText
-        var offset = 1 // first \n
-        testResults = lines.map { line ->
-            val lineStart = offset
-            val lineEnd = offset + line.length
-            val matched = matchRanges.any { it.first < lineEnd && it.last > lineStart }
-            offset = lineEnd + 1 // +1 for the \n that was between lines
-            TestLineResult(line = line, matched = matched, matchResult = if (matched) line else null)
+            testResults = results
+            testRunning = false
         }
     }