fix: Improve smart delete behavior

Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com>
This commit is contained in:
Myzel394 2024-09-03 21:25:13 +02:00
parent 6d535a9adf
commit 7045f438b5
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
2 changed files with 159 additions and 42 deletions

View File

@ -27,35 +27,47 @@ data class SmartDeleteHandler(
val position = selection.start val position = selection.start
val rightBracketPosition = when (position) {
findRightBracket(position); 0 -> return TextRange(0, 0)
val leftBracketPosition = 1 -> return TextRange(0, 1)
findLeftBracket(position) }
?: return TextRange(
rightBracketPosition?.let { takeNextIfIsOperator(it) }
?.let { if (it > position) 0 else it } ?: 0,
position,
);
if (rightBracketPosition == null) { val bracketPos = findPreviousBracket(position.coerceAtMost(value.length - 1) - 1)
val rightBracketRelativeToLeftPosition = findRightBracket(leftBracketPosition + 1)
return if (rightBracketRelativeToLeftPosition == null) { if (bracketPos == null) {
// 1+2(+5|+6 return TextRange(0, position)
TextRange((leftBracketPosition + 1).coerceAtMost(position), position) }
val isAtLeftEdge =
position - 1 == bracketPos && value[bracketPos] == Token.Operator.leftBracket[0]
val isAtRightEdge =
position - 1 == bracketPos && value[bracketPos] == Token.Operator.rightBracket[0]
if (!isAtLeftEdge && !isAtRightEdge) {
return TextRange(bracketPos + 1, position)
}
if (isAtRightEdge) {
val leftBracketPos = findClosingParenBackwards(bracketPos)
return if (leftBracketPos != null) {
TextRange(leftBracketPos + 1, position)
} else { } else {
// 1+2(6)+5|+6 // Weird case, should not happen
TextRange( TextRange(0, position + 1)
takeNextIfIsOperator(rightBracketRelativeToLeftPosition + 1).coerceAtMost(
position,
),
position,
)
} }
} }
val end = findClosingParen(leftBracketPosition) val rightBracketPos = findClosingParen(bracketPos)
return TextRange((leftBracketPosition + 1).coerceAtMost(end), end);
if (rightBracketPos != null) {
return TextRange(bracketPos + 1, rightBracketPos)
}
// Find previous bracket and return range from there to cursor position
val previousBracketPos = findPreviousBracket(bracketPos - 1)?.let { it + 1 } ?: 0
return TextRange(previousBracketPos, position)
} }
private fun takeNextIfIsOperator(position: Int): Int { private fun takeNextIfIsOperator(position: Int): Int {
@ -68,6 +80,16 @@ data class SmartDeleteHandler(
private fun isSelectionARange(): Boolean = selection.start != selection.end private fun isSelectionARange(): Boolean = selection.start != selection.end
private fun findPreviousBracket(startPosition: Int): Int? {
for (index in startPosition.coerceAtMost(value.length - 1) downTo 0) {
if (value[index] == Token.Operator.rightBracket[0] || value[index] == Token.Operator.leftBracket[0]) {
return index
}
}
return null
}
private fun findLeftBracket(startPosition: Int): Int? { private fun findLeftBracket(startPosition: Int): Int? {
for (index in startPosition.coerceAtMost(value.length - 1) downTo 0) { for (index in startPosition.coerceAtMost(value.length - 1) downTo 0) {
if (value[index] == Token.Operator.leftBracket[0]) { if (value[index] == Token.Operator.leftBracket[0]) {
@ -79,7 +101,7 @@ data class SmartDeleteHandler(
} }
private fun findRightBracket(startPosition: Int): Int? { private fun findRightBracket(startPosition: Int): Int? {
for (index in startPosition.coerceAtMost(value.length - 1) until value.length) { for (index in startPosition.coerceAtMost(value.length - 1) downTo 0) {
if (value[index] == Token.Operator.rightBracket[0]) { if (value[index] == Token.Operator.rightBracket[0]) {
return index return index
} }
@ -88,19 +110,60 @@ data class SmartDeleteHandler(
return null return null
} }
private fun isAtEdge(position: Int): Boolean {
if (position == 0) {
return false
}
val previousCharacter = value[position.coerceAtMost(value.length - 1) - 1]
return previousCharacter == Token.Operator.leftBracket[0] || previousCharacter == Token.Operator.rightBracket[0]
}
// Based of https://stackoverflow.com/a/12752226/9878135 // Based of https://stackoverflow.com/a/12752226/9878135
fun findClosingParen(openPos: Int): Int { fun findClosingParen(openPos: Int): Int? {
var closePos = openPos var closePos = openPos
var counter = 1 var counter = 1
while (counter > 0) { while (counter > 0) {
val c = value[++closePos] val nextPos = ++closePos
if (nextPos >= value.length) {
return null
}
val c = value[nextPos]
if (c == Token.Operator.leftBracket[0]) { if (c == Token.Operator.leftBracket[0]) {
counter++ counter++
} else if (c == Token.Operator.rightBracket[0]) { } else if (c == Token.Operator.rightBracket[0]) {
counter-- counter--
} }
} }
if (closePos == openPos) {
return null
}
return closePos
}
fun findClosingParenBackwards(openPos: Int): Int? {
var closePos = openPos
var counter = 1
while (counter > 0) {
val c = value[--closePos]
if (c == Token.Operator.leftBracket[0]) {
counter--
} else if (c == Token.Operator.rightBracket[0]) {
counter++
}
}
if (closePos == openPos) {
return null
}
return closePos return closePos
} }
} }

View File

@ -19,7 +19,7 @@ class SmartDeleteHandlerTest {
assertDeleteRange( assertDeleteRange(
"1+(2+3)", "1+(2+3)",
TextRange(4, 4), TextRange(4, 4),
TextRange(3, 6), TextRange(3, 4),
) )
} }
@ -64,7 +64,7 @@ class SmartDeleteHandlerTest {
assertDeleteRange( assertDeleteRange(
"1+(2+3)×54", "1+(2+3)×54",
TextRange(9, 9), TextRange(9, 9),
TextRange(8, 9), TextRange(7, 9),
) )
} }
@ -73,7 +73,7 @@ class SmartDeleteHandlerTest {
assertDeleteRange( assertDeleteRange(
"1+(2+(3+4))", "1+(2+(3+4))",
TextRange(8, 8), TextRange(8, 8),
TextRange(6, 9), TextRange(6, 8),
) )
} }
@ -82,6 +82,15 @@ class SmartDeleteHandlerTest {
assertDeleteRange( assertDeleteRange(
"1+(2+(3+4))", "1+(2+(3+4))",
TextRange(4, 4), TextRange(4, 4),
TextRange(3, 4),
)
}
@Test
fun `test nested brackets, inside the outside one, at edge`() {
assertDeleteRange(
"1+(2+(3+4))",
TextRange(3, 3),
TextRange(3, 10), TextRange(3, 10),
) )
} }
@ -89,18 +98,9 @@ class SmartDeleteHandlerTest {
@Test @Test
fun `test nested empty brackets`() { fun `test nested empty brackets`() {
assertDeleteRange( assertDeleteRange(
"1+(2+())", "1+(2+(6))",
TextRange(4, 4), TextRange(3, 3),
TextRange(3, 7), TextRange(3, 8),
)
}
@Test
fun `test pure empty nested brackets`() {
assertDeleteRange(
"1+((()))",
TextRange(5, 5),
TextRange(5, 5),
) )
} }
@ -122,6 +122,24 @@ class SmartDeleteHandlerTest {
) )
} }
@Test
fun `test right bracket cursor after it`() {
assertDeleteRange(
"5+(2+6)+4",
TextRange(8, 8),
TextRange(7, 8),
)
}
@Test
fun `test right bracket cursor at edge`() {
assertDeleteRange(
"5+(2+6)+4",
TextRange(7, 7),
TextRange(3, 7),
)
}
@Test @Test
fun `test single bracket with cursor at 0,0`() { fun `test single bracket with cursor at 0,0`() {
assertDeleteRange( assertDeleteRange(
@ -135,7 +153,7 @@ class SmartDeleteHandlerTest {
fun `test nested brackets with operators`() { fun `test nested brackets with operators`() {
assertDeleteRange( assertDeleteRange(
"1+(2*(3+4))", "1+(2*(3+4))",
TextRange(8, 8), TextRange(6, 6),
TextRange(6, 9), TextRange(6, 9),
) )
} }
@ -149,6 +167,42 @@ class SmartDeleteHandlerTest {
) )
} }
@Test
fun `test empty brackets deletes rest`() {
assertDeleteRange(
"5+(",
TextRange(3, 3),
TextRange(0, 3),
)
}
@Test
fun `left bracket, no right bracket`() {
assertDeleteRange(
"1+(5+6+",
TextRange(8, 8),
TextRange(3, 8),
)
}
@Test
fun `no bracket`() {
assertDeleteRange(
"1+5+6",
TextRange(5, 5),
TextRange(0, 5),
)
}
@Test
fun `closed bracket before, but left bracket after`() {
assertDeleteRange(
"1+(5+6)+(5+",
TextRange(11, 11),
TextRange(9, 11),
)
}
private fun assertDeleteRange(input: String, selection: TextRange, expected: TextRange) { private fun assertDeleteRange(input: String, selection: TextRange, expected: TextRange) {
val smartDeleteHandler = val smartDeleteHandler =