Compare commits
2 Commits
1aa1954d35
...
501d15e410
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
501d15e410 | ||
|
|
514c77c1af |
@ -46,6 +46,12 @@ type Model interface {
|
|||||||
CommandHistoryCursor() int
|
CommandHistoryCursor() int
|
||||||
SetCommandHistoryCursor(cur int)
|
SetCommandHistoryCursor(cur int)
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Search Mode State
|
||||||
|
// ==================================================
|
||||||
|
SearchState() core.SearchState
|
||||||
|
SetSearchState(s core.SearchState)
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
// ==================================================
|
// ==================================================
|
||||||
|
|||||||
178
internal/action/search.go
Normal file
178
internal/action/search.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package action
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EnterSearchMode struct {
|
||||||
|
Forward bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a EnterSearchMode) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
search.Forword = a.Forward
|
||||||
|
|
||||||
|
// BUG: Not sure if this is safe?
|
||||||
|
m.SetCommandOutput(nil)
|
||||||
|
|
||||||
|
m.SetSearchState(search)
|
||||||
|
m.SetMode(core.SearchMode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExitSearchMode struct{}
|
||||||
|
|
||||||
|
func (a ExitSearchMode) Execute(m Model) tea.Cmd {
|
||||||
|
// Reset state
|
||||||
|
search := m.SearchState()
|
||||||
|
|
||||||
|
if strings.TrimSpace(search.Query) != "" {
|
||||||
|
search.History = append(search.History, search.Query)
|
||||||
|
}
|
||||||
|
search.Cursor = 0
|
||||||
|
search.Query = ""
|
||||||
|
search.HistoryCursor = 0
|
||||||
|
|
||||||
|
// TODO: Maybe we want to keep Query until we enter it again next, for N and n?
|
||||||
|
|
||||||
|
m.SetSearchState(search)
|
||||||
|
m.SetMode(core.NormalMode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsertSearchChar struct {
|
||||||
|
Char string
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertSearchChar.Execute: Inserts a character at the search cursor position.
|
||||||
|
func (a InsertSearchChar) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
|
||||||
|
cur := search.Cursor
|
||||||
|
query := search.Query
|
||||||
|
|
||||||
|
search.Query = query[:cur] + a.Char + query[cur:]
|
||||||
|
search.Cursor++
|
||||||
|
|
||||||
|
m.SetSearchState(search)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchBackspace implements Action - deletes character before cursor in search mode.
|
||||||
|
type SearchBackspace struct{}
|
||||||
|
|
||||||
|
// SearchBackspace.Execute: Deletes the character before the search cursor (Backspace key).
|
||||||
|
func (a SearchBackspace) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
|
||||||
|
cur := search.Cursor
|
||||||
|
query := search.Query
|
||||||
|
|
||||||
|
if cur > 0 {
|
||||||
|
search.Query = query[:cur-1] + query[cur:]
|
||||||
|
search.Cursor--
|
||||||
|
m.SetSearchState(search)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchDelete implements Action - deletes character at cursor in search mode.
|
||||||
|
type SearchDelete struct{}
|
||||||
|
|
||||||
|
// SearchDelete.Execute: Deletes the character at the command cursor (Delete key).
|
||||||
|
func (a SearchDelete) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
cur := search.Cursor
|
||||||
|
query := search.Query
|
||||||
|
|
||||||
|
if cur < len(query)-1 {
|
||||||
|
search.Query = query[:cur+1] + query[cur+2:]
|
||||||
|
} else if cur == len(query)-1 {
|
||||||
|
// last text char, delete it
|
||||||
|
search.Query = query[:cur] + query[cur+1:]
|
||||||
|
} else if cur == len(query) && cur > 0 {
|
||||||
|
// if at end, we do backspace op
|
||||||
|
search.Query = query[:cur-1] + query[cur:]
|
||||||
|
search.Cursor = max(0, search.Cursor-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetSearchState(search)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchDeletePreviousWord implements Action - deletes word before cursor in search mode.
|
||||||
|
type SearchDeletePreviousWord struct{}
|
||||||
|
|
||||||
|
// SearchDeletePreviousWord.Execute: Deletes the word before the search cursor (Ctrl+W).
|
||||||
|
func (a SearchDeletePreviousWord) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
cur := search.Cursor
|
||||||
|
cmd := search.Query
|
||||||
|
|
||||||
|
if cur > 0 {
|
||||||
|
newCur := cur
|
||||||
|
|
||||||
|
// If we are on punctuation, we should just skip them all and quit
|
||||||
|
if isPunctuation(cmd[newCur-1]) {
|
||||||
|
for newCur > 0 && isPunctuation(cmd[newCur-1]) {
|
||||||
|
newCur--
|
||||||
|
}
|
||||||
|
|
||||||
|
search.Query = cmd[:newCur] + cmd[cur:]
|
||||||
|
search.Cursor = newCur
|
||||||
|
m.SetSearchState(search)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip whitespace immediately before the cursor
|
||||||
|
for newCur > 0 && (cmd[newCur-1] == ' ' || cmd[newCur-1] == '\t') {
|
||||||
|
newCur--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the word characters before the cursor
|
||||||
|
for newCur > 0 && isWordChar(cmd[newCur-1]) {
|
||||||
|
newCur--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete everything from newCur up to cur in one operation
|
||||||
|
search.Query = cmd[:newCur] + cmd[cur:]
|
||||||
|
search.Cursor = newCur
|
||||||
|
m.SetSearchState(search)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchExecute struct{}
|
||||||
|
|
||||||
|
func (a SearchExecute) Execute(m Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
|
||||||
|
// Exit normally
|
||||||
|
defer func() {
|
||||||
|
act := ExitSearchMode{}
|
||||||
|
act.Execute(m)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !search.Forword {
|
||||||
|
m.SetCommandOutput(&core.CommandOutput{
|
||||||
|
Lines: []string{"reverse search not implemented yet."},
|
||||||
|
Inline: true,
|
||||||
|
IsError: true,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the search
|
||||||
|
win := m.ActiveWindow()
|
||||||
|
buf := m.ActiveBuffer()
|
||||||
|
|
||||||
|
x, y := win.Cursor.Col, win.Cursor.Line
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -959,3 +959,27 @@ func cmdUndoList(m action.Model, args []string, force bool) tea.Cmd {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cmdSearch(m action.Model, args []string, force bool) tea.Cmd {
|
||||||
|
_, _ = args, force
|
||||||
|
|
||||||
|
search := m.SearchState()
|
||||||
|
|
||||||
|
lines := []string{
|
||||||
|
fmt.Sprintf("Query: %s", search.Query),
|
||||||
|
fmt.Sprintf("Forward: %v", search.Forword),
|
||||||
|
fmt.Sprintf("Cursor: %d", search.Cursor),
|
||||||
|
fmt.Sprintf("HistoryCursor: %d", search.HistoryCursor),
|
||||||
|
fmt.Sprintf("History: %q", search.History),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetMode(core.CommandOutputMode)
|
||||||
|
m.SetCommandOutput(&core.CommandOutput{
|
||||||
|
Title: ":search",
|
||||||
|
Lines: lines,
|
||||||
|
Inline: false,
|
||||||
|
IsError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -244,4 +244,10 @@ func (r *Registry) registerDefaults() {
|
|||||||
ShortForm: "u",
|
ShortForm: "u",
|
||||||
Handler: cmdUndoList,
|
Handler: cmdUndoList,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Register(Command{
|
||||||
|
Name: "search",
|
||||||
|
ShortForm: "s",
|
||||||
|
Handler: cmdSearch,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const (
|
|||||||
VisualBlockMode
|
VisualBlockMode
|
||||||
ReplaceMode
|
ReplaceMode
|
||||||
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
|
WaitingMode // Same as NORMAL output, but cursor is the REPLACE cursor
|
||||||
|
SearchMode
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mode.ToString: Returns a human-readable string representation of the mode
|
// Mode.ToString: Returns a human-readable string representation of the mode
|
||||||
@ -25,6 +26,8 @@ func (m Mode) ToString() string {
|
|||||||
return "INSERT"
|
return "INSERT"
|
||||||
case CommandMode:
|
case CommandMode:
|
||||||
return "COMMAND"
|
return "COMMAND"
|
||||||
|
case SearchMode:
|
||||||
|
return "SEARCH"
|
||||||
case VisualMode:
|
case VisualMode:
|
||||||
return "VISUAL"
|
return "VISUAL"
|
||||||
case VisualLineMode:
|
case VisualLineMode:
|
||||||
|
|||||||
22
internal/core/search.go
Normal file
22
internal/core/search.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
type SearchState struct {
|
||||||
|
Query string
|
||||||
|
Forword bool
|
||||||
|
|
||||||
|
Cursor int
|
||||||
|
|
||||||
|
// History is editor wide
|
||||||
|
History []string
|
||||||
|
HistoryCursor int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchState() SearchState {
|
||||||
|
return SearchState{
|
||||||
|
Query: "",
|
||||||
|
Forword: true,
|
||||||
|
Cursor: 0,
|
||||||
|
History: []string{},
|
||||||
|
HistoryCursor: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,6 @@ func NewDefaultSettings() EditorSettings {
|
|||||||
return EditorSettings{
|
return EditorSettings{
|
||||||
TabStop: 2,
|
TabStop: 2,
|
||||||
// TODO: This should be "default" but until we have a startup config, this is fine
|
// TODO: This should be "default" but until we have a startup config, this is fine
|
||||||
CurrentTheme: "kanagawa",
|
CurrentTheme: "default",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,9 @@ type Model struct {
|
|||||||
commandHistory []string
|
commandHistory []string
|
||||||
commandHistoryCursor int
|
commandHistoryCursor int
|
||||||
|
|
||||||
|
// Search state
|
||||||
|
searchState core.SearchState
|
||||||
|
|
||||||
// Global settings
|
// Global settings
|
||||||
settings core.EditorSettings
|
settings core.EditorSettings
|
||||||
|
|
||||||
@ -328,6 +331,18 @@ func (m *Model) SetCommandHistoryCursor(cur int) {
|
|||||||
m.commandHistoryCursor = cur
|
m.commandHistoryCursor = cur
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================================================
|
||||||
|
// Search Mode State
|
||||||
|
// ==================================================
|
||||||
|
|
||||||
|
func (m *Model) SearchState() core.SearchState {
|
||||||
|
return m.searchState
|
||||||
|
|
||||||
|
}
|
||||||
|
func (m *Model) SetSearchState(s core.SearchState) {
|
||||||
|
m.searchState = s
|
||||||
|
}
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// Editor-wide State
|
// Editor-wide State
|
||||||
// ==================================================
|
// ==================================================
|
||||||
|
|||||||
@ -261,6 +261,29 @@ func drawCommandBar(m Model, t theme.EditorTheme) string {
|
|||||||
leftBar = t.Line.Render(content)
|
leftBar = t.Line.Render(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only display when when we are currently searching
|
||||||
|
if m.Mode() == core.SearchMode {
|
||||||
|
search := m.SearchState()
|
||||||
|
if search.Forword {
|
||||||
|
leftBar = t.Line.Render("/")
|
||||||
|
} else {
|
||||||
|
leftBar = t.Line.Render("?")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, r := range search.Query {
|
||||||
|
if i == search.Cursor {
|
||||||
|
// TODO: Make sure other themes support this
|
||||||
|
leftBar += t.DefaultCursor(m.Mode()).Render(string(r))
|
||||||
|
} else {
|
||||||
|
leftBar += t.Line.Render(string(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cursor at end of command
|
||||||
|
if search.Cursor >= len(search.Query) {
|
||||||
|
leftBar += t.DefaultCursor(m.Mode()).Render(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compute right bar
|
// Compute right bar
|
||||||
|
|
||||||
var rightBar string
|
var rightBar string
|
||||||
|
|||||||
@ -43,6 +43,7 @@ type Handler struct {
|
|||||||
insertKeymap *Keymap
|
insertKeymap *Keymap
|
||||||
replaceKeymap *Keymap
|
replaceKeymap *Keymap
|
||||||
commandKeymap *Keymap
|
commandKeymap *Keymap
|
||||||
|
searchKeymap *Keymap
|
||||||
|
|
||||||
currentKeymap *Keymap
|
currentKeymap *Keymap
|
||||||
}
|
}
|
||||||
@ -56,6 +57,7 @@ func NewHandler() *Handler {
|
|||||||
insertKeymap: NewInsertKeymap(),
|
insertKeymap: NewInsertKeymap(),
|
||||||
replaceKeymap: NewReplaceKeymap(),
|
replaceKeymap: NewReplaceKeymap(),
|
||||||
commandKeymap: NewCommandKeymap(),
|
commandKeymap: NewCommandKeymap(),
|
||||||
|
searchKeymap: NewSearchKeymap(),
|
||||||
currentKeymap: nil,
|
currentKeymap: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ESC always resets everything
|
// ESC always resets everything
|
||||||
if key == "esc" {
|
if key == "esc" && m.Mode() != core.SearchMode {
|
||||||
// If insert mode, keep the escape
|
// If insert mode, keep the escape
|
||||||
if m.Mode() == core.InsertMode {
|
if m.Mode() == core.InsertMode {
|
||||||
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
|
m.SetLastChangeKeys(append(m.LastChangeKeys(), key))
|
||||||
@ -101,6 +103,8 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
return h.handleReplaceKey(m, key)
|
return h.handleReplaceKey(m, key)
|
||||||
case core.CommandMode:
|
case core.CommandMode:
|
||||||
return h.handleCommandKey(m, key)
|
return h.handleCommandKey(m, key)
|
||||||
|
case core.SearchMode:
|
||||||
|
return h.handleSearchKey(m, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If waiting for character argument (f/t/F/T), capture it
|
// If waiting for character argument (f/t/F/T), capture it
|
||||||
@ -616,6 +620,22 @@ func (h *Handler) handleCommandKey(m action.Model, key string) tea.Cmd {
|
|||||||
return action.InsertCommandChar{Char: key}.Execute(m)
|
return action.InsertCommandChar{Char: key}.Execute(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler.handleSearchKey: Processes a keypress in search mode, executing
|
||||||
|
// it as an action or inserting it into the search line. This does not record
|
||||||
|
// anything into the undo stack.
|
||||||
|
func (h *Handler) handleSearchKey(m action.Model, key string) tea.Cmd {
|
||||||
|
kind, binding := h.searchKeymap.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.InsertSearchChar{Char: key}.Execute(m)
|
||||||
|
}
|
||||||
|
|
||||||
// normalizeVisualSelection: Returns the visual selection with start before end,
|
// normalizeVisualSelection: Returns the visual selection with start before end,
|
||||||
// regardless of which direction the selection was made.
|
// regardless of which direction the selection was made.
|
||||||
func normalizeVisualSelection(m action.Model) (core.Position, core.Position) {
|
func normalizeVisualSelection(m action.Model) (core.Position, core.Position) {
|
||||||
|
|||||||
@ -84,6 +84,8 @@ func NewNormalKeymap() *Keymap {
|
|||||||
"R": action.EnterReplace{},
|
"R": action.EnterReplace{},
|
||||||
"J": action.JoinLines{Preserve: false},
|
"J": action.JoinLines{Preserve: false},
|
||||||
"gJ": action.JoinLines{Preserve: true},
|
"gJ": action.JoinLines{Preserve: true},
|
||||||
|
"/": action.EnterSearchMode{Forward: true},
|
||||||
|
"?": action.EnterSearchMode{Forward: false},
|
||||||
},
|
},
|
||||||
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},
|
||||||
@ -257,6 +259,26 @@ func NewCommandKeymap() *Keymap {
|
|||||||
"ctrl+w": action.CommandDeletePreviousWord{},
|
"ctrl+w": action.CommandDeletePreviousWord{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSearchKeymap: Creates a keymap for search mode with command line editing.
|
||||||
|
func NewSearchKeymap() *Keymap {
|
||||||
|
return &Keymap{
|
||||||
|
motions: map[string]action.Motion{
|
||||||
|
"left": motion.MoveSearchLeft{},
|
||||||
|
"right": motion.MoveSearchRight{},
|
||||||
|
// "up": motion.MoveCommandHistoryUp{},
|
||||||
|
// "down": motion.MoveCommandHistoryDown{},
|
||||||
|
},
|
||||||
|
operators: map[string]action.Operator{}, // this will likely be empty
|
||||||
|
actions: map[string]action.Action{
|
||||||
|
"esc": action.ExitSearchMode{},
|
||||||
|
"enter": action.SearchExecute{},
|
||||||
|
"backspace": action.SearchBackspace{},
|
||||||
|
"delete": action.SearchDelete{},
|
||||||
|
"ctrl+w": action.SearchDeletePreviousWord{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
internal/motion/search.go
Normal file
37
internal/motion/search.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package motion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||||
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MoveSearchLeft implements Motion - moves cursor left in search line.
|
||||||
|
type MoveSearchLeft struct{}
|
||||||
|
|
||||||
|
// MoveSearchLeft.Execute: Moves the search line cursor one position to the left.
|
||||||
|
func (a MoveSearchLeft) Execute(m action.Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
search.Cursor = max(0, search.Cursor-1)
|
||||||
|
m.SetSearchState(search)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveSearchLeft.Type: Returns CharwiseExclusive for search line motion.
|
||||||
|
func (a MoveSearchLeft) Type() core.MotionType { return core.CharwiseExclusive }
|
||||||
|
|
||||||
|
// MoveSearchRight implements Motion - moves cursor right in search line.
|
||||||
|
type MoveSearchRight struct{}
|
||||||
|
|
||||||
|
// MoveSearchRight.Execute: Moves the search line cursor one position to the right.
|
||||||
|
func (a MoveSearchRight) Execute(m action.Model) tea.Cmd {
|
||||||
|
search := m.SearchState()
|
||||||
|
search.Cursor = min(search.Cursor+1, len(search.Query))
|
||||||
|
m.SetSearchState(search)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveSearchRight.Type: Returns CharwiseExclusive for search line motion.
|
||||||
|
func (a MoveSearchRight) Type() core.MotionType { return core.CharwiseExclusive }
|
||||||
@ -68,7 +68,7 @@ func (t EditorTheme) DefaultCursor(mode core.Mode) lipgloss.Style {
|
|||||||
switch mode {
|
switch mode {
|
||||||
case core.InsertMode:
|
case core.InsertMode:
|
||||||
return t.Cursors.Insert
|
return t.Cursors.Insert
|
||||||
case core.CommandMode:
|
case core.CommandMode, core.SearchMode:
|
||||||
return t.Cursors.Command
|
return t.Cursors.Command
|
||||||
case core.ReplaceMode:
|
case core.ReplaceMode:
|
||||||
return t.Cursors.Replace
|
return t.Cursors.Replace
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user