fix: added multi line delimiter support
This commit is contained in:
parent
21ed76bed5
commit
9960d5c4e2
@ -706,3 +706,274 @@ func TestTextObjectEdgeCases(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Multi-line Delimiter Tests
|
||||
// ============================================================================
|
||||
|
||||
func TestTextObjectMultiLineDelimiters(t *testing.T) {
|
||||
t.Run("test 'di{' on multi-line braces", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"func test() {",
|
||||
" body",
|
||||
"}",
|
||||
}
|
||||
// Cursor on "body"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"func test() {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'da{' on multi-line braces", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"func test() {",
|
||||
" body",
|
||||
"}",
|
||||
}
|
||||
// Cursor on "body"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
||||
sendKeys(tm, "d", "a", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"func test() ",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'vi(' on multi-line parentheses", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"function(",
|
||||
" arg1,",
|
||||
" arg2",
|
||||
")",
|
||||
}
|
||||
// Cursor on "arg1"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
||||
sendKeys(tm, "v", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// Should select from after '(' to before ')'
|
||||
// Line 0, col 9 (after '(') to line 3, col -1 (before ')')
|
||||
// But since we're in visual mode, check the anchor and cursor
|
||||
if m.ActiveWindow().Anchor.Line != 0 || m.ActiveWindow().Cursor.Line != 2 {
|
||||
t.Errorf("anchor.Line=%d, cursor.Line=%d, want anchor.Line=0, cursor.Line=2",
|
||||
m.ActiveWindow().Anchor.Line, m.ActiveWindow().Cursor.Line)
|
||||
}
|
||||
// Anchor should be at col 9 (after '('), cursor at end of line 2
|
||||
if m.ActiveWindow().Anchor.Col != 9 {
|
||||
t.Errorf("anchor.Col=%d, want 9", m.ActiveWindow().Anchor.Col)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test 'di(' on multi-line parentheses", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"function(",
|
||||
" arg1,",
|
||||
" arg2",
|
||||
")",
|
||||
}
|
||||
// Cursor on "arg1"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
||||
sendKeys(tm, "d", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"function(",
|
||||
")",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test nested multi-line braces - cursor in outer", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"outer {",
|
||||
" inner {",
|
||||
" content",
|
||||
" }",
|
||||
" more",
|
||||
"}",
|
||||
}
|
||||
// Cursor on "more" (inside outer, outside inner)
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 4})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"outer {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test nested multi-line braces - cursor in inner", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"outer {",
|
||||
" inner {",
|
||||
" content",
|
||||
" }",
|
||||
" more",
|
||||
"}",
|
||||
}
|
||||
// Cursor on "content" (inside inner block)
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 2})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"outer {",
|
||||
" inner {",
|
||||
" }",
|
||||
" more",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test nested multi-line braces with multiple nesting levels", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"level1 {",
|
||||
" level2 {",
|
||||
" level3 {",
|
||||
" target",
|
||||
" }",
|
||||
" }",
|
||||
"}",
|
||||
}
|
||||
// Cursor on "target"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 12, Line: 3})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"level1 {",
|
||||
" level2 {",
|
||||
" level3 {",
|
||||
" }",
|
||||
" }",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test multi-line delimiters - cursor on opening line", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"function(arg) {",
|
||||
" body",
|
||||
"}",
|
||||
}
|
||||
// Cursor on opening line, after '{'
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 14, Line: 0})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"function(arg) {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test multi-line delimiters - cursor on closing line", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"function(arg) {",
|
||||
" body",
|
||||
"}",
|
||||
}
|
||||
// Cursor on closing line, before '}'
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"function(arg) {",
|
||||
"}",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test multi-line delimiters - cursor before delimiters searches forward", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"before",
|
||||
"function(arg) {",
|
||||
" body",
|
||||
"}",
|
||||
"after",
|
||||
}
|
||||
// Cursor on "before"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "d", "i", "{")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"before",
|
||||
"function(arg) {",
|
||||
"}",
|
||||
"after",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test nested parentheses across lines", func(t *testing.T) {
|
||||
lines := []string{
|
||||
"outer(",
|
||||
" inner(",
|
||||
" content",
|
||||
" ),",
|
||||
" more",
|
||||
")",
|
||||
}
|
||||
// Cursor on "content"
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 2})
|
||||
sendKeys(tm, "d", "i", "(")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
expected := []string{
|
||||
"outer(",
|
||||
" inner(",
|
||||
" ),",
|
||||
" more",
|
||||
")",
|
||||
}
|
||||
if !slicesEqual(m.ActiveBuffer().Lines, expected) {
|
||||
t.Errorf("lines = %v, want %v", m.ActiveBuffer().Lines, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to compare slices
|
||||
func slicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -46,10 +46,8 @@ type Delimiter struct {
|
||||
Char rune
|
||||
}
|
||||
|
||||
// TODO: This should allow for many lines, not just a single line
|
||||
func (to Delimiter) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
|
||||
buf := m.ActiveBuffer()
|
||||
line := buf.Lines[cursor.Line]
|
||||
|
||||
// Determine which is a starting delimiter and which ends
|
||||
_, isStartingDelimiter := DirectionalDelimiterMap[to.Char]
|
||||
@ -77,22 +75,18 @@ func (to Delimiter) GetRange(m action.Model, cursor core.Position, modifier stri
|
||||
return cursor, cursor, core.CharwiseExclusive
|
||||
}
|
||||
|
||||
// Try to find delimiters around the cursor position
|
||||
start, end, found := findDelimiterPair(line, startDelim, endDelim, cursor.Col, modifier == "a")
|
||||
// Use multi-line delimiter pair finding
|
||||
start, end, found := findMultiLineDelimiterPair(buf.Lines, startDelim, endDelim, cursor, modifier == "a")
|
||||
|
||||
if !found {
|
||||
return cursor, cursor, core.CharwiseExclusive
|
||||
}
|
||||
|
||||
// This happens when nothing is between the delimiter
|
||||
if start.Col > end.Col {
|
||||
// Check if positions are valid
|
||||
if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
|
||||
return cursor, cursor, core.CharwiseExclusive
|
||||
}
|
||||
|
||||
// Delimiter objects don't span lines (for now)
|
||||
start.Line = cursor.Line
|
||||
end.Line = cursor.Line
|
||||
|
||||
return start, end, core.CharwiseInclusive
|
||||
}
|
||||
|
||||
@ -247,3 +241,278 @@ func findNextDelimiter(line string, delimiter rune, startCol int) (int, bool) {
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Multi-line Delimiter Functions
|
||||
// ============================================================================
|
||||
|
||||
// findMultiLineDelimiterPair finds a matching pair of delimiters across multiple lines.
|
||||
// It handles proper nesting for directional delimiters (parens, brackets, braces).
|
||||
func findMultiLineDelimiterPair(lines []string, startDelim, endDelim rune, cursor core.Position, includeDelimiters bool) (core.Position, core.Position, bool) {
|
||||
// First, try to find delimiters around the cursor position (innermost pair)
|
||||
start, end, found := findEnclosingDelimiterPair(lines, startDelim, endDelim, cursor, includeDelimiters)
|
||||
if found {
|
||||
return start, end, true
|
||||
}
|
||||
|
||||
// Not inside delimiters, search forward for the next pair
|
||||
start, end, found = findNextDelimiterPair(lines, startDelim, endDelim, cursor, includeDelimiters)
|
||||
return start, end, found
|
||||
}
|
||||
|
||||
// findEnclosingDelimiterPair finds the innermost delimiter pair that encloses the cursor.
|
||||
func findEnclosingDelimiterPair(lines []string, startDelim, endDelim rune, cursor core.Position, includeDelimiters bool) (core.Position, core.Position, bool) {
|
||||
// Search backward from cursor to find opening delimiter
|
||||
startPos, foundStart := searchBackwardForDelimiter(lines, startDelim, endDelim, cursor)
|
||||
if !foundStart {
|
||||
return core.Position{}, core.Position{}, false
|
||||
}
|
||||
|
||||
// Search forward from the opening delimiter to find matching closing delimiter
|
||||
endPos, foundEnd := searchForwardForMatchingDelimiter(lines, startDelim, endDelim, startPos)
|
||||
if !foundEnd {
|
||||
return core.Position{}, core.Position{}, false
|
||||
}
|
||||
|
||||
// Check if cursor is between these delimiters
|
||||
if !isCursorBetween(cursor, startPos, endPos) {
|
||||
return core.Position{}, core.Position{}, false
|
||||
}
|
||||
|
||||
// Adjust positions based on includeDelimiters
|
||||
start, end, ok := adjustDelimiterPositions(lines, startPos, endPos, includeDelimiters)
|
||||
return start, end, ok
|
||||
}
|
||||
|
||||
// findNextDelimiterPair searches forward from cursor for the next delimiter pair.
|
||||
func findNextDelimiterPair(lines []string, startDelim, endDelim rune, cursor core.Position, includeDelimiters bool) (core.Position, core.Position, bool) {
|
||||
// Search forward from cursor for opening delimiter
|
||||
startPos, foundStart := searchForwardSimple(lines, startDelim, cursor)
|
||||
if !foundStart {
|
||||
return core.Position{}, core.Position{}, false
|
||||
}
|
||||
|
||||
// Search forward from opening for matching closing delimiter
|
||||
endPos, foundEnd := searchForwardForMatchingDelimiter(lines, startDelim, endDelim, startPos)
|
||||
if !foundEnd {
|
||||
return core.Position{}, core.Position{}, false
|
||||
}
|
||||
|
||||
// Adjust positions based on includeDelimiters
|
||||
start, end, ok := adjustDelimiterPositions(lines, startPos, endPos, includeDelimiters)
|
||||
return start, end, ok
|
||||
}
|
||||
|
||||
// searchBackwardForDelimiter searches backward from cursor to find the nearest opening delimiter
|
||||
// that could enclose the cursor position.
|
||||
func searchBackwardForDelimiter(lines []string, startDelim, endDelim rune, cursor core.Position) (core.Position, bool) {
|
||||
// For quote-like delimiters, only search current line
|
||||
if startDelim == endDelim {
|
||||
return searchBackwardForDelimiterSingleLine(lines[cursor.Line], startDelim, endDelim, cursor.Col, cursor.Line)
|
||||
}
|
||||
|
||||
// Start from cursor position and go backward
|
||||
line := cursor.Line
|
||||
col := cursor.Col
|
||||
|
||||
// Search current line from cursor backwards
|
||||
for i := col; i >= 0; i-- {
|
||||
if i < len(lines[line]) && rune(lines[line][i]) == startDelim {
|
||||
// Found a potential start delimiter, verify it could enclose cursor
|
||||
pos := core.Position{Line: line, Col: i}
|
||||
// Check if this delimiter's matching pair is after the cursor
|
||||
endPos, found := searchForwardForMatchingDelimiter(lines, startDelim, endDelim, pos)
|
||||
if found && isCursorBetween(cursor, pos, endPos) {
|
||||
return pos, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search previous lines
|
||||
for line = cursor.Line - 1; line >= 0; line-- {
|
||||
for i := len(lines[line]) - 1; i >= 0; i-- {
|
||||
if rune(lines[line][i]) == startDelim {
|
||||
pos := core.Position{Line: line, Col: i}
|
||||
endPos, found := searchForwardForMatchingDelimiter(lines, startDelim, endDelim, pos)
|
||||
if found && isCursorBetween(cursor, pos, endPos) {
|
||||
return pos, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return core.Position{}, false
|
||||
}
|
||||
|
||||
// searchBackwardForDelimiterSingleLine searches backward on a single line (for quotes).
|
||||
func searchBackwardForDelimiterSingleLine(line string, startDelim, endDelim rune, col int, lineNum int) (core.Position, bool) {
|
||||
for i := col; i >= 0; i-- {
|
||||
if i < len(line) && rune(line[i]) == startDelim {
|
||||
// For quotes, verify it's an opening quote by checking if it has a matching closing quote
|
||||
// Count quotes before this position
|
||||
quotesBefore := 0
|
||||
for j := 0; j < i; j++ {
|
||||
if rune(line[j]) == startDelim {
|
||||
quotesBefore++
|
||||
}
|
||||
}
|
||||
// If even number of quotes before, this is an opening quote
|
||||
if quotesBefore%2 == 0 {
|
||||
// Find matching closing quote
|
||||
for j := i + 1; j < len(line); j++ {
|
||||
if rune(line[j]) == endDelim {
|
||||
// Check if cursor is between i and j
|
||||
if col > i && col < j {
|
||||
return core.Position{Line: lineNum, Col: i}, true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return core.Position{}, false
|
||||
}
|
||||
|
||||
// searchForwardForMatchingDelimiter searches forward from startPos to find the matching closing delimiter.
|
||||
// It properly handles nesting for directional delimiters.
|
||||
func searchForwardForMatchingDelimiter(lines []string, startDelim, endDelim rune, startPos core.Position) (core.Position, bool) {
|
||||
nestLevel := 0
|
||||
line := startPos.Line
|
||||
startCol := startPos.Col + 1 // Start after the opening delimiter
|
||||
|
||||
// For quote-like delimiters (same start and end)
|
||||
if startDelim == endDelim {
|
||||
// Simple search for next occurrence on same line
|
||||
if line < len(lines) {
|
||||
for i := startCol; i < len(lines[line]); i++ {
|
||||
if rune(lines[line][i]) == endDelim {
|
||||
return core.Position{Line: line, Col: i}, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return core.Position{}, false
|
||||
}
|
||||
|
||||
// For directional delimiters with nesting
|
||||
// Search current line from startCol
|
||||
for i := startCol; i < len(lines[line]); i++ {
|
||||
ch := rune(lines[line][i])
|
||||
if ch == startDelim {
|
||||
nestLevel++
|
||||
} else if ch == endDelim {
|
||||
if nestLevel == 0 {
|
||||
return core.Position{Line: line, Col: i}, true
|
||||
}
|
||||
nestLevel--
|
||||
}
|
||||
}
|
||||
|
||||
// Search subsequent lines
|
||||
for line = startPos.Line + 1; line < len(lines); line++ {
|
||||
for i := 0; i < len(lines[line]); i++ {
|
||||
ch := rune(lines[line][i])
|
||||
if ch == startDelim {
|
||||
nestLevel++
|
||||
} else if ch == endDelim {
|
||||
if nestLevel == 0 {
|
||||
return core.Position{Line: line, Col: i}, true
|
||||
}
|
||||
nestLevel--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return core.Position{}, false
|
||||
}
|
||||
|
||||
// searchForwardSimple searches forward from cursor for the next occurrence of delimiter.
|
||||
func searchForwardSimple(lines []string, delimiter rune, cursor core.Position) (core.Position, bool) {
|
||||
// Search current line from cursor
|
||||
for i := cursor.Col; i < len(lines[cursor.Line]); i++ {
|
||||
if rune(lines[cursor.Line][i]) == delimiter {
|
||||
return core.Position{Line: cursor.Line, Col: i}, true
|
||||
}
|
||||
}
|
||||
|
||||
// Search subsequent lines
|
||||
for line := cursor.Line + 1; line < len(lines); line++ {
|
||||
for i := 0; i < len(lines[line]); i++ {
|
||||
if rune(lines[line][i]) == delimiter {
|
||||
return core.Position{Line: line, Col: i}, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return core.Position{}, false
|
||||
}
|
||||
|
||||
// isCursorBetween checks if cursor is between start and end positions.
|
||||
func isCursorBetween(cursor, start, end core.Position) bool {
|
||||
// Check if cursor is after or at start
|
||||
if cursor.Line < start.Line || (cursor.Line == start.Line && cursor.Col < start.Col) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if cursor is before or at end
|
||||
if cursor.Line > end.Line || (cursor.Line == end.Line && cursor.Col > end.Col) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// hasOnlyWhitespaceBefore checks if there is only whitespace before the character at col.
|
||||
func hasOnlyWhitespaceBefore(line string, col int) bool {
|
||||
for i := 0; i < col; i++ {
|
||||
if !isWhitespace(rune(line[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isWhitespace checks if a rune is whitespace.
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// adjustDelimiterPositions adjusts the delimiter positions based on whether to include delimiters.
|
||||
func adjustDelimiterPositions(lines []string, startPos, endPos core.Position, includeDelimiters bool) (core.Position, core.Position, bool) {
|
||||
if includeDelimiters {
|
||||
// Include the delimiters themselves
|
||||
return startPos, endPos, true
|
||||
}
|
||||
|
||||
// Exclude the delimiters - start after opening, end before closing
|
||||
start := core.Position{Line: startPos.Line, Col: startPos.Col + 1}
|
||||
end := core.Position{Line: endPos.Line, Col: endPos.Col - 1}
|
||||
|
||||
// Handle special cases
|
||||
if startPos.Line == endPos.Line {
|
||||
// Same line - check if there's content between delimiters
|
||||
if startPos.Col+1 > endPos.Col-1 {
|
||||
// Empty delimiters like "()" - return positions that will be detected as invalid
|
||||
return start, end, true
|
||||
}
|
||||
} else {
|
||||
// Multi-line case
|
||||
// If start position is beyond the line (delimiter was last char on its line)
|
||||
if start.Col >= len(lines[start.Line]) {
|
||||
// Position at end of line to include the newline in deletion
|
||||
start.Col = len(lines[start.Line])
|
||||
}
|
||||
|
||||
// If end position is negative (delimiter was first char on its line) OR
|
||||
// delimiter has only whitespace before it on its line
|
||||
if end.Col < 0 || hasOnlyWhitespaceBefore(lines[endPos.Line], endPos.Col) {
|
||||
// Position at end of previous line to include that line's newline
|
||||
if end.Line > 0 {
|
||||
end.Line--
|
||||
end.Col = len(lines[end.Line])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return start, end, true
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user