Skip to content

Commit 8c32d3a

Browse files
david-allisonBrayanDSO
authored andcommitted
fix(study-screen): regex crash on typed answers
* New study screen * Enable setting: "Type answer into card" * A 'type' card with the back containing: "$" caused: "java.lang.IllegalArgumentException: Illegal group reference" Fixed by using `Regex.escapeReplacement` as recommended in the docs https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-regex/replace.html The lambda .replace() overload was also an option, but I wanted to be explicit about the intent Fixes 20575 Assisted-by: Claude Opus 4.6 Very minor: created the base test file
1 parent 0662a31 commit 8c32d3a

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/previewer/TypeAnswer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class TypeAnswer private constructor(
5555

5656
@Language("HTML")
5757
val repl = """<div style="font-family: '$font'; font-size: ${fontSize}px">$answerComparison</div>"""
58-
return typeAnsRe.replace(text, repl)
58+
return typeAnsRe.replace(text, Regex.escapeReplacement(repl))
5959
}
6060

6161
companion object {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2026 David Allison <davidallisongithub@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
package com.ichi2.anki.previewer
17+
18+
import androidx.test.ext.junit.runners.AndroidJUnit4
19+
import com.ichi2.anki.libanki.Card
20+
import com.ichi2.testutils.JvmTest
21+
import org.hamcrest.MatcherAssert.assertThat
22+
import org.hamcrest.Matchers.containsString
23+
import org.hamcrest.Matchers.not
24+
import org.junit.Test
25+
import org.junit.jupiter.api.assertDoesNotThrow
26+
import org.junit.runner.RunWith
27+
28+
@RunWith(AndroidJUnit4::class)
29+
class TypeAnswerTest : JvmTest() {
30+
/** [Issue #20575](https://github.com/ankidroid/Anki-Android/issues/20575) */
31+
@Test
32+
fun `answerFilter escapes Regex`() =
33+
runTest {
34+
val card = addBasicWithTypingNote("List directory contents.", "$ ls").firstCard()
35+
36+
val typeAnswer = TypeAnswer.createInstance(card)
37+
38+
val result = assertDoesNotThrow { typeAnswer.answerFilter("") }
39+
assertThat(result, containsString("$ ls"))
40+
assertThat(result, not(containsString("[[type:Back]]")))
41+
}
42+
43+
companion object {
44+
suspend fun TypeAnswer.Companion.createInstance(card: Card) = requireNotNull(TypeAnswer.getInstance(card, VALID_CARD_TEXT))
45+
46+
const val VALID_CARD_TEXT = """<style>.card {
47+
font-family: arial;
48+
font-size: 20px;
49+
line-height: 1.5;
50+
text-align: center;
51+
color: black;
52+
background-color: white;
53+
}
54+
</style>List directory contents.
55+
56+
<hr id=answer>
57+
58+
[[type:Back]]"""
59+
}
60+
}

0 commit comments

Comments
 (0)