Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions internal/action/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,28 @@ func (h *BufPane) JumpToMatchingBrace() bool {
return false
}

// JumpToOpeningBrace moves the cursor to the opening brace in current brace scope
func (h *BufPane) JumpToOpeningBrace() bool {
matchingBrace, found := h.Buf.FindOpeningBrace(h.Cursor.Loc)
if found {
h.Cursor.GotoLoc(matchingBrace)
h.Relocate()
return true
}
return false
}

// JumpToClosingBrace moves the cursor to the closing brace in current brace scope
func (h *BufPane) JumpToClosingBrace() bool {
matchingBrace, found := h.Buf.FindClosingBrace(h.Cursor.Loc)
if found {
h.Cursor.GotoLoc(matchingBrace)
h.Relocate()
return true
}
return false
}

// SelectAll selects the entire buffer
func (h *BufPane) SelectAll() bool {
h.Cursor.SetSelectionStart(h.Buf.Start())
Expand Down
4 changes: 4 additions & 0 deletions internal/action/bufpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,8 @@ var BufKeyActions = map[string]BufKeyAction{
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpToOpeningBrace": (*BufPane).JumpToOpeningBrace,
"JumpToClosingBrace": (*BufPane).JumpToClosingBrace,
"JumpLine": (*BufPane).JumpLine,
"Deselect": (*BufPane).Deselect,
"ClearInfo": (*BufPane).ClearInfo,
Expand Down Expand Up @@ -930,4 +932,6 @@ var MultiActions = map[string]bool{
"StartOfTextToggle": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
"JumpToOpeningBrace": true,
"JumpToClosingBrace": true,
}
2 changes: 2 additions & 0 deletions internal/action/defaults_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ var bufdefaults = map[string]string{
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
"Alt-<": "JumpToOpeningBrace",
"Alt->": "JumpToClosingBrace",

// Emacs-style keybindings
"Alt-f": "WordRight",
Expand Down
2 changes: 2 additions & 0 deletions internal/action/defaults_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ var bufdefaults = map[string]string{
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
"Alt-<": "JumpToOpeningBrace",
"Alt->": "JumpToClosingBrace",

// Emacs-style keybindings
"Alt-f": "WordRight",
Expand Down
209 changes: 155 additions & 54 deletions internal/buffer/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1173,86 +1173,187 @@ var BracePairs = [][2]rune{
{'[', ']'},
}

func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {
var i int
if char == braceType[0] {
for y := start.Y; y < b.LinesNum(); y++ {
l := []rune(string(b.LineBytes(y)))
xInit := 0
if y == start.Y {
xInit = start.X
func (b *Buffer) resolveBraceStartLoc(start Loc) Loc {
var onbrace bool = false
// TODO: maybe can be more efficient with utf8 package
curLine := []rune(string(b.LineBytes(start.Y)))
// See if we are on any brace
if start.X >= 0 && start.X < len(curLine) {
startChar := curLine[start.X]
for _, bp := range BracePairs {
if startChar == bp[0] || startChar == bp[1] {
return start
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}, true
}
}
}

if !onbrace && b.Settings["matchbraceleft"].(bool) {
if start.X-1 >= 0 && start.X-1 < len(curLine) {
startChar := curLine[start.X-1]
for _, bp := range BracePairs {
if startChar == bp[0] || startChar == bp[1] {
return Loc{start.X - 1, start.Y}
}
}
}
} else if char == braceType[1] {
for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y {
xInit = start.X
}
return start
}

func (b *Buffer) findOpeningBrace(braceType [2]rune, start Loc) (Loc, bool) {
// Bound guard
start = clamp(start, b.LineArray)
if len(b.lines) == 0 {
return start, false
}

i := 1
// If we are on a closing brace, let the counter be incremented below when we traverse
curLine := []rune(string(b.LineBytes(start.Y)))
if start.X >= 0 && start.X < len(curLine) {
startChar := curLine[start.X]
if startChar == braceType[1] {
i = 0
}
}

for y := start.Y; y >= 0; y-- {
l := []rune(string(b.lines[y].data))
xInit := len(l) - 1
if y == start.Y && start.X < len(curLine) {
xInit = start.X
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[1] {
i++
} else if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}, true
}
}
for x := xInit; x >= 0; x-- {
r := l[x]
if r == braceType[1] {
i++
} else if r == braceType[0] {
i--
if i == 0 {
return Loc{x, y}, true
}
}
}
return start, false
}

// Returns the opening brace in current brace scope starting at the start location and a boolean
// indicating if an opening brace is found
func (b *Buffer) FindOpeningBrace(start Loc) (Loc, bool) {
currentDist := -1
currentMb := Loc{-1, -1}
start = b.resolveBraceStartLoc(start)
for _, bp := range BracePairs {
mb, found := b.findOpeningBrace(bp, start)
if found {
dist := DiffLA(start, mb, b.LineArray)
if currentDist < 0 || dist < currentDist {
currentMb = mb
currentDist = dist
}
}
}

if currentDist == -1 {
return start, false
} else {
return currentMb, true
}
}

func (b *Buffer) findClosingBrace(braceType [2]rune, start Loc) (Loc, bool) {
// Bound guard
start = clamp(start, b.LineArray)
if len(b.lines) == 0 {
return start, false
}

i := 1
// If we are on an opening brace, let the counter be incremented below when we traverse
curLine := []rune(string(b.LineBytes(start.Y)))
if start.X >= 0 && start.X < len(curLine) {
startChar := curLine[start.X]
if startChar == braceType[0] {
i = 0
}
}

for y := start.Y; y < b.LinesNum(); y++ {
l := []rune(string(b.LineBytes(y)))
xInit := 0
if y == start.Y && start.X >= 0 {
xInit = start.X
}
for x := xInit; x < len(l); x++ {
r := l[x]
if r == braceType[0] {
i++
} else if r == braceType[1] {
i--
if i == 0 {
return Loc{x, y}, true
}
}
}
}
return start, false
}

// Returns the closing brace in current brace scope starting at the start location and a boolean
// indicating if an closing brace is found
func (b *Buffer) FindClosingBrace(start Loc) (Loc, bool) {
currentDist := -1
currentMb := Loc{-1, -1}
start = b.resolveBraceStartLoc(start)
for _, bp := range BracePairs {
mb, found := b.findClosingBrace(bp, start)
if found {
dist := DiffLA(start, mb, b.LineArray)
if currentDist < 0 || dist < currentDist {
currentMb = mb
currentDist = dist
}
}
}

if currentDist == -1 {
return start, false
} else {
return currentMb, true
}
}

func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {
if char == braceType[0] {
return b.findClosingBrace(braceType, start)
} else if char == braceType[1] {
return b.findOpeningBrace(braceType, start)
}
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
newstart := b.resolveBraceStartLoc(start)
var left bool = false
if start != newstart {
left = true
start = newstart
}
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
}
}
}
}

if b.Settings["matchbraceleft"].(bool) {
// 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 mb, left, true
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions runtime/help/defaultkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ can change it!
| PageDown | Move cursor down one page |
| Ctrl-Home or Ctrl-UpArrow | Move cursor to start of document |
| Ctrl-End or Ctrl-DownArrow | Move cursor to end of document |
| Alt-< | Move cursor to opening brace in current brace scope |
| Alt-> | Move cursor to closing brace in current brace scope |
| Ctrl-l | Jump to a line in the file (prompts with #) |
| Ctrl-w | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split) |

Expand Down
4 changes: 4 additions & 0 deletions runtime/help/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ JumpLine
Deselect
ClearInfo
None
JumpToOpeningBrace
JumpToClosingBrace
```

The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
Expand Down Expand Up @@ -604,6 +606,8 @@ conventions for text editing defaults.
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
"Alt-<": "JumpToOpeningBrace",
"Alt->": "JumpToClosingBrace",

// Emacs-style keybindings
"Alt-f": "WordRight",
Expand Down
Loading