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 // // BUG: This does not work properly when the cursor is not inside a delimiter. If the cursor // does not fall inside a delimiter range, it should search forward and find the delimiter. 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 }