Added delegation support: Delegate, lazy, "by" keyword for valr/var/fun and object {} singletons

This commit is contained in:
sergeych committed 2026-01-05 19:05:16 +01:00
1 parent 8e766490d9
commit 5f819dc87a
20 files changed
+1183 -754

No files matched your search

@@ -1,5 +1,5 @@
/*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -318,6 +318,21 @@ fun EditorWithOverlay(
try { ta.setSelectionRange(res.selStart, res.selEnd) } catch (_: Throwable) {}
pendingSelStart = res.selStart
pendingSelEnd = res.selEnd
} else if (key.length == 1 && !ev.ctrlKey && !ev.metaKey && !ev.altKey) {
// Handle single character input (like '}') for dedenting
// This is an alternative to onInput to have better control
val start = ta.selectionStart ?: 0
val end = ta.selectionEnd ?: start
val current = ta.value
val res = applyChar(current, start, end, key[0], tabSize)
if (res.text != (current.substring(0, start) + key + current.substring(end))) {
// Logic decided to change something else (e.g. dedent)
ev.preventDefault()
setCode(res.text)
try { ta.setSelectionRange(res.selStart, res.selEnd) } catch (_: Throwable) {}
pendingSelStart = res.selStart
pendingSelEnd = res.selEnd
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,55 +83,6 @@ private fun nextNonWs(text: String, idxInclusive: Int): Int {
/** Apply Enter key behavior with smart indent/undent rules. */
fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResult {
// Global early rule (Rule 3): if the line after the current line is brace-only, dedent that line by one block and
// do NOT insert a newline. This uses precise line boundaries.
run {
val start = minOf(selStart, text.length)
val eol = lineEndAt(text, start)
val nextLineStart = if (eol < text.length && text[eol] == '\n') eol + 1 else eol
val nextLineEnd = lineEndAt(text, nextLineStart)
if (nextLineStart <= nextLineEnd && nextLineStart < text.length) {
val trimmedNext = text.substring(nextLineStart, nextLineEnd).trim()
if (trimmedNext == "}") {
val rbraceIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (nextLineStart < text.length && text[nextLineStart] == '\r') 1 else 0
val out = buildString(text.length) {
append(safeSubstring(text, 0, nextLineStart))
append(safeSubstring(text, nextLineStart + crShift + removeCount, text.length))
}
val caret = nextLineStart + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
}
// Absolute top-priority: caret is exactly at a line break and the next line is '}'-only (ignoring spaces).
// Dedent that next line by one block (tabSize) and do NOT insert a newline.
run {
if (selStart < text.length) {
val isCrLf = selStart + 1 < text.length && text[selStart] == '\r' && text[selStart + 1] == '\n'
val isLf = text[selStart] == '\n'
if (isCrLf || isLf) {
val nextLineStart = selStart + if (isCrLf) 2 else 1
val nextLineEnd = lineEndAt(text, nextLineStart)
if (nextLineStart <= nextLineEnd) {
val trimmed = text.substring(nextLineStart, nextLineEnd).trim()
if (trimmed == "}") {
val rbraceIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (nextLineStart < text.length && text[nextLineStart] == '\r') 1 else 0
val out = buildString(text.length) {
append(safeSubstring(text, 0, nextLineStart))
append(safeSubstring(text, nextLineStart + crShift + removeCount, text.length))
}
val caret = nextLineStart + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
}
}
}
// If there is a selection, replace it by newline + current line indent
if (selEnd != selStart) {
val lineStart = lineStartAt(text, selStart)
@@ -148,8 +99,6 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
val lineEnd = lineEndAt(text, start)
val indent = countIndentSpaces(text, lineStart, lineEnd)
// (Handled by the global early rule above; no need for additional EOL variants.)
// Compute neighborhood characters early so rule precedence can use them
val prevIdx = prevNonWs(text, start)
val nextIdx = nextNonWs(text, start)
@@ -158,85 +107,6 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
val before = text.substring(0, start)
val after = text.substring(start)
// Rule 2: On a brace-only line '}' (caret on the same line)
// If the current line’s trimmed text is exactly '}', decrease that line’s indent by one block (not below 0),
// then insert a newline. The newly inserted line uses the (decreased) indent. Place the caret at the start of
// the newly inserted line.
run {
val trimmed = text.substring(lineStart, lineEnd).trim()
// IMPORTANT: Rule precedence — do NOT trigger this rule when caret is after '}' and the rest of the line
// up to EOL contains only spaces (Rule 5 handles that case). That scenario must insert AFTER the brace,
// not before it.
var onlySpacesAfterCaret = true
var k = start
while (k < lineEnd) { if (text[k] != ' ') { onlySpacesAfterCaret = false; break }; k++ }
val rule5Situation = (prevCh == '}') && onlySpacesAfterCaret
if (trimmed == "}" && !rule5Situation) {
val removeCount = kotlin.math.min(tabSize, indent)
val newIndent = (indent - removeCount).coerceAtLeast(0)
val crShift = if (lineStart < text.length && text[lineStart] == '\r') 1 else 0
val out = buildString(text.length + 1 + newIndent) {
append(safeSubstring(text, 0, lineStart))
append("\n")
append(" ".repeat(newIndent))
// Write the brace line but with its indent reduced by removeCount spaces
append(safeSubstring(text, lineStart + crShift + removeCount, text.length))
}
val caret = lineStart + 1 + newIndent
return EditResult(out, caret, caret)
}
}
// (The special case of caret after the last non-ws on a '}'-only line is covered by the rule above.)
// 0) Caret is at end-of-line and the next line is a closing brace-only line: dedent that line, no extra newline
run {
val atCr = start + 1 < text.length && text[start] == '\r' && text[start + 1] == '\n'
val atNl = start < text.length && text[start] == '\n'
val atEol = atNl || atCr
if (atEol) {
val nlAdvance = if (atCr) 2 else 1
val nextLineStart = start + nlAdvance
val nextLineEnd = lineEndAt(text, nextLineStart)
val trimmedNext = text.substring(nextLineStart, nextLineEnd).trim()
if (trimmedNext == "}") {
val rbraceIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
// Dedent the '}' line by one block level (tabSize), but not below column 0
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (nextLineStart < text.length && text[nextLineStart] == '\r') 1 else 0
val out = buildString(text.length) {
append(safeSubstring(text, 0, nextLineStart))
append(safeSubstring(text, nextLineStart + crShift + removeCount, text.length))
}
val caret = start + nlAdvance + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
}
// 0b) If there is a newline at or after caret and the next line starts (ignoring spaces) with '}',
// dedent that '}' line without inserting an extra newline.
run {
val nlPos = text.indexOf('\n', start)
if (nlPos >= 0) {
val nextLineStart = nlPos + 1
val nextLineEnd = lineEndAt(text, nextLineStart)
val nextLineFirstNonWs = nextNonWs(text, nextLineStart)
if (nextLineFirstNonWs in nextLineStart until nextLineEnd && text[nextLineFirstNonWs] == '}') {
val rbraceIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (nextLineStart < text.length && text[nextLineStart] == '\r') 1 else 0
val out = buildString(text.length) {
append(safeSubstring(text, 0, nextLineStart))
append(safeSubstring(text, nextLineStart + crShift + removeCount, text.length))
}
val caret = nextLineStart + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
}
// 1) Between braces { | } -> two lines, inner indented
if (prevCh == '{' && nextCh == '}') {
val innerIndent = indent + tabSize
@@ -254,119 +124,17 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
}
// 3) Before '}'
if (nextCh == '}') {
// We want two things:
// - reduce indentation of the upcoming '}' line by one level
// - avoid creating an extra blank line if caret is already at EOL (the next char is a newline)
// Compute where the '}' line starts and how many leading spaces it has
val rbraceLineStart = lineStartAt(text, nextIdx)
val rbraceLineEnd = lineEndAt(text, nextIdx)
val rbraceIndent = countIndentSpaces(text, rbraceLineStart, rbraceLineEnd)
// Dedent the '}' line by one block level (tabSize), but not below column 0
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (rbraceLineStart < text.length && text[rbraceLineStart] == '\r') 1 else 0
// If there is already a newline between caret and the '}', do NOT insert another newline.
// Just dedent the existing '}' line by one block and place caret at its start.
run {
val nlBetween = text.indexOf('\n', start)
if (nlBetween in start until rbraceLineStart) {
val out = buildString(text.length) {
append(safeSubstring(text, 0, rbraceLineStart))
append(safeSubstring(text, rbraceLineStart + crShift + removeCount, text.length))
}
val caret = rbraceLineStart + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
val hasNewlineAtCaret = (start < text.length && text[start] == '\n')
// New indentation for the line we create (if we actually insert one now)
val newLineIndent = (indent - tabSize).coerceAtLeast(0)
val insertion = if (hasNewlineAtCaret) "" else "\n" + " ".repeat(newLineIndent)
val out = buildString(text.length + insertion.length) {
append(before)
append(insertion)
// keep text up to the start of '}' line
append(safeSubstring(text, start, rbraceLineStart))
// drop up to tabSize spaces before '}'
append(safeSubstring(text, rbraceLineStart + crShift + removeCount, text.length))
}
val caret = if (hasNewlineAtCaret) {
// Caret moves to the beginning of the '}' line after dedent (right after the single newline)
start + 1 + kotlin.math.max(0, rbraceIndent - removeCount)
} else {
start + insertion.length
}
return EditResult(out, caret, caret)
}
// 4) After '}' with only trailing spaces before EOL
// According to Rule 5: if the last non-whitespace before the caret is '}' and
// only spaces remain until EOL, we must:
// - dedent the current (brace) line by one block (not below 0)
// - insert a newline just AFTER '}' (do NOT move caret backward)
// - set the caret at the start of the newly inserted blank line, whose indent equals the dedented indent
if (prevCh == '}') {
var onlySpaces = true
var k = prevIdx + 1
while (k < lineEnd) { if (text[k] != ' ') { onlySpaces = false; break }; k++ }
if (onlySpaces) {
val removeCount = kotlin.math.min(tabSize, indent)
val newIndent = (indent - removeCount).coerceAtLeast(0)
val crShift = if (lineStart < text.length && text[lineStart] == '\r') 1 else 0
// Build the result:
// - keep everything before the line start
// - write the current line content up to the caret, but with its left indent reduced by removeCount
// - insert newline + spaces(newIndent)
// - drop trailing spaces after caret up to EOL
// - keep the rest of the text starting from EOL
val out = buildString(text.length) {
append(safeSubstring(text, 0, lineStart))
append(safeSubstring(text, lineStart + crShift + removeCount, start))
append("\n")
append(" ".repeat(newIndent))
append(safeSubstring(text, lineEnd, text.length))
}
val caret = (lineStart + (start - (lineStart + crShift + removeCount)) + 1 + newIndent)
return EditResult(out, caret, caret)
} else {
// Default smart indent for cases where there are non-space characters after '}'
val insertion = "\n" + " ".repeat(indent)
val out = before + insertion + after
val caret = start + insertion.length
return EditResult(out, caret, caret)
}
}
// 5) Fallback: if there is a newline ahead and the next line, trimmed, equals '}', dedent that '}' line by one block
run {
val nlPos = text.indexOf('\n', start)
if (nlPos >= 0) {
val nextLineStart = nlPos + 1
val nextLineEnd = lineEndAt(text, nextLineStart)
val trimmedNext = text.substring(nextLineStart, nextLineEnd).trim()
if (trimmedNext == "}") {
val rbraceIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
val removeCount = kotlin.math.min(tabSize, rbraceIndent)
val crShift = if (nextLineStart < text.length && text[nextLineStart] == '\r') 1 else 0
val out = buildString(text.length) {
append(safeSubstring(text, 0, nextLineStart))
append(safeSubstring(text, nextLineStart + crShift + removeCount, text.length))
}
val caret = nextLineStart + kotlin.math.max(0, rbraceIndent - removeCount)
return EditResult(out, caret, caret)
}
}
}
// default keep same indent
run {
val insertion = "\n" + " ".repeat(indent)
val out = before + insertion + after
val caret = start + insertion.length
return EditResult(out, caret, caret)
}
// default keep same indent
val insertion = "\n" + " ".repeat(indent)
val out = before + insertion + after
val caret = start + insertion.length
return EditResult(out, caret, caret)
}
/** Apply Tab key: insert spaces at caret (single-caret only). */
@@ -423,3 +191,37 @@ fun applyShiftTab(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditR
val e = maxOf(newSelStart, newSelEnd)
return EditResult(sb.toString(), s, e)
}
/**
* Apply a typed character. If the character is '}', and it's the only non-whitespace on the line,
* it may be dedented.
*/
fun applyChar(text: String, selStart: Int, selEnd: Int, ch: Char, tabSize: Int): EditResult {
// Selection replacement
val current = if (selStart != selEnd) {
text.substring(0, minOf(selStart, selEnd)) + text.substring(maxOf(selStart, selEnd))
} else text
val pos = minOf(selStart, selEnd)
val before = current.substring(0, pos)
val after = current.substring(pos)
val newText = before + ch + after
val newPos = pos + 1
if (ch == '}') {
val lineStart = lineStartAt(newText, pos)
val lineEnd = lineEndAt(newText, newPos)
val trimmed = newText.substring(lineStart, lineEnd).trim()
if (trimmed == "}") {
// Dedent this line
val indent = countIndentSpaces(newText, lineStart, lineEnd)
val removeCount = minOf(tabSize, indent)
if (removeCount > 0) {
val out = newText.substring(0, lineStart) + newText.substring(lineStart + removeCount)
return EditResult(out, newPos - removeCount, newPos - removeCount)
}
}
}
return EditResult(newText, newPos, newPos)
}
+36 -282
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,81 +25,19 @@ class EditorLogicTest {
private val tab = 4
@Test
fun enter_after_only_rbrace_undents() {
val line = " } " // 4 spaces, brace, trailing spaces; caret after last non-ws
val res = applyEnter(line, line.length, line.length, tab)
// Should insert newline with one indent level less (0 spaces here)
assertEquals(" } \n" + "" , res.text.substring(0, res.text.indexOf('\n')+1))
// After insertion caret should be at end of inserted indentation
// Here indent was 4 and undented by 4 -> 0 spaces after newline
val expectedCaret = line.length + 1 + 0
assertEquals(expectedCaret, res.selStart)
assertEquals(expectedCaret, res.selEnd)
fun type_rbrace_dedents_if_only_char_on_line() {
val text = " "
val res = applyChar(text, 4, 4, '}', tab)
assertEquals("}", res.text)
assertEquals(1, res.selStart)
}
@Test
fun enter_after_rbrace_with_only_spaces_to_eol_inserts_after_brace_and_dedents_and_undents_brace_line() {
// Rule 5 exact check: last non-ws before caret is '}', remainder to EOL only spaces
val indents = listOf(0, 4, 8)
for (indent in indents) {
val spaces = " ".repeat(indent)
// Line ends with '}' followed by three spaces
val before = (
"""
1
${'$'}spaces}
"""
).trimIndent() + " "
// Caret right after '}', before trailing spaces
val caret = before.indexOf('}') + 1
val res = applyEnter(before, caret, caret, tab)
val newBraceIndent = (indent - tab).coerceAtLeast(0)
val newLineIndent = newBraceIndent
val expected = buildString {
append("1\n")
append(" ".repeat(newBraceIndent))
append("}\n")
append(" ".repeat(newLineIndent))
}
assertEquals(expected, res.text)
// Caret must be at start of the newly inserted line (after the final newline)
assertEquals(expected.length, res.selStart)
assertEquals(res.selStart, res.selEnd)
}
}
@Test
fun enter_after_rbrace_with_only_spaces_to_eol_crlf_and_undents_brace_line() {
val indent = 4
val spaces = " ".repeat(indent)
val beforeLf = (
"""
1
${'$'}spaces}
"""
).trimIndent() + " "
val before = beforeLf.replace("\n", "\r\n")
val caret = before.indexOf('}') + 1
val res = applyEnter(before, caret, caret, tab)
val actual = res.text.replace("\r\n", "\n")
val newIndent = (indent - tab).coerceAtLeast(0)
val expected = "1\n${" ".repeat(newIndent)}}\n${" ".repeat(newIndent)}"
assertEquals(expected, actual)
assertEquals(expected.length, res.selStart)
assertEquals(res.selStart, res.selEnd)
}
@Test
fun enter_before_closing_brace_outdents() {
val text = " }" // caret before '}' at index 4
val caret = 4
val res = applyEnter(text, caret, caret, tab)
// Inserted a newline with indent reduced to 0
assertEquals("\n" + "" + "}", res.text.substring(caret, caret + 2))
val expectedCaret = caret + 1 + 0
assertEquals(expectedCaret, res.selStart)
assertEquals(expectedCaret, res.selEnd)
fun type_rbrace_no_dedent_if_not_only_char() {
val text = " foo "
val res = applyChar(text, 8, 8, '}', tab)
assertEquals(" foo }", res.text)
assertEquals(9, res.selStart)
}
@Test
@@ -123,23 +61,6 @@ class EditorLogicTest {
assertEquals(1 + 1 + 4, res.selStart)
}
@Test
fun enter_after_rbrace_line_undents_ignoring_trailing_ws() {
// Line contains only '}' plus trailing spaces; caret after last non-ws
val line = " } "
val res = applyEnter(line, line.length, line.length, tab)
// Expect insertion of a newline and an undented indentation of (indent - tab)
val expectedIndentAfterNewline = 0 // 4 - 4
val expected = line + "\n" + " ".repeat(expectedIndentAfterNewline)
// Compare prefix up to inserted indentation
val prefix = res.text.substring(0, expected.length)
assertEquals(expected, prefix)
// Caret positioned at end of inserted indentation
val expectedCaret = expected.length
assertEquals(expectedCaret, res.selStart)
assertEquals(expectedCaret, res.selEnd)
}
@Test
fun shift_tab_outdents_rbrace_only_line_no_newline() {
// Multi-line: a block followed by a number; we outdent the '}' line only
@@ -171,222 +92,55 @@ class EditorLogicTest {
assertEquals(1 + 4, res.selStart)
}
/*
@Test
fun enter_before_line_with_only_rbrace_dedents_that_line() {
// Initial content as reported by user before fix
val before = (
"""
{
1
2
3
}
1
2
3
"""
).trimIndent()
// Place caret at the end of the line that contains just " 3" (before the line with '}')
val lines = before.split('\n')
// Build index of end of the line with the last " 3"
var idx = 0
var caret = 0
for (i in lines.indices) {
val line = lines[i]
if (line.trimEnd() == "3" && i + 1 < lines.size && lines[i + 1].trim() == "}") {
caret = idx + line.length // position after '3', before the newline
break
}
idx += line.length + 1 // +1 for newline
}
val res = applyEnter(before, caret, caret, tab)
val expected = (
"""
{
1
2
3
}
1
2
3
"""
).trimIndent()
assertEquals(expected, res.text)
}
...
*/
/*
@Test
fun enter_eol_before_brace_only_next_line_various_indents() {
// Cover Rule 3 with LF newlines, at indents 0, 2, 4, 8
val indents = listOf(0, 2, 4, 8)
for (indent in indents) {
val spaces = " ".repeat(indent)
val before = (
"""
1
2
3
${'$'}spaces}
4
"""
).trimIndent()
// Caret at end of the line with '3' (line before the rbrace-only line)
val caret = before.indexOf("3\n") + 1 // just before LF
val res = applyEnter(before, caret, caret, tab)
// The '}' line must be dedented by one block (clamped at 0) and caret moved to its start
val expectedIndent = (indent - tab).coerceAtLeast(0)
val expected = (
"""
1
2
3
${'$'}{" ".repeat(expectedIndent)}}
4
"""
).trimIndent()
assertEquals(expected, res.text, "EOL before '}' dedent failed for indent=${'$'}indent")
// Caret should be at start of that '}' line (line index 3)
val lines = res.text.split('\n')
var pos = 0
for (i in 0 until 3) pos += lines[i].length + 1
assertEquals(pos, res.selStart, "Caret pos mismatch for indent=${'$'}indent")
assertEquals(pos, res.selEnd, "Caret pos mismatch for indent=${'$'}indent")
}
}
...
*/
/*
@Test
fun enter_eol_before_brace_only_next_line_various_indents_crlf() {
// Same as above but with CRLF newlines
val indents = listOf(0, 2, 4, 8)
for (indent in indents) {
val spaces = " ".repeat(indent)
val beforeLf = (
"""
1
2
3
${'$'}spaces}
4
"""
).trimIndent()
val before = beforeLf.replace("\n", "\r\n")
val caret = before.indexOf("3\r\n") + 1 // at '3' index + 1 moves to end-of-line before CR
val res = applyEnter(before, caret, caret, tab)
val expectedIndent = (indent - tab).coerceAtLeast(0)
val expectedLf = (
"""
1
2
3
${'$'}{" ".repeat(expectedIndent)}}
4
"""
).trimIndent()
assertEquals(expectedLf, res.text.replace("\r\n", "\n"), "CRLF case failed for indent=${'$'}indent")
}
}
...
*/
/*
@Test
fun enter_at_start_of_brace_only_line_at_cols_0_2_4() {
val indents = listOf(0, 2, 4)
for (indent in indents) {
val spaces = " ".repeat(indent)
val before = (
"""
1
2
${'$'}spaces}
3
"""
).trimIndent()
// Caret at start of the brace line
val lines = before.split('\n')
var caret = 0
for (i in 0 until 2) caret += lines[i].length + 1
caret += 0 // column 0 of brace line
val res = applyEnter(before, caret, caret, tab)
// Expect the brace line to be dedented by one block, and a new line inserted before it
val expectedIndent = (indent - tab).coerceAtLeast(0)
val expected = (
"""
1
2
${'$'}{" ".repeat(expectedIndent)}
${'$'}{" ".repeat(expectedIndent)}}
3
"""
).trimIndent()
assertEquals(expected, res.text, "Brace-line start enter failed for indent=${'$'}indent")
// Caret must be at start of the inserted line, which has expectedIndent spaces
val afterLines = res.text.split('\n')
var pos = 0
for (i in 0 until 3) pos += afterLines[i].length + 1
// The inserted line is line index 2 (0-based), caret at its start
pos -= afterLines[2].length + 1
pos += 0
assertEquals(pos, res.selStart, "Caret mismatch for indent=${'$'}indent")
assertEquals(pos, res.selEnd, "Caret mismatch for indent=${'$'}indent")
}
}
...
*/
@Test
fun enter_on_whitespace_only_line_keeps_same_indent() {
val before = " \nnext" // line 0 has 4 spaces only
val caret = 0 + 4 // at end of spaces, before LF
val caret = 4 // at end of spaces, before LF
val res = applyEnter(before, caret, caret, tab)
// Default smart indent should keep indent = 4
// Original text: ' ' + '\n' + 'next'
// Inserted: '\n' + ' ' at caret 4
// Result: ' ' + '\n' + ' ' + '\n' + 'next'
assertEquals(" \n \nnext", res.text)
// Caret at start of the new blank line with 4 spaces
assertEquals(1 + 4, res.selStart)
// Caret at start of the new blank line with 4 spaces (after first newline)
// lineStart(0) + indent(4) + newline(1) + newIndent(4) = 9
assertEquals(4 + 1 + 4, res.selStart)
assertEquals(res.selStart, res.selEnd)
}
/*
@Test
fun enter_on_line_with_rbrace_else_lbrace_defaults_smart() {
val text = " } else {"
// Try caret positions after '}', before 'e', and after '{'
val carets = listOf(5, 6, text.length)
for (c in carets) {
val res = applyEnter(text, c, c, tab)
// Should not trigger special cases since line is not brace-only or only-spaces after '}'
// Expect same indent (4 spaces)
val expectedPrefix = text.substring(0, c) + "\n" + " ".repeat(4)
assertEquals(expectedPrefix, res.text.substring(0, expectedPrefix.length))
assertEquals(c + 1 + 4, res.selStart)
assertEquals(res.selStart, res.selEnd)
}
}
...
*/
/*
@Test
fun enter_with_selection_replaces_and_uses_anchor_indent() {
val text = (
"""
1
2
3
"""
).trimIndent()
// Select "2\n3" starting at column 4 of line 1 (indent = 4)
val idxLine0 = text.indexOf('1')
val idxLine1 = text.indexOf('\n', idxLine0) + 1
val selStart = idxLine1 + 4 // after 4 spaces
val selEnd = text.length
val res = applyEnter(text, selStart, selEnd, tab)
val expected = (
"""
1
"""
).trimIndent()
assertEquals(expected, res.text)
assertEquals(expected.length, res.selStart)
assertEquals(res.selStart, res.selEnd)
}
...
*/
}