aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-02-29 22:13:22 +0100
committerGitHub <noreply@github.com>2024-02-29 22:13:22 +0100
commitd4bd14793bf920d27de14bcea7350d67f1a8a000 (patch)
tree1bf8b3e241448317e102302de6ced3815cb35bde
parent0c96b9d6ee14a9dcaafad303ed82357f252fdd6b (diff)
downloadskyhanni-d4bd14793bf920d27de14bcea7350d67f1a8a000.tar.gz
skyhanni-d4bd14793bf920d27de14bcea7350d67f1a8a000.tar.bz2
skyhanni-d4bd14793bf920d27de14bcea7350d67f1a8a000.zip
Improve performance of removeColor. (#1079)
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt59
-rw-r--r--src/test/java/at/hannibal2/skyhanni/test/RemoveColorTest.kt45
2 files changed, 90 insertions, 14 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
index 192073111..a8ab6e6cd 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
@@ -34,7 +34,7 @@ object StringUtils {
return first + lowercase.substring(1)
}
- private val formattingChars by lazy { "kmolnr".toCharArray() + "kmolnr".uppercase().toCharArray() }
+ private val formattingChars = "kmolnrKMOLNR".toSet()
/**
* Removes color and optionally formatting codes from the given string, leaving plain text.
@@ -42,22 +42,53 @@ object StringUtils {
* @param keepFormatting Boolean indicating whether to retain non-color formatting codes (default: false).
* @return A string with color codes removed (and optionally formatting codes if specified).
*/
- fun String.removeColor(keepFormatting: Boolean = false): String {
- val builder = StringBuilder(this.length)
-
- var counter = 0
- while (counter < this.length) {
- if (this[counter] == '§') {
- if (!keepFormatting || this[counter + 1] !in formattingChars) {
- counter += 2
- continue
- }
+ fun CharSequence.removeColor(keepFormatting: Boolean = false): String {
+ // Glossary:
+ // Formatting indicator: The '§' character indicating the beginning of a formatting sequence
+ // Formatting code: The character following a formatting indicator which specifies what color or text style this sequence corresponds to
+ // Formatting sequence: The combination of a formatting indicator and code that changes the color or format of a string
+
+ // Find the first formatting indicator
+ var nextFormattingSequence = indexOf('§')
+
+ // If this string does not contain any formatting indicators, just return this string directly
+ if (nextFormattingSequence < 0) return this.toString()
+
+ // Let's create a new string, and pre-allocate enough space to store this entire string
+ val cleanedString = StringBuilder(this.length)
+
+ // Read index stores the position in `this` which we have written up until now
+ // a/k/a where we need to start reading from
+ var readIndex = 0
+
+ // As long as there still is a formatting indicator left in our string
+ while (nextFormattingSequence >= 0) {
+
+ // Write everything from the read index up to the next formatting indicator into our clean string
+ cleanedString.append(this, readIndex, nextFormattingSequence)
+
+ // If the next formatting sequence's code indicates a non-color format and we should keep those
+ if (keepFormatting && nextFormattingSequence + 1 < length && this[nextFormattingSequence + 1] in formattingChars) {
+ // Set the readIndex to the formatting indicator, so that the next loop will start writing from that paragraph symbol
+ readIndex = nextFormattingSequence
+ // Find the next § symbol after the formatting sequence
+ nextFormattingSequence = indexOf('§', startIndex = readIndex + 1)
+ } else {
+ // If this formatting sequence should be skipped (either a color code, or !keepFormatting or an incomplete formatting sequence without a code)
+ // Set the readIndex to after this formatting sequence, so that the next loop will skip over it before writing the string
+ readIndex = nextFormattingSequence + 2
+ // Find the next § symbol after the formatting sequence
+ nextFormattingSequence = indexOf('§', startIndex = readIndex)
+
+ // If the next read would be out of bound, reset the readIndex to the very end of the string, resulting in a "" string to be appended
+ readIndex = readIndex.coerceAtMost(this.length)
}
- builder.append(this[counter])
- counter++
}
+ // Finally, after the last formatting sequence was processed, copy over the last sequence of the string
+ cleanedString.append(this, readIndex, this.length)
- return builder.toString()
+ // And turn the string builder into a string
+ return cleanedString.toString()
}
fun UUID.toDashlessUUID(): String {
diff --git a/src/test/java/at/hannibal2/skyhanni/test/RemoveColorTest.kt b/src/test/java/at/hannibal2/skyhanni/test/RemoveColorTest.kt
new file mode 100644
index 000000000..f12f295fa
--- /dev/null
+++ b/src/test/java/at/hannibal2/skyhanni/test/RemoveColorTest.kt
@@ -0,0 +1,45 @@
+package at.hannibal2.skyhanni.test
+
+import at.hannibal2.skyhanni.utils.StringUtils.removeColor
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+class RemoveColorTest {
+ @Test
+ fun testEdging() {
+ Assertions.assertEquals("", "§".removeColor())
+ Assertions.assertEquals("a", "a§".removeColor())
+ Assertions.assertEquals("b", "§ab§".removeColor())
+ }
+
+ @Test
+ fun `testDouble§`() {
+ Assertions.assertEquals("1", "§§1".removeColor())
+ Assertions.assertEquals("1", "§§1".removeColor(true))
+ Assertions.assertEquals("k", "§§k".removeColor(true))
+ }
+
+ @Test
+ fun testKeepNonColor() {
+ Assertions.assertEquals("§k§l§m§n§o§r", "§k§l§m§f§n§o§r".removeColor(true))
+ }
+
+ @Test
+ fun testPlainString() {
+ Assertions.assertEquals("bcdefgp", "bcdefgp")
+ Assertions.assertEquals("", "")
+ }
+
+ @Test
+ fun testSomeNormalTestCases() {
+ Assertions.assertEquals(
+ "You are not currently in a party.",
+ "§r§cYou are not currently in a party.§r".removeColor()
+ )
+ Assertions.assertEquals(
+ "Ancient Necron's Chestplate ✪✪✪✪",
+ "§dAncient Necron's Chestplate §6✪§6✪§6✪§6✪".removeColor()
+ )
+ }
+
+}