Merge pull request 'Implemented the replace (r and R) actions and replace mode' (#5) from feature/replace into master
All checks were successful
Run Test Suite / test (push) Successful in 16s
All checks were successful
Run Test Suite / test (push) Successful in 16s
Reviewed-on: #5
This commit is contained in:
commit
d6323be62b
@ -133,8 +133,8 @@
|
|||||||
- [ ] `U` - Undo all changes on line
|
- [ ] `U` - Undo all changes on line
|
||||||
|
|
||||||
### Other Normal Mode
|
### Other Normal Mode
|
||||||
- [ ] `r{char}` - Replace character
|
- [x] `r{char}` - Replace character
|
||||||
- [ ] `R` - Replace mode
|
- [x] `R` - Replace mode
|
||||||
- [ ] `~` - Swap case of character
|
- [ ] `~` - Swap case of character
|
||||||
- [ ] `ctrl+a` - Increment number
|
- [ ] `ctrl+a` - Increment number
|
||||||
- [ ] `ctrl+x` - Decrement number
|
- [ ] `ctrl+x` - Decrement number
|
||||||
|
|||||||
12
README.md
12
README.md
@ -64,6 +64,18 @@ While the undo tree method that vim uses is powerful, I rarely find myself using
|
|||||||
approach is more natural to "non-vim" users and much simpler to implement. Implementing a feature similar
|
approach is more natural to "non-vim" users and much simpler to implement. Implementing a feature similar
|
||||||
to Vims undo tree would many times longer than a simple stack.
|
to Vims undo tree would many times longer than a simple stack.
|
||||||
|
|
||||||
|
#### Vim-like Replace vs. Custom Replace
|
||||||
|
|
||||||
|
The way that vim's replace mode is implemented is quite complex, keeping track of the previous
|
||||||
|
line backspace can only delete newly replaced characters. This is a complex feature, one that
|
||||||
|
I rarely use, and even find a bit un-intuitive. Implementing replace mode in a way where all
|
||||||
|
actions function the same as insert mode (other than the actual character typing) allows for
|
||||||
|
a much simpler implementation, as well as a more intuitive user experience.
|
||||||
|
|
||||||
|
Replace mode implements and replaces (no pun intended) the last inserted keys of insert mode. Due to
|
||||||
|
the infrequent use of replace mode, and the '.' action for insert mode, this felt like a natural
|
||||||
|
trade off.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## TODO List
|
## TODO List
|
||||||
|
|||||||
129
internal/action/replace.go
Normal file
129
internal/action/replace.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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.SetMode(core.ReplaceMode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplaceModeChar struct {
|
||||||
|
Char string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceModeChar.Execute: Inserts a single character at the cursor position, overwriting current
|
||||||
|
// character.
|
||||||
|
func (a ReplaceModeChar) Execute(m Model) tea.Cmd {
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
|
x, y := win.Cursor.Col, win.Cursor.Line
|
||||||
|
l := buf.Line(y)
|
||||||
|
if x < len(l) {
|
||||||
|
buf.SetLine(y, l[:x]+a.Char+l[x+1:])
|
||||||
|
} else {
|
||||||
|
buf.SetLine(y, l+a.Char)
|
||||||
|
}
|
||||||
|
win.SetCursorCol(x + len(a.Char))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceNewline splits the current line at the cursor (enter key)
|
||||||
|
type ReplaceNewline struct{}
|
||||||
|
|
||||||
|
// ReplaceNewline.Execute: Splits the current line at the cursor (Enter key).
|
||||||
|
func (a ReplaceNewline) Execute(m Model) tea.Cmd {
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
|
x, y := win.Cursor.Col, win.Cursor.Line
|
||||||
|
l := buf.Line(y)
|
||||||
|
if x == len(l) {
|
||||||
|
buf.InsertLine(y+1, "")
|
||||||
|
} else {
|
||||||
|
buf.SetLine(y, l[:x])
|
||||||
|
buf.InsertLine(y+1, l[x+1:])
|
||||||
|
}
|
||||||
|
win.SetCursorPos(y+1, 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceTab inserts spaces equal to the tab size
|
||||||
|
type ReplaceTab struct{}
|
||||||
|
|
||||||
|
// ReplaceTab.Execute: Inserts spaces equal to the tab size (Tab key).
|
||||||
|
func (a ReplaceTab) Execute(m Model) tea.Cmd {
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
|
x, y := win.Cursor.Col, win.Cursor.Line
|
||||||
|
l := buf.Line(y)
|
||||||
|
tabs := strings.Repeat(" ", m.Settings().TabStop)
|
||||||
|
if x < len(l) {
|
||||||
|
buf.SetLine(y, l[:x]+tabs+l[x+1:])
|
||||||
|
} else {
|
||||||
|
buf.SetLine(y, l+tabs)
|
||||||
|
}
|
||||||
|
win.SetCursorCol(x + len(tabs))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -11,13 +11,15 @@ const (
|
|||||||
VisualMode
|
VisualMode
|
||||||
VisualLineMode
|
VisualLineMode
|
||||||
VisualBlockMode
|
VisualBlockMode
|
||||||
|
ReplaceMode
|
||||||
|
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mode.ToString: Returns a human-readable string representation of the mode
|
// Mode.ToString: Returns a human-readable string representation of the mode
|
||||||
// for display in the status bar.
|
// for display in the status bar.
|
||||||
func (m Mode) ToString() string {
|
func (m Mode) ToString() string {
|
||||||
switch m {
|
switch m {
|
||||||
case NormalMode:
|
case NormalMode, WaitingMode:
|
||||||
return "NORMAL"
|
return "NORMAL"
|
||||||
case InsertMode:
|
case InsertMode:
|
||||||
return "INSERT"
|
return "INSERT"
|
||||||
@ -29,6 +31,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 "-----"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,16 @@ func sendKeys(tm *teatest.TestModel, keys ...string) {
|
|||||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR})
|
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlR})
|
||||||
case "ctrl+w":
|
case "ctrl+w":
|
||||||
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW})
|
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW})
|
||||||
|
case "tab":
|
||||||
|
tm.Send(tea.KeyMsg{Type: tea.KeyTab})
|
||||||
|
case "left":
|
||||||
|
tm.Send(tea.KeyMsg{Type: tea.KeyLeft})
|
||||||
|
case "right":
|
||||||
|
tm.Send(tea.KeyMsg{Type: tea.KeyRight})
|
||||||
|
case "up":
|
||||||
|
tm.Send(tea.KeyMsg{Type: tea.KeyUp})
|
||||||
|
case "down":
|
||||||
|
tm.Send(tea.KeyMsg{Type: tea.KeyDown})
|
||||||
default:
|
default:
|
||||||
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
|
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
1040
internal/editor/integration_replace_test.go
Normal file
1040
internal/editor/integration_replace_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -11,76 +11,7 @@ type ModelBuilder struct {
|
|||||||
model Model
|
model Model
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPGLE
|
// NewModelBuilder: Builds and returns a new model, using the default color scheme (kanagawa-wave).
|
||||||
// abap
|
|
||||||
// algol
|
|
||||||
// algol_nu
|
|
||||||
// arduino
|
|
||||||
// ashen
|
|
||||||
// aura-theme-dark
|
|
||||||
// aura-theme-dark-soft
|
|
||||||
// autumn
|
|
||||||
// average
|
|
||||||
// base16-snazzy
|
|
||||||
// borland
|
|
||||||
// bw
|
|
||||||
// catppuccin-frappe
|
|
||||||
// catppuccin-latte
|
|
||||||
// catppuccin-macchiato
|
|
||||||
// catppuccin-mocha
|
|
||||||
// colorful
|
|
||||||
// doom-one
|
|
||||||
// doom-one2
|
|
||||||
// dracula
|
|
||||||
// emacs
|
|
||||||
// evergarden
|
|
||||||
// friendly
|
|
||||||
// fruity
|
|
||||||
// github
|
|
||||||
// github-dark
|
|
||||||
// gruvbox
|
|
||||||
// gruvbox-light
|
|
||||||
// hr_high_contrast
|
|
||||||
// hrdark
|
|
||||||
// igor
|
|
||||||
// lovelace
|
|
||||||
// manni
|
|
||||||
// modus-operandi
|
|
||||||
// modus-vivendi
|
|
||||||
// monokai
|
|
||||||
// monokailight
|
|
||||||
// murphy
|
|
||||||
// native
|
|
||||||
// nord
|
|
||||||
// nordic
|
|
||||||
// onedark
|
|
||||||
// onesenterprise
|
|
||||||
// paraiso-dark
|
|
||||||
// paraiso-light
|
|
||||||
// pastie
|
|
||||||
// perldoc
|
|
||||||
// pygments
|
|
||||||
// rainbow_dash
|
|
||||||
// rose-pine
|
|
||||||
// rose-pine-dawn
|
|
||||||
// rose-pine-moon
|
|
||||||
// rrt
|
|
||||||
// solarized-dark
|
|
||||||
// solarized-dark256
|
|
||||||
// solarized-light
|
|
||||||
// swapoff
|
|
||||||
// tango
|
|
||||||
// tokyonight-day
|
|
||||||
// tokyonight-moon
|
|
||||||
// tokyonight-night
|
|
||||||
// tokyonight-storm
|
|
||||||
// trac
|
|
||||||
// vim
|
|
||||||
// vs
|
|
||||||
// vulcan
|
|
||||||
// witchhazel
|
|
||||||
// xcode
|
|
||||||
// xcode-dark
|
|
||||||
func NewModelBuilder() *ModelBuilder {
|
func NewModelBuilder() *ModelBuilder {
|
||||||
chromaStyle := styles.Get("kanagawa-wave")
|
chromaStyle := styles.Get("kanagawa-wave")
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,7 @@ type Handler struct {
|
|||||||
normalKeymap *Keymap
|
normalKeymap *Keymap
|
||||||
visualKeymap *Keymap
|
visualKeymap *Keymap
|
||||||
insertKeymap *Keymap
|
insertKeymap *Keymap
|
||||||
|
replaceKeymap *Keymap
|
||||||
commandKeymap *Keymap
|
commandKeymap *Keymap
|
||||||
|
|
||||||
currentKeymap *Keymap
|
currentKeymap *Keymap
|
||||||
@ -53,6 +54,7 @@ func NewHandler() *Handler {
|
|||||||
normalKeymap: NewNormalKeymap(),
|
normalKeymap: NewNormalKeymap(),
|
||||||
visualKeymap: NewVisualKeymap(),
|
visualKeymap: NewVisualKeymap(),
|
||||||
insertKeymap: NewInsertKeymap(),
|
insertKeymap: NewInsertKeymap(),
|
||||||
|
replaceKeymap: NewReplaceKeymap(),
|
||||||
commandKeymap: NewCommandKeymap(),
|
commandKeymap: NewCommandKeymap(),
|
||||||
currentKeymap: nil,
|
currentKeymap: nil,
|
||||||
}
|
}
|
||||||
@ -77,7 +79,7 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
|
|
||||||
h.recordingKeys = []string{} // Clear recording on ESC
|
h.recordingKeys = []string{} // Clear recording on ESC
|
||||||
h.Reset()
|
h.Reset()
|
||||||
if m.Mode() == core.InsertMode {
|
if m.Mode() == core.InsertMode || m.Mode() == core.ReplaceMode {
|
||||||
// Before exiting insert mode, end the block in the undo stack
|
// Before exiting insert mode, end the block in the undo stack
|
||||||
win := m.ActiveWindow()
|
win := m.ActiveWindow()
|
||||||
buf := m.ActiveBuffer()
|
buf := m.ActiveBuffer()
|
||||||
@ -95,6 +97,8 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
switch m.Mode() {
|
switch m.Mode() {
|
||||||
case core.InsertMode:
|
case core.InsertMode:
|
||||||
return h.handleInsertKey(m, key)
|
return h.handleInsertKey(m, key)
|
||||||
|
case core.ReplaceMode:
|
||||||
|
return h.handleReplaceKey(m, key)
|
||||||
case core.CommandMode:
|
case core.CommandMode:
|
||||||
return h.handleCommandKey(m, key)
|
return h.handleCommandKey(m, key)
|
||||||
}
|
}
|
||||||
@ -172,6 +176,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.WaitingMode)
|
||||||
|
}
|
||||||
h.charMotionType = key
|
h.charMotionType = key
|
||||||
h.state = StateWaitingForChar
|
h.state = StateWaitingForChar
|
||||||
return nil
|
return nil
|
||||||
@ -362,7 +369,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 +389,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)
|
||||||
h.Reset()
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,6 +574,32 @@ func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
|
|||||||
return action.InsertChar{Char: key}.Execute(m)
|
return action.InsertChar{Char: key}.Execute(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleReplaceKey(m action.Model, key string) tea.Cmd {
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
|
||||||
|
// Start undo block on first insert key
|
||||||
|
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
|
||||||
|
buf.UndoStack.BeginBlock(win.Cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the key for count replay (e.g. 5i...)
|
||||||
|
m.SetInsertKeys(append(m.InsertKeys(), key))
|
||||||
|
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
|
||||||
|
|
||||||
|
// Check the insert keymap first
|
||||||
|
kind, binding := h.replaceKeymap.Lookup(key)
|
||||||
|
switch kind {
|
||||||
|
case "action":
|
||||||
|
return binding.(action.Action).Execute(m)
|
||||||
|
case "motion":
|
||||||
|
return binding.(action.Motion).Execute(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: treat as a regular character to "insert"
|
||||||
|
return action.ReplaceModeChar{Char: key}.Execute(m)
|
||||||
|
}
|
||||||
|
|
||||||
// Handler.handleCommandKey: Processes a keypress in command mode, executing
|
// Handler.handleCommandKey: Processes a keypress in command mode, executing
|
||||||
// it as an action or inserting it into the command line. This does not record
|
// it as an action or inserting it into the command line. This does not record
|
||||||
// anything into the undo stack.
|
// anything into the undo stack.
|
||||||
|
|||||||
@ -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,
|
||||||
@ -146,6 +148,8 @@ func NewVisualKeymap() *Keymap {
|
|||||||
"X": operator.DeleteOperator{},
|
"X": operator.DeleteOperator{},
|
||||||
"y": operator.YankOperator{},
|
"y": operator.YankOperator{},
|
||||||
"c": operator.ChangeOperator{},
|
"c": operator.ChangeOperator{},
|
||||||
|
"s": operator.ChangeOperator{}, // Same as c in visual mode
|
||||||
|
"R": operator.ChangeOperator{}, // Seems to do the same thing
|
||||||
},
|
},
|
||||||
actions: map[string]action.Action{
|
actions: map[string]action.Action{
|
||||||
"p": action.VisualPaste{Count: 1, Replace: true},
|
"p": action.VisualPaste{Count: 1, Replace: true},
|
||||||
@ -202,7 +206,26 @@ func NewInsertKeymap() *Keymap {
|
|||||||
"ctrl+w": action.InsertDeletePreviousWord{},
|
"ctrl+w": action.InsertDeletePreviousWord{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReplaceKeymap: Creates a keymap for replace mode with editing actions. All 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.ReplaceNewline{},
|
||||||
|
"backspace": action.InsertBackspace{},
|
||||||
|
"delete": action.InsertDelete{},
|
||||||
|
"tab": action.ReplaceTab{}, // TODO: This needs replacing
|
||||||
|
"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,11 @@ 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, core.WaitingMode:
|
||||||
|
return lipgloss.NewStyle().
|
||||||
|
Background(style.GetBackground()).
|
||||||
|
Foreground(style.GetForeground()).
|
||||||
|
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