124 lines
3.0 KiB
Go
124 lines
3.0 KiB
Go
package textobject
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
|
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
)
|
|
|
|
// Map opposite char
|
|
var DirectionalDelimiterMap map[rune]rune = map[rune]rune{
|
|
'(': ')',
|
|
'[': ']',
|
|
'{': '}',
|
|
'<': '>',
|
|
}
|
|
|
|
var singleDelimiterList []rune = []rune{'"', '\'', '`'}
|
|
|
|
func getStartDelimiterFromEnd(d rune) (rune, bool) {
|
|
if slices.Contains(singleDelimiterList, d) {
|
|
return d, true
|
|
}
|
|
|
|
for start, end := range DirectionalDelimiterMap {
|
|
if end == d {
|
|
return start, true
|
|
}
|
|
}
|
|
|
|
return ' ', false
|
|
}
|
|
|
|
func getEndDelimiterFromStart(d rune) (rune, bool) {
|
|
if slices.Contains(singleDelimiterList, d) {
|
|
return d, true
|
|
}
|
|
|
|
end, found := DirectionalDelimiterMap[d]
|
|
return end, found
|
|
}
|
|
|
|
// Delimiter implements text object for words (iw/aw)
|
|
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]
|
|
var (
|
|
startDelim rune
|
|
endDelim rune
|
|
startFound bool = true
|
|
endFound bool = true
|
|
)
|
|
|
|
if isStartingDelimiter {
|
|
startDelim = to.Char
|
|
endDelim, endFound = getEndDelimiterFromStart(to.Char)
|
|
} else {
|
|
endDelim = to.Char
|
|
startDelim, startFound = getStartDelimiterFromEnd(to.Char)
|
|
}
|
|
|
|
if !endFound || !startFound {
|
|
m.SetCommandOutput(&core.CommandOutput{
|
|
Lines: []string{fmt.Sprintf("Could not find delimiters from '%c'", to.Char)},
|
|
Inline: true,
|
|
IsError: false,
|
|
})
|
|
return cursor, cursor, core.CharwiseExclusive
|
|
}
|
|
|
|
// Find object boundaries
|
|
start, sOk := findDelimiterStart(line, startDelim, cursor.Col, modifier == "a")
|
|
end, eOk := findDelimiterEnd(line, endDelim, cursor.Col, modifier == "a")
|
|
|
|
// Handle the case where they are not found
|
|
if !sOk || !eOk {
|
|
return cursor, cursor, core.CharwiseExclusive
|
|
}
|
|
|
|
// This happens when nothing is between the delimiter, fixes the bugs we found
|
|
if start.Col > end.Col {
|
|
return cursor, cursor, core.CharwiseExclusive
|
|
}
|
|
|
|
// Word object's don't span lines
|
|
start.Line = cursor.Line
|
|
end.Line = cursor.Line
|
|
|
|
return start, end, core.CharwiseInclusive
|
|
}
|
|
|
|
func findDelimiterStart(line string, delimiter rune, col int, includeDelimiter bool) (core.Position, bool) {
|
|
for i := col; i >= 0; i-- {
|
|
if rune(line[i]) == delimiter {
|
|
if includeDelimiter {
|
|
return core.Position{Line: 0, Col: i}, true
|
|
}
|
|
return core.Position{Line: 0, Col: i + 1}, true
|
|
}
|
|
}
|
|
return core.Position{Line: 0, Col: 0}, false
|
|
}
|
|
|
|
func findDelimiterEnd(line string, delimiter rune, col int, includeDelimiter bool) (core.Position, bool) {
|
|
for i := col; i < len(line); i++ {
|
|
if rune(line[i]) == delimiter {
|
|
if includeDelimiter {
|
|
return core.Position{Line: 0, Col: i}, true
|
|
}
|
|
return core.Position{Line: 0, Col: i - 1}, true
|
|
}
|
|
}
|
|
return core.Position{Line: 0, Col: 0}, false
|
|
}
|