feat: implement the r action, tested
All checks were successful
Run Test Suite / test (push) Successful in 17s
All checks were successful
Run Test Suite / test (push) Successful in 17s
Began work on replace mode, but not complete.
This commit is contained in:
parent
58082afdd2
commit
6033e58d0e
72
internal/action/replace.go
Normal file
72
internal/action/replace.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReplaceChar struct {
|
||||||
|
Char string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m ReplaceChar) WithChar(char string) Motion {
|
||||||
|
m.Char = char
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m ReplaceChar) Type() core.MotionType {
|
||||||
|
return core.CharwiseInclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCount sets the count (required by Repeatable interface)
|
||||||
|
func (m ReplaceChar) WithCount(n int) Action {
|
||||||
|
m.Count = n
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ReplaceChar) Execute(m Model) tea.Cmd {
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
|
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
|
||||||
|
buf.UndoStack.BeginBlock(win.Cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := win.Cursor.Col
|
||||||
|
line := buf.Line(win.Cursor.Line)
|
||||||
|
for i := 0; i < a.Count && pos < len(line); i++ {
|
||||||
|
line = line[:pos] + a.Char + line[pos+1:]
|
||||||
|
buf.SetLine(win.Cursor.Line, line)
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
win.SetCursorCol(pos - 1)
|
||||||
|
m.SetMode(core.NormalMode)
|
||||||
|
|
||||||
|
if buf.UndoStack != nil {
|
||||||
|
buf.UndoStack.EndBlock(win.Cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnterReplace struct {
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a EnterReplace) WithCount(n int) Action {
|
||||||
|
a.Count = n
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a EnterReplace) Execute(m Model) tea.Cmd {
|
||||||
|
|
||||||
|
m.SetCommandOutput(&core.CommandOutput{
|
||||||
|
Lines: []string{"Replace mode (R) not implemented yet"},
|
||||||
|
Inline: true,
|
||||||
|
IsError: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ const (
|
|||||||
VisualMode
|
VisualMode
|
||||||
VisualLineMode
|
VisualLineMode
|
||||||
VisualBlockMode
|
VisualBlockMode
|
||||||
|
ReplaceMode
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mode.ToString: Returns a human-readable string representation of the mode
|
// Mode.ToString: Returns a human-readable string representation of the mode
|
||||||
@ -29,6 +30,8 @@ func (m Mode) ToString() string {
|
|||||||
return "V-LINE"
|
return "V-LINE"
|
||||||
case VisualBlockMode:
|
case VisualBlockMode:
|
||||||
return "V-BLOCK"
|
return "V-BLOCK"
|
||||||
|
case ReplaceMode:
|
||||||
|
return "REPLACE"
|
||||||
default:
|
default:
|
||||||
return "-----"
|
return "-----"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,7 +176,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
|||||||
sendKeys(tm, "$")
|
sendKeys(tm, "$")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
want := len(lines[0])
|
want := len(lines[0]) - 1
|
||||||
if m.ActiveWindow().Cursor.Col != want {
|
if m.ActiveWindow().Cursor.Col != want {
|
||||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
|||||||
sendKeys(tm, "$")
|
sendKeys(tm, "$")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
want := len(lines[0])
|
want := len(lines[0]) - 1
|
||||||
if m.ActiveWindow().Cursor.Col != want {
|
if m.ActiveWindow().Cursor.Col != want {
|
||||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||||
}
|
}
|
||||||
@ -200,7 +200,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
|||||||
sendKeys(tm, "$")
|
sendKeys(tm, "$")
|
||||||
|
|
||||||
m := getFinalModel(t, tm)
|
m := getFinalModel(t, tm)
|
||||||
want := len(lines[0])
|
want := len(lines[0]) - 1
|
||||||
if m.ActiveWindow().Cursor.Col != want {
|
if m.ActiveWindow().Cursor.Col != want {
|
||||||
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
t.Errorf("CursorX() = %d, want %d", m.ActiveWindow().Cursor.Col, want)
|
||||||
}
|
}
|
||||||
|
|||||||
566
internal/editor/integration_replace_test.go
Normal file
566
internal/editor/integration_replace_test.go
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
package editor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReplaceChar(t *testing.T) {
|
||||||
|
t.Run("test 'rx' replaces character under cursor", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' in middle of line", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hexlo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hexlo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' at end of line", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hellx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hellx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' cursor position remains at replaced char", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().Cursor.Col != 2 {
|
||||||
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' stays in normal mode", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.Mode() != core.NormalMode {
|
||||||
|
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' with space replaces with space", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", " ")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != " ello" {
|
||||||
|
t.Errorf("lines[0] = %q, want ' ello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' with digit replaces with digit", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "5")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "5ello" {
|
||||||
|
t.Errorf("lines[0] = %q, want '5ello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' with special char replaces correctly", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "@")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "@ello" {
|
||||||
|
t.Errorf("lines[0] = %q, want '@ello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test multiple 'r' operations in sequence", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "l", "r", "y")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xyllo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xyllo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharWithCount(t *testing.T) {
|
||||||
|
t.Run("test '3rx' replaces three characters", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "3", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxxlo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxxlo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '3rx' cursor position after replace", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "3", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Cursor should be at last replaced character
|
||||||
|
if m.ActiveWindow().Cursor.Col != 2 {
|
||||||
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '5rx' replaces all five characters", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "5", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxxxx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxxxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '10rx' with overflow stops at line end", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "1", "0", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Should only replace 5 chars (all available)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxxxx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxxxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '2rx' from middle", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "2", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hxxlo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hxxlo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '2rx' cursor position from middle", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "2", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveWindow().Cursor.Col != 2 {
|
||||||
|
t.Errorf("CursorX() = %d, want 2", m.ActiveWindow().Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '4rx' from near end stops at line end", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||||
|
sendKeys(tm, "4", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Should only replace 2 chars (3 and 4)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "helxx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'helxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test large count '100rx' doesn't crash", func(t *testing.T) {
|
||||||
|
lines := []string{"short"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "1", "0", "0", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxxxx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxxxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharEdgeCases(t *testing.T) {
|
||||||
|
t.Run("test 'r' on empty line does nothing", func(t *testing.T) {
|
||||||
|
lines := []string{""}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "" {
|
||||||
|
t.Errorf("Line(0) = %q, want ''", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
if m.ActiveWindow().Cursor.Col != 0 {
|
||||||
|
t.Errorf("CursorX() = %d, want 0", m.ActiveWindow().Cursor.Col)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' on single character line", func(t *testing.T) {
|
||||||
|
lines := []string{"a"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "x" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'x'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' at last char replaces it", func(t *testing.T) {
|
||||||
|
lines := []string{"ab"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "ax" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'ax'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' with whitespace", func(t *testing.T) {
|
||||||
|
lines := []string{"a b c"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "axb c" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'axb c'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' preserves other lines", func(t *testing.T) {
|
||||||
|
lines := []string{"hello", "world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().LineCount() != 2 {
|
||||||
|
t.Errorf("LineCount() = %d, want 2", m.ActiveBuffer().LineCount())
|
||||||
|
}
|
||||||
|
if m.ActiveBuffer().Lines[1].String() != "world" {
|
||||||
|
t.Errorf("Line(1) = %q, want 'world'", m.ActiveBuffer().Lines[1].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' on line with tabs", func(t *testing.T) {
|
||||||
|
lines := []string{"a\tb"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "axb" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'axb'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '5rx' with only 3 chars available", func(t *testing.T) {
|
||||||
|
lines := []string{"abc"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "5", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxx" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'xxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' in middle preserves surrounding chars", func(t *testing.T) {
|
||||||
|
lines := []string{"abcde"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "abxde" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'abxde'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' on whitespace-only line", func(t *testing.T) {
|
||||||
|
lines := []string{" "}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != " x " {
|
||||||
|
t.Errorf("Line(0) = %q, want ' x '", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' on different lines independently", func(t *testing.T) {
|
||||||
|
lines := []string{"hello", "world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "j", "r", "y")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xello" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'xello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
if m.ActiveBuffer().Lines[1].String() != "yorld" {
|
||||||
|
t.Errorf("Line(1) = %q, want 'yorld'", m.ActiveBuffer().Lines[1].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' does not affect line count", func(t *testing.T) {
|
||||||
|
lines := []string{"line 1", "line 2", "line 3"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().LineCount() != 3 {
|
||||||
|
t.Errorf("LineCount() = %d, want 3", m.ActiveBuffer().LineCount())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' with newline character is treated as single char", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
// Note: In vim, 'r' with enter doesn't work the same way
|
||||||
|
// This test documents the current behavior
|
||||||
|
sendKeys(tm, "r", "\n")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Replacing with "\n" string (not actual newline)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "\nello" {
|
||||||
|
t.Errorf("Line(0) = %q, want '\\nello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharWithMotion(t *testing.T) {
|
||||||
|
t.Run("test 'lrx' moves then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "l", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hxllo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hxllo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'wrx' moves to next word start then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "w", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hello xorld" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hello xorld'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '$rx' moves to end then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "$", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hellx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hellx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '2lrx' moves 2 right then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "2", "l", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hexlo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hexlo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharUndo(t *testing.T) {
|
||||||
|
t.Run("test 'rx' can be undone with 'u'", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "u")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '3rx' can be undone with 'u'", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "3", "r", "x", "u")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test multiple 'r' operations can be undone separately", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "l", "r", "y", "u")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Only the last 'ry' should be undone
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test undo then redo 'rx'", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "u", "ctrl+r")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharRepeat(t *testing.T) {
|
||||||
|
t.Run("test 'rx' can be repeated with '.'", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "x", "l", ".")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxllo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxllo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '3rx' can be repeated with '.'", func(t *testing.T) {
|
||||||
|
lines := []string{"hello world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "3", "r", "x", "w", ".")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// First 3rx at position 0, second at position 6
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxxlo xxxld" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxxlo xxxld'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '.' repeats last replace char", func(t *testing.T) {
|
||||||
|
lines := []string{"hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "r", "a", "l", ".", "l", ".")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "aaalo" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'aaalo'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE: This is the same as Vim's handling, but that is okay, I like this more, feels more honest
|
||||||
|
t.Run("test '.' with count multiplies", func(t *testing.T) {
|
||||||
|
lines := []string{"hello world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "2", "r", "x", "w", "3", ".")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "xxllo xxxxx" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'xxllo xxxxx'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharMultiLine(t *testing.T) {
|
||||||
|
t.Run("test 'r' on second line", func(t *testing.T) {
|
||||||
|
lines := []string{"first", "second"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "first" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'first'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
if m.ActiveBuffer().Lines[1].String() != "xecond" {
|
||||||
|
t.Errorf("Line(1) = %q, want 'xecond'", m.ActiveBuffer().Lines[1].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' does not cross line boundaries", func(t *testing.T) {
|
||||||
|
lines := []string{"ab", "cd"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||||
|
sendKeys(tm, "5", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Should only replace 'b', not cross to next line
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "ax" {
|
||||||
|
t.Errorf("Line(0) = %q, want 'ax'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
if m.ActiveBuffer().Lines[1].String() != "cd" {
|
||||||
|
t.Errorf("Line(1) = %q, want 'cd'", m.ActiveBuffer().Lines[1].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test 'r' on last line", func(t *testing.T) {
|
||||||
|
lines := []string{"first", "second", "third"}
|
||||||
|
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||||
|
sendKeys(tm, "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[2].String() != "xhird" {
|
||||||
|
t.Errorf("Line(2) = %q, want 'xhird'", m.ActiveBuffer().Lines[2].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceCharCombinations(t *testing.T) {
|
||||||
|
t.Run("test 'frx' finds then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello world"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "f", "w", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "hello xorld" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'hello xorld'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '2frx' finds second occurrence then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{"hello hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "2", "f", "l", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
// Find second 'l', which is at position 3
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != "helxo hello" {
|
||||||
|
t.Errorf("lines[0] = %q, want 'helxo hello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test '^rx' moves to first non-blank then replaces", func(t *testing.T) {
|
||||||
|
lines := []string{" hello"}
|
||||||
|
tm := newTestModelWithLines(t, lines)
|
||||||
|
sendKeys(tm, "^", "r", "x")
|
||||||
|
|
||||||
|
m := getFinalModel(t, tm)
|
||||||
|
if m.ActiveBuffer().Lines[0].String() != " xello" {
|
||||||
|
t.Errorf("lines[0] = %q, want ' xello'", m.ActiveBuffer().Lines[0].String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -398,9 +398,8 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
|||||||
if m.ActiveWindow().Anchor.Col != 0 {
|
if m.ActiveWindow().Anchor.Col != 0 {
|
||||||
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
t.Errorf("AnchorX() = %d, want 0", m.ActiveWindow().Anchor.Col)
|
||||||
}
|
}
|
||||||
// $ moves past end of line
|
if m.ActiveWindow().Cursor.Col != 10 {
|
||||||
if m.ActiveWindow().Cursor.Col != 11 {
|
t.Errorf("CursorX() = %d, want 10", m.ActiveWindow().Cursor.Col)
|
||||||
t.Errorf("CursorX() = %d, want 11", m.ActiveWindow().Cursor.Col)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -172,6 +172,9 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
|
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
|
||||||
// Handle character motions (f/t/F/T) - transition to waiting state
|
// Handle character motions (f/t/F/T) - transition to waiting state
|
||||||
if kind == "char_motion" {
|
if kind == "char_motion" {
|
||||||
|
if key == "r" {
|
||||||
|
m.SetMode(core.ReplaceMode)
|
||||||
|
}
|
||||||
h.charMotionType = key
|
h.charMotionType = key
|
||||||
h.state = StateWaitingForChar
|
h.state = StateWaitingForChar
|
||||||
return nil
|
return nil
|
||||||
@ -362,7 +365,11 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
|
|||||||
|
|
||||||
// Apply count if supported
|
// Apply count if supported
|
||||||
if r, ok := mot.(action.Repeatable); ok {
|
if r, ok := mot.(action.Repeatable); ok {
|
||||||
mot = r.WithCount(count).(action.Motion)
|
result := r.WithCount(count)
|
||||||
|
// WithCount returns Action, but char motions still implement Motion
|
||||||
|
if m, ok := result.(action.Motion); ok {
|
||||||
|
mot = m
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If operator pending (e.g., "df{char}"), get range and operate
|
// If operator pending (e.g., "df{char}"), get range and operate
|
||||||
@ -378,7 +385,14 @@ func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
|
|||||||
|
|
||||||
// Otherwise just execute the motion
|
// Otherwise just execute the motion
|
||||||
cmd := h.executeMotion(m, mot)
|
cmd := h.executeMotion(m, mot)
|
||||||
|
|
||||||
|
// ReplaceChar modifies the buffer, so it should be repeatable with '.'
|
||||||
|
// (unlike f/t/F/T which are pure motions)
|
||||||
|
if _, isReplace := mot.(action.ReplaceChar); isReplace {
|
||||||
|
h.RecordAndReset(m)
|
||||||
|
} else {
|
||||||
h.Reset()
|
h.Reset()
|
||||||
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,12 +77,14 @@ func NewNormalKeymap() *Keymap {
|
|||||||
"u": action.Undo{},
|
"u": action.Undo{},
|
||||||
"ctrl+r": action.Redo{},
|
"ctrl+r": action.Redo{},
|
||||||
".": action.Repeat{Count: 1},
|
".": action.Repeat{Count: 1},
|
||||||
|
"R": action.EnterReplace{},
|
||||||
},
|
},
|
||||||
charMotions: map[string]action.Motion{
|
charMotions: map[string]action.Motion{
|
||||||
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
|
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
|
||||||
"F": action.FindChar{Forward: false, Inclusive: true, Repeated: false},
|
"F": action.FindChar{Forward: false, Inclusive: true, Repeated: false},
|
||||||
"t": action.FindChar{Forward: true, Inclusive: false, Repeated: false},
|
"t": action.FindChar{Forward: true, Inclusive: false, Repeated: false},
|
||||||
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
|
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
|
||||||
|
"r": action.ReplaceChar{Count: 1},
|
||||||
},
|
},
|
||||||
modifiers: map[string]any{
|
modifiers: map[string]any{
|
||||||
"i": nil,
|
"i": nil,
|
||||||
@ -202,7 +204,26 @@ func NewInsertKeymap() *Keymap {
|
|||||||
"ctrl+w": action.InsertDeletePreviousWord{},
|
"ctrl+w": action.InsertDeletePreviousWord{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReplaceKeymap: Creates a keymap for replace mode with editing actions.
|
||||||
|
func NewReplaceKeymap() *Keymap {
|
||||||
|
return &Keymap{
|
||||||
|
motions: map[string]action.Motion{
|
||||||
|
"down": motion.MoveDown{Count: 1},
|
||||||
|
"up": motion.MoveUp{Count: 1},
|
||||||
|
"left": motion.MoveLeft{Count: 1},
|
||||||
|
"right": motion.MoveRight{Count: 1},
|
||||||
|
},
|
||||||
|
operators: map[string]action.Operator{}, // this will likely be empty
|
||||||
|
actions: map[string]action.Action{
|
||||||
|
"enter": action.InsertNewline{},
|
||||||
|
"backspace": action.InsertBackspace{},
|
||||||
|
"delete": action.InsertDelete{},
|
||||||
|
"tab": action.InsertTab{},
|
||||||
|
"ctrl+w": action.InsertDeletePreviousWord{},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommandKeymap: Creates a keymap for command mode with command line editing.
|
// NewCommandKeymap: Creates a keymap for command mode with command line editing.
|
||||||
|
|||||||
@ -50,7 +50,7 @@ type MoveToLineEnd struct{}
|
|||||||
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
|
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
|
||||||
win := m.ActiveWindow()
|
win := m.ActiveWindow()
|
||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len())
|
win.SetCursorCol(buf.Lines[win.Cursor.Line].Len() - 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type Styles struct {
|
|||||||
CursorNormal lipgloss.Style
|
CursorNormal lipgloss.Style
|
||||||
CursorInsert lipgloss.Style
|
CursorInsert lipgloss.Style
|
||||||
CursorCommand lipgloss.Style
|
CursorCommand lipgloss.Style
|
||||||
|
CursorReplace lipgloss.Style
|
||||||
|
|
||||||
// Gutter (line numbers)
|
// Gutter (line numbers)
|
||||||
Gutter lipgloss.Style
|
Gutter lipgloss.Style
|
||||||
@ -47,6 +48,7 @@ func DefaultStyles() Styles {
|
|||||||
CursorNormal: lipgloss.NewStyle().Reverse(true),
|
CursorNormal: lipgloss.NewStyle().Reverse(true),
|
||||||
CursorInsert: lipgloss.NewStyle().Underline(true),
|
CursorInsert: lipgloss.NewStyle().Underline(true),
|
||||||
CursorCommand: lipgloss.NewStyle().Reverse(true),
|
CursorCommand: lipgloss.NewStyle().Reverse(true),
|
||||||
|
CursorReplace: lipgloss.NewStyle().Underline(true),
|
||||||
|
|
||||||
Gutter: lipgloss.NewStyle().
|
Gutter: lipgloss.NewStyle().
|
||||||
Background(lipgloss.Color("236")).
|
Background(lipgloss.Color("236")).
|
||||||
@ -95,12 +97,17 @@ func ChromaStyles(chromaStyle *chroma.Style) Styles {
|
|||||||
|
|
||||||
CursorInsert: lipgloss.NewStyle().
|
CursorInsert: lipgloss.NewStyle().
|
||||||
Background(lipgloss.Color(bgString)).
|
Background(lipgloss.Color(bgString)).
|
||||||
|
Bold(true).
|
||||||
Underline(true),
|
Underline(true),
|
||||||
|
|
||||||
CursorCommand: lipgloss.NewStyle().
|
CursorCommand: lipgloss.NewStyle().
|
||||||
Background(lipgloss.Color(bgString)).
|
Background(lipgloss.Color(bgString)).
|
||||||
Reverse(true),
|
Reverse(true),
|
||||||
|
|
||||||
|
CursorReplace: lipgloss.NewStyle().
|
||||||
|
Background(lipgloss.Color(bgString)).
|
||||||
|
Underline(true),
|
||||||
|
|
||||||
Gutter: lipgloss.NewStyle().
|
Gutter: lipgloss.NewStyle().
|
||||||
Background(lipgloss.Color(
|
Background(lipgloss.Color(
|
||||||
darkenColor(lineNumbers.Background, 0.9).String()),
|
darkenColor(lineNumbers.Background, 0.9).String()),
|
||||||
@ -163,6 +170,8 @@ func (s Styles) DefaultCursorStyle(mode core.Mode) lipgloss.Style {
|
|||||||
return s.CursorInsert
|
return s.CursorInsert
|
||||||
case core.CommandMode:
|
case core.CommandMode:
|
||||||
return s.CursorCommand
|
return s.CursorCommand
|
||||||
|
case core.ReplaceMode:
|
||||||
|
return s.CursorReplace
|
||||||
default:
|
default:
|
||||||
return s.CursorNormal
|
return s.CursorNormal
|
||||||
}
|
}
|
||||||
@ -177,6 +186,9 @@ func (s Styles) CursorStyle(mode core.Mode, style lipgloss.Style) lipgloss.Style
|
|||||||
return lipgloss.NewStyle().
|
return lipgloss.NewStyle().
|
||||||
Background(style.GetForeground()).
|
Background(style.GetForeground()).
|
||||||
Foreground(style.GetBackground())
|
Foreground(style.GetBackground())
|
||||||
|
case core.ReplaceMode:
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Underline(true)
|
||||||
default:
|
default:
|
||||||
return lipgloss.NewStyle().
|
return lipgloss.NewStyle().
|
||||||
Background(s.BackgroundStyle.GetBackground()).
|
Background(s.BackgroundStyle.GetBackground()).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user