Hayden Hargreaves aa156971ad feat: text objects initial impl, tested
However, it does not work for multi line delimiters.
2026-03-26 14:09:10 -07:00

206 lines
4.4 KiB
Go

package textobject
import (
"git.gophernest.net/azpect/TextEditor/internal/action"
"git.gophernest.net/azpect/TextEditor/internal/core"
)
// Word implements text object for words (iw/aw)
type Word struct{}
func (to Word) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer()
line := buf.Lines[cursor.Line]
// Find word boundaries
start := findWordStart(line, cursor.Col)
end := findWordEnd(line, cursor.Col, modifier == "a")
// Word object's don't span lines
start.Line = cursor.Line
end.Line = cursor.Line
return start, end, core.CharwiseInclusive
}
// Word implements text object for WORDs (iW/aW)
type WORD struct{}
func (to WORD) GetRange(m action.Model, cursor core.Position, modifier string) (core.Position, core.Position, core.MotionType) {
buf := m.ActiveBuffer()
line := buf.Lines[cursor.Line]
// Find word boundaries
start := findWORDStart(line, cursor.Col)
end := findWORDEnd(line, cursor.Col, modifier == "a")
// Word object's don't span lines
start.Line = cursor.Line
end.Line = cursor.Line
return start, end, core.CharwiseInclusive
}
// isWordChar: Returns true if the character is a word character (alphanumeric
// or underscore). COPIED FROM internal/motion/word.go
func isWordChar(c byte) bool {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_'
}
func findWordStart(line string, col int) core.Position {
if col >= len(line) || col < 0 {
return core.Position{Line: 0, Col: 0}
}
curChar := line[col]
// Don't start on whitespace (shouldn't happen for text objects)
if curChar == ' ' || curChar == '\t' {
return core.Position{Line: 0, Col: col}
}
// Determine if we're on word char or punctuation
onWordChar := isWordChar(curChar)
// Move backwards while in the same character class
i := col
for i > 0 {
prevChar := line[i-1]
// Stop at whitespace
if prevChar == ' ' || prevChar == '\t' {
break
}
// Stop if character class changes
if isWordChar(prevChar) != onWordChar {
break
}
i--
}
return core.Position{Line: 0, Col: i}
}
func findWordEnd(line string, col int, includeWhitespace bool) core.Position {
if col >= len(line) || col < 0 {
return core.Position{Line: 0, Col: 0}
}
curChar := line[col]
// Don't start on whitespace
if curChar == ' ' || curChar == '\t' {
return core.Position{Line: 0, Col: col}
}
// Determine if we're on word char or punctuation
onWordChar := isWordChar(curChar)
// Move forward while in the same character class
i := col
for i < len(line) {
c := line[i]
// Stop at whitespace
if c == ' ' || c == '\t' {
break
}
// Stop if character class changes
if isWordChar(c) != onWordChar {
break
}
i++
}
// i is now one past the end, so back up
i--
// If including whitespace, skip trailing spaces/tabs
if includeWhitespace {
i++ // Move forward to whitespace
for i < len(line) && (line[i] == ' ' || line[i] == '\t') {
i++
}
i-- // Back to last whitespace
}
return core.Position{Line: 0, Col: i}
}
func findWORDStart(line string, col int) core.Position {
if col >= len(line) || col < 0 {
return core.Position{Line: 0, Col: 0}
}
curChar := line[col]
// Don't start on whitespace (shouldn't happen for text objects)
if curChar == ' ' || curChar == '\t' {
return core.Position{Line: 0, Col: col}
}
// For WORD, all non-whitespace is one class
// Just move backwards until we hit whitespace or start of line
i := col
for i > 0 {
prevChar := line[i-1]
// Stop at whitespace
if prevChar == ' ' || prevChar == '\t' {
break
}
i--
}
return core.Position{Line: 0, Col: i}
}
func findWORDEnd(line string, col int, includeWhitespace bool) core.Position {
if col >= len(line) || col < 0 {
return core.Position{Line: 0, Col: 0}
}
curChar := line[col]
// Don't start on whitespace
if curChar == ' ' || curChar == '\t' {
return core.Position{Line: 0, Col: col}
}
// For WORD, all non-whitespace is one class
// Move forward until we hit whitespace or end of line
i := col
for i < len(line) {
c := line[i]
// Stop at whitespace
if c == ' ' || c == '\t' {
break
}
i++
}
// i is now one past the end, so back up
i--
// If including whitespace, skip trailing spaces/tabs
if includeWhitespace {
i++ // Move forward to whitespace
for i < len(line) && (line[i] == ' ' || line[i] == '\t') {
i++
}
i-- // Back to last whitespace
}
return core.Position{Line: 0, Col: i}
}