Rework FindMatchingBrace() interface and implementation (#3319)

Instead of passing a single brace pair to FindMatchingBrace(), make it
traverse all brace pairs in buffer.BracePairs on its own.

This has the following advantages:

1. Makes FindMatchingBrace() easier to use, in particular much easier
   to use from Lua.

2. Lets FindMatchingBrace() ensure that we use just one matching brace -
   the higher-priority one. This fixes the following issues:

    ([foo]bar)
     ^

when the cursor is on `[`:

- Both `[]` and `()` pairs are highlighted, whereas the expected
  behavior is that only one pair is highlighted - the one that the
  JumpToMatchingBrace action would jump to.

- JumpToMatchingBrace action incorrectly jumps to `)` instead of
  `]` (which should take higher priority in this case).

In contrast, with `((foo)bar)` it works correctly.
This commit is contained in:
Dmytro Maluka 2024-06-05 00:56:19 +02:00 committed by GitHub
parent 46e55c8e91
commit 9eb8782ff2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 72 deletions

View file

@ -1394,21 +1394,15 @@ func (h *BufPane) paste(clip string) {
// JumpToMatchingBrace moves the cursor to the matching brace if it is // JumpToMatchingBrace moves the cursor to the matching brace if it is
// currently on a brace // currently on a brace
func (h *BufPane) JumpToMatchingBrace() bool { func (h *BufPane) JumpToMatchingBrace() bool {
for _, bp := range buffer.BracePairs { matchingBrace, left, found := h.Buf.FindMatchingBrace(h.Cursor.Loc)
r := h.Cursor.RuneUnder(h.Cursor.X) if found {
rl := h.Cursor.RuneUnder(h.Cursor.X - 1) if left {
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { h.Cursor.GotoLoc(matchingBrace)
matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc) } else {
if found { h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
if left {
h.Cursor.GotoLoc(matchingBrace)
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
}
h.Relocate()
return true
}
} }
h.Relocate()
return true
} }
return false return false
} }

View file

@ -1140,34 +1140,14 @@ var BracePairs = [][2]rune{
{'[', ']'}, {'[', ']'},
} }
// FindMatchingBrace returns the location in the buffer of the matching bracket func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {
// It is given a brace type containing the open and closing character, (for example
// '{' and '}') as well as the location to match from
// TODO: maybe can be more efficient with utf8 package
// returns the location of the matching brace
// if the boolean returned is true then the original matching brace is one character left
// of the starting location
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
curLine := []rune(string(b.LineBytes(start.Y)))
startChar := ' '
if start.X >= 0 && start.X < len(curLine) {
startChar = curLine[start.X]
}
leftChar := ' '
if start.X-1 >= 0 && start.X-1 < len(curLine) {
leftChar = curLine[start.X-1]
}
var i int var i int
if startChar == braceType[0] || (leftChar == braceType[0] && startChar != braceType[1]) { if char == braceType[0] {
for y := start.Y; y < b.LinesNum(); y++ { for y := start.Y; y < b.LinesNum(); y++ {
l := []rune(string(b.LineBytes(y))) l := []rune(string(b.LineBytes(y)))
xInit := 0 xInit := 0
if y == start.Y { if y == start.Y {
if startChar == braceType[0] { xInit = start.X
xInit = start.X
} else {
xInit = start.X - 1
}
} }
for x := xInit; x < len(l); x++ { for x := xInit; x < len(l); x++ {
r := l[x] r := l[x]
@ -1176,24 +1156,17 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, boo
} else if r == braceType[1] { } else if r == braceType[1] {
i-- i--
if i == 0 { if i == 0 {
if startChar == braceType[0] { return Loc{x, y}, true
return Loc{x, y}, false, true
}
return Loc{x, y}, true, true
} }
} }
} }
} }
} else if startChar == braceType[1] || leftChar == braceType[1] { } else if char == braceType[1] {
for y := start.Y; y >= 0; y-- { for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data)) l := []rune(string(b.lines[y].data))
xInit := len(l) - 1 xInit := len(l) - 1
if y == start.Y { if y == start.Y {
if startChar == braceType[1] { xInit = start.X
xInit = start.X
} else {
xInit = start.X - 1
}
} }
for x := xInit; x >= 0; x-- { for x := xInit; x >= 0; x-- {
r := l[x] r := l[x]
@ -1202,16 +1175,55 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, boo
} else if r == braceType[0] { } else if r == braceType[0] {
i-- i--
if i == 0 { if i == 0 {
if startChar == braceType[1] { return Loc{x, y}, true
return Loc{x, y}, false, true
}
return Loc{x, y}, true, true
} }
} }
} }
} }
} }
return start, true, false return start, false
}
// If there is a brace character (for example '{' or ']') at the given start location,
// FindMatchingBrace returns the location of the matching brace for it (for example '}'
// or '['). The second returned value is true if there was no matching brace found
// for given starting location but it was found for the location one character left
// of it. The third returned value is true if the matching brace was found at all.
func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
// TODO: maybe can be more efficient with utf8 package
curLine := []rune(string(b.LineBytes(start.Y)))
// first try to find matching brace for the given location (it has higher priority)
if start.X >= 0 && start.X < len(curLine) {
startChar := curLine[start.X]
for _, bp := range BracePairs {
if startChar == bp[0] || startChar == bp[1] {
mb, found := b.findMatchingBrace(bp, start, startChar)
if found {
return mb, false, true
}
}
}
}
// failed to find matching brace for the given location, so try to find matching
// brace for the location one character left of it
if start.X-1 >= 0 && start.X-1 < len(curLine) {
leftChar := curLine[start.X-1]
left := Loc{start.X - 1, start.Y}
for _, bp := range BracePairs {
if leftChar == bp[0] || leftChar == bp[1] {
mb, found := b.findMatchingBrace(bp, left, leftChar)
if found {
return mb, true, true
}
}
}
}
return start, false, false
} }
// Retab changes all tabs to spaces or vice versa // Retab changes all tabs to spaces or vice versa

View file

@ -392,28 +392,20 @@ func (w *BufWindow) displayBuffer() {
var matchingBraces []buffer.Loc var matchingBraces []buffer.Loc
// bracePairs is defined in buffer.go // bracePairs is defined in buffer.go
if b.Settings["matchbrace"].(bool) { if b.Settings["matchbrace"].(bool) {
for _, bp := range buffer.BracePairs { for _, c := range b.GetCursors() {
for _, c := range b.GetCursors() { if c.HasSelection() {
if c.HasSelection() { continue
continue }
}
curX := c.X
curLoc := c.Loc
r := c.RuneUnder(curX) mb, left, found := b.FindMatchingBrace(c.Loc)
rl := c.RuneUnder(curX - 1) if found {
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { matchingBraces = append(matchingBraces, mb)
mb, left, found := b.FindMatchingBrace(bp, curLoc) if !left {
if found { if b.Settings["matchbracestyle"].(string) != "highlight" {
matchingBraces = append(matchingBraces, mb) matchingBraces = append(matchingBraces, c.Loc)
if !left {
if b.Settings["matchbracestyle"].(string) != "highlight" {
matchingBraces = append(matchingBraces, curLoc)
}
} else {
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
}
} }
} else {
matchingBraces = append(matchingBraces, c.Loc.Move(-1, b))
} }
} }
} }