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) + ) + } +}