diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/CursorFixer.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/CursorFixer.kt
index 723cbb6b..acbd5134 100644
--- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/CursorFixer.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/CursorFixer.kt
@@ -36,6 +36,10 @@ fun String.fixCursor(pos: Int, grouping: String): Int {
return listOf(leftCursor, rightCursor).minBy { abs(it - pos) }
}
+/**
+ * Same as [String.tokenAhead], but more efficient. We only need a number, not string.
+ * Don't replace!
+ */
fun String.tokenLengthAhead(pos: Int): Int {
Token.Func.allWithOpeningBracket.forEach {
if (pos.isAfterToken(this, it)) return it.length
@@ -44,6 +48,14 @@ fun String.tokenLengthAhead(pos: Int): Int {
return 1
}
+fun String.tokenAhead(pos: Int): String {
+ Token.Func.allWithOpeningBracket.forEach {
+ if (pos.isAfterToken(this, it)) return it
+ }
+
+ return substring((pos - 1).coerceAtLeast(0), pos)
+}
+
private fun String.isPlacedIllegallyAt(pos: Int, grouping: String): Boolean {
// For things like "123,|456" - this is illegal
if (pos.isAfterToken(this, grouping)) return true
@@ -71,3 +83,36 @@ private fun Int.isAfterToken(str: String, token: String): Boolean {
.substring((this - token.length).coerceAtLeast(0), this)
.contains(token)
}
+
+
+// This can make [TextFieldValue.addTokens] better by checking tokens both ways. Needs more tests
+//fun String.tokenAfter(pos: Int): String {
+// Token.Func.allWithOpeningBracket.forEach {
+// if (pos.isBeforeToken(this, it)) return it
+// }
+//
+// return substring(pos, (pos + 1).coerceAtMost(this.length))
+//}
+
+//private fun String.numberNearby(cursor: Int): String {
+// val text = this
+//
+// var aheadCursor = cursor
+// var afterCursor = cursor
+//
+// while (text.tokenAhead(aheadCursor) in Token.Digit.allWithDot) {
+// aheadCursor--
+// }
+//
+// while (text.tokenAfter(afterCursor) in Token.Digit.allWithDot) {
+// afterCursor++
+// }
+//
+// return text.substring(aheadCursor, afterCursor)
+//}
+
+//private fun Int.isBeforeToken(str: String, token: String): Boolean {
+// return str
+// .substring(this, (this + token.length).coerceAtMost(str.length))
+// .contains(token)
+//}
diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/TextFieldValueExtensions.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/TextFieldValueExtensions.kt
index 5e48fd28..d97b1e8b 100644
--- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/TextFieldValueExtensions.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/TextFieldValueExtensions.kt
@@ -20,14 +20,50 @@ package com.sadellie.unitto.core.ui.common.textfield
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
+import com.sadellie.unitto.core.base.Token
fun TextFieldValue.addTokens(tokens: String): TextFieldValue {
+ val ahead by lazy { text.tokenAhead(selection.start) }
+
+ when (tokens) {
+ Token.Operator.plus,
+ Token.Operator.multiply,
+ Token.Operator.divide,
+ Token.Operator.power -> {
+ if (ahead == Token.Operator.plus) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.minus) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.multiply) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.divide) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.sqrt) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.power) return deleteAheadAndAdd(tokens)
+ if (ahead == "") return deleteTokens()
+ }
+ Token.Operator.minus -> {
+ if (ahead == Token.Operator.plus) return deleteAheadAndAdd(tokens)
+ if (ahead == Token.Operator.minus) return deleteAheadAndAdd(tokens)
+ }
+ Token.Digit.dot -> {
+ if (ahead == Token.Digit.dot) return deleteAheadAndAdd(tokens)
+ }
+ }
+
return this.copy(
text = text.replaceRange(selection.start, selection.end, tokens),
selection = TextRange(selection.start + tokens.length)
)
}
+/**
+ * !!! Recursive !!! (one wrong step and you are dead 💀)
+ */
+private fun TextFieldValue.deleteAheadAndAdd(tokens: String): TextFieldValue {
+ var newValue = this
+ if (!selection.collapsed) newValue = this.deleteTokens()
+ return newValue
+ .deleteTokens()
+ .addTokens(tokens)
+}
+
fun TextFieldValue.deleteTokens(): TextFieldValue {
val distanceFromEnd = text.length - selection.end
diff --git a/core/ui/src/test/java/com/sadellie/unitto/core/ui/TextFieldValueExtensionsTest.kt b/core/ui/src/test/java/com/sadellie/unitto/core/ui/TextFieldValueExtensionsTest.kt
new file mode 100644
index 00000000..4c419be3
--- /dev/null
+++ b/core/ui/src/test/java/com/sadellie/unitto/core/ui/TextFieldValueExtensionsTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Unitto is a unit converter for Android
+ * Copyright (c) 2023 Elshan Agaev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sadellie.unitto.core.ui
+
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import com.sadellie.unitto.core.base.Token
+import com.sadellie.unitto.core.ui.common.textfield.addTokens
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class TextFieldValueExtensionsTest {
+
+ @Test
+ fun addPlus() {
+ // EMPTY
+ assertEquals(tf("[]123+456"), tf("[]123+456").addTokens(Token.Operator.plus))
+ assertEquals(tf("[]+456"), tf("[123]+456").addTokens(Token.Operator.plus))
+ assertEquals(tf("[]"), tf("[123+456]").addTokens(Token.Operator.plus))
+
+ // PLUS
+ assertEquals(tf("123+[]"), tf("123+[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[+]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123+[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123+[456]").addTokens(Token.Operator.plus))
+
+ // MINUS
+ assertEquals(tf("123+[]"), tf("123-[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[-]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123-[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123-[456]").addTokens(Token.Operator.plus))
+
+ // MULTIPLY
+ assertEquals(tf("123+[]"), tf("123*[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[*]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123*[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123*[456]").addTokens(Token.Operator.plus))
+
+ // DIVIDE
+ assertEquals(tf("123+[]"), tf("123/[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[/]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123/[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123/[456]").addTokens(Token.Operator.plus))
+
+ // SQRT
+ assertEquals(tf("123+[]"), tf("123√[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[√]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123√[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123√[456]").addTokens(Token.Operator.plus))
+
+ // POWER
+ assertEquals(tf("123+[]"), tf("123^[]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123[^]").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]56"), tf("123^[4]56").addTokens(Token.Operator.plus))
+ assertEquals(tf("123+[]"), tf("123^[456]").addTokens(Token.Operator.plus))
+ }
+
+ @Test
+ fun addMinus() {
+ // EMPTY
+ assertEquals(tf("-[]123+456"), tf("[]123+456").addTokens(Token.Operator.minus))
+ assertEquals(tf("-[]+456"), tf("[123]+456").addTokens(Token.Operator.minus))
+ assertEquals(tf("-[]"), tf("[123+456]").addTokens(Token.Operator.minus))
+
+ // PLUS
+ assertEquals(tf("123-[]"), tf("123+[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[+]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]56"), tf("123+[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123+[456]").addTokens(Token.Operator.minus))
+
+ // MINUS
+ assertEquals(tf("123-[]"), tf("123-[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[-]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]56"), tf("123-[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123-[456]").addTokens(Token.Operator.minus))
+
+ // MULTIPLY
+ assertEquals(tf("123*-[]"), tf("123*[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[*]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123*-[]56"), tf("123*[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123*-[]"), tf("123*[456]").addTokens(Token.Operator.minus))
+
+ // DIVIDE
+ assertEquals(tf("123/-[]"), tf("123/[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[/]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123/-[]56"), tf("123/[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123/-[]"), tf("123/[456]").addTokens(Token.Operator.minus))
+
+ // SQRT
+ assertEquals(tf("123√-[]"), tf("123√[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[√]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123√-[]56"), tf("123√[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123√-[]"), tf("123√[456]").addTokens(Token.Operator.minus))
+
+ // POWER
+ assertEquals(tf("123^-[]"), tf("123^[]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123-[]"), tf("123[^]").addTokens(Token.Operator.minus))
+ assertEquals(tf("123^-[]56"), tf("123^[4]56").addTokens(Token.Operator.minus))
+ assertEquals(tf("123^-[]"), tf("123^[456]").addTokens(Token.Operator.minus))
+ }
+
+ @Test
+ fun addMultiply() {
+ // EMPTY
+ assertEquals(tf("[]123+456"), tf("[]123+456").addTokens(Token.Operator.multiply))
+ assertEquals(tf("[]+456"), tf("[123]+456").addTokens(Token.Operator.multiply))
+ assertEquals(tf("[]"), tf("[123+456]").addTokens(Token.Operator.multiply))
+
+ // PLUS
+ assertEquals(tf("123*[]"), tf("123+[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[+]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123+[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123+[456]").addTokens(Token.Operator.multiply))
+
+ // MINUS
+ assertEquals(tf("123*[]"), tf("123-[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[-]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123-[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123-[456]").addTokens(Token.Operator.multiply))
+
+ // MULTIPLY
+ assertEquals(tf("123*[]"), tf("123*[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[*]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123*[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123*[456]").addTokens(Token.Operator.multiply))
+
+ // DIVIDE
+ assertEquals(tf("123*[]"), tf("123/[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[/]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123/[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123/[456]").addTokens(Token.Operator.multiply))
+
+ // SQRT
+ assertEquals(tf("123*[]"), tf("123√[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[√]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123√[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123√[456]").addTokens(Token.Operator.multiply))
+
+ // POWER
+ assertEquals(tf("123*[]"), tf("123^[]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123[^]").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]56"), tf("123^[4]56").addTokens(Token.Operator.multiply))
+ assertEquals(tf("123*[]"), tf("123^[456]").addTokens(Token.Operator.multiply))
+ }
+
+ @Test
+ fun addDivide() {
+ // EMPTY
+ assertEquals(tf("[]123+456"), tf("[]123+456").addTokens(Token.Operator.divide))
+ assertEquals(tf("[]+456"), tf("[123]+456").addTokens(Token.Operator.divide))
+ assertEquals(tf("[]"), tf("[123+456]").addTokens(Token.Operator.divide))
+
+ // PLUS
+ assertEquals(tf("123/[]"), tf("123+[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[+]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123+[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123+[456]").addTokens(Token.Operator.divide))
+
+ // MINUS
+ assertEquals(tf("123/[]"), tf("123-[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[-]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123-[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123-[456]").addTokens(Token.Operator.divide))
+
+ // MULTIPLY
+ assertEquals(tf("123/[]"), tf("123*[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[*]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123*[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123*[456]").addTokens(Token.Operator.divide))
+
+ // DIVIDE
+ assertEquals(tf("123/[]"), tf("123/[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[/]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123/[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123/[456]").addTokens(Token.Operator.divide))
+
+ // SQRT
+ assertEquals(tf("123/[]"), tf("123√[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[√]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123√[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123√[456]").addTokens(Token.Operator.divide))
+
+ // POWER
+ assertEquals(tf("123/[]"), tf("123^[]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123[^]").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]56"), tf("123^[4]56").addTokens(Token.Operator.divide))
+ assertEquals(tf("123/[]"), tf("123^[456]").addTokens(Token.Operator.divide))
+ }
+
+ @Test
+ fun addPower() {
+ // EMPTY
+ assertEquals(tf("[]123+456"), tf("[]123+456").addTokens(Token.Operator.power))
+ assertEquals(tf("[]+456"), tf("[123]+456").addTokens(Token.Operator.power))
+ assertEquals(tf("[]"), tf("[123+456]").addTokens(Token.Operator.power))
+
+ // PLUS
+ assertEquals(tf("123^[]"), tf("123+[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[+]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123+[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123+[456]").addTokens(Token.Operator.power))
+
+ // MINUS
+ assertEquals(tf("123^[]"), tf("123-[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[-]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123-[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123-[456]").addTokens(Token.Operator.power))
+
+ // MULTIPLY
+ assertEquals(tf("123^[]"), tf("123*[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[*]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123*[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123*[456]").addTokens(Token.Operator.power))
+
+ // DIVIDE
+ assertEquals(tf("123^[]"), tf("123/[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[/]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123/[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123/[456]").addTokens(Token.Operator.power))
+
+ // SQRT
+ assertEquals(tf("123^[]"), tf("123√[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[√]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123√[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123√[456]").addTokens(Token.Operator.power))
+
+ // POWER
+ assertEquals(tf("123^[]"), tf("123^[]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123[^]").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]56"), tf("123^[4]56").addTokens(Token.Operator.power))
+ assertEquals(tf("123^[]"), tf("123^[456]").addTokens(Token.Operator.power))
+ }
+
+ @Test
+ fun addDot() {
+ // EMPTY
+ assertEquals(tf(".[]123+456"), tf("[]123+456").addTokens(Token.Digit.dot))
+ assertEquals(tf(".[]+456"), tf("[123]+456").addTokens(Token.Digit.dot))
+ assertEquals(tf(".[]"), tf("[123+456]").addTokens(Token.Digit.dot))
+
+ assertEquals(tf("123+456.[]78"), tf("123+456.[]78").addTokens(Token.Digit.dot))
+ assertEquals(tf("123+456.[]78"), tf("123+456[.]78").addTokens(Token.Digit.dot))
+ assertEquals(tf("123+456.[]8"), tf("123+456[.7]8").addTokens(Token.Digit.dot))
+ assertEquals(tf("123+45.[]78"), tf("123+45[6.]78").addTokens(Token.Digit.dot))
+ }
+
+ // Use [] for selection
+ private fun tf(
+ text: String = "",
+ ): TextFieldValue {
+ val selectionStart = text.indexOf("[")
+ val selectionEnd = text.indexOf("]") - 1
+
+ return TextFieldValue(
+ text = text
+ .replace("[", "")
+ .replace("]", "")
+ .replace("-", Token.Operator.minus)
+ .replace("/", Token.Operator.divide)
+ .replace("*", Token.Operator.multiply),
+ selection = TextRange(selectionStart, selectionEnd)
+ )
+ }
+}