mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-03-14 13:03:44 +03:00
refactor: split files into {modal,normal,insert}.go
This commit is contained in:
parent
447ff2da6a
commit
fb44abddf8
117
textarea/insert.go
Normal file
117
textarea/insert.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package textarea
|
||||||
|
|
||||||
|
import (
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
rw "github.com/mattn/go-runewidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Model) insertUpdate(msg tea.Msg) tea.Cmd {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc":
|
||||||
|
return m.SetMode(ModeNormal)
|
||||||
|
case "ctrl+k":
|
||||||
|
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||||
|
if m.col >= len(m.value[m.row]) {
|
||||||
|
m.mergeLineBelow(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.deleteAfterCursor()
|
||||||
|
case "ctrl+u":
|
||||||
|
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||||
|
if m.col <= 0 {
|
||||||
|
m.mergeLineAbove(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.deleteBeforeCursor()
|
||||||
|
case "backspace", "ctrl+h":
|
||||||
|
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||||
|
if m.col <= 0 {
|
||||||
|
m.mergeLineAbove(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(m.value[m.row]) > 0 {
|
||||||
|
m.value[m.row] = append(m.value[m.row][:max(0, m.col-1)], m.value[m.row][m.col:]...)
|
||||||
|
if m.col > 0 {
|
||||||
|
m.SetCursor(m.col - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "delete", "ctrl+d":
|
||||||
|
if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
|
||||||
|
m.value[m.row] = append(m.value[m.row][:m.col], m.value[m.row][m.col+1:]...)
|
||||||
|
}
|
||||||
|
if m.col >= len(m.value[m.row]) {
|
||||||
|
m.mergeLineBelow(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "alt+backspace", "ctrl+w":
|
||||||
|
if m.col <= 0 {
|
||||||
|
m.mergeLineAbove(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.deleteWordLeft()
|
||||||
|
case "alt+delete", "alt+d":
|
||||||
|
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||||
|
if m.col >= len(m.value[m.row]) {
|
||||||
|
m.mergeLineBelow(m.row)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.deleteWordRight()
|
||||||
|
case "enter", "ctrl+m":
|
||||||
|
if len(m.value) >= maxHeight {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
||||||
|
m.splitLine(m.row, m.col)
|
||||||
|
case "end", "ctrl+e":
|
||||||
|
m.CursorEnd()
|
||||||
|
case "home", "ctrl+a":
|
||||||
|
m.CursorStart()
|
||||||
|
case "right", "ctrl+f":
|
||||||
|
if m.col < len(m.value[m.row]) {
|
||||||
|
m.SetCursor(m.col + 1)
|
||||||
|
} else {
|
||||||
|
if m.row < len(m.value)-1 {
|
||||||
|
m.row++
|
||||||
|
m.CursorStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "down", "ctrl+n":
|
||||||
|
m.CursorDown()
|
||||||
|
case "alt+right", "alt+f":
|
||||||
|
m.wordRight()
|
||||||
|
case "ctrl+v":
|
||||||
|
return Paste
|
||||||
|
case "left", "ctrl+b":
|
||||||
|
if m.col == 0 && m.row != 0 {
|
||||||
|
m.row--
|
||||||
|
m.CursorEnd()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if m.col > 0 {
|
||||||
|
m.SetCursor(m.col - 1)
|
||||||
|
}
|
||||||
|
case "up", "ctrl+p":
|
||||||
|
m.CursorUp()
|
||||||
|
case "alt+left", "alt+b":
|
||||||
|
m.wordLeft()
|
||||||
|
default:
|
||||||
|
if m.CharLimit > 0 && rw.StringWidth(m.Value()) >= m.CharLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
m.col = min(m.col, len(m.value[m.row]))
|
||||||
|
m.value[m.row] = append(m.value[m.row][:m.col], append(msg.Runes, m.value[m.row][m.col:]...)...)
|
||||||
|
m.SetCursor(m.col + len(msg.Runes))
|
||||||
|
}
|
||||||
|
|
||||||
|
case pasteMsg:
|
||||||
|
m.handlePaste(string(msg))
|
||||||
|
|
||||||
|
case pasteErrMsg:
|
||||||
|
m.Err = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
75
textarea/modal.go
Normal file
75
textarea/modal.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package textarea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/bubbles/cursor"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mode is the possible modes of the textarea for modal editing.
|
||||||
|
type Mode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ModeNormal is the normal mode for navigating around text.
|
||||||
|
ModeNormal Mode = "normal"
|
||||||
|
// ModeInsert is the insert mode for inserting text.
|
||||||
|
ModeInsert Mode = "insert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetMode sets the mode of the textarea.
|
||||||
|
func (m *Model) SetMode(mode Mode) tea.Cmd {
|
||||||
|
switch mode {
|
||||||
|
case ModeInsert:
|
||||||
|
m.mode = ModeInsert
|
||||||
|
return m.Cursor.SetCursorMode(cursor.CursorBlink)
|
||||||
|
case ModeNormal:
|
||||||
|
m.mode = ModeNormal
|
||||||
|
return m.Cursor.SetCursorMode(cursor.CursorStatic)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action is the type of action that will be performed when the user completes
|
||||||
|
// a key combination.
|
||||||
|
type Action int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ActionMove moves the cursor.
|
||||||
|
ActionMove Action = iota
|
||||||
|
// ActionSeek seeks the cursor to the desired character.
|
||||||
|
// Used in conjunction with f/F/t/T.
|
||||||
|
ActionSeek
|
||||||
|
// ActionReplace replaces text.
|
||||||
|
ActionReplace
|
||||||
|
// ActionDelete deletes text.
|
||||||
|
ActionDelete
|
||||||
|
// ActionYank yanks text.
|
||||||
|
ActionYank
|
||||||
|
)
|
||||||
|
|
||||||
|
// Position is a (row, column) pair representing a position of the cursor or
|
||||||
|
// any character.
|
||||||
|
type Position struct {
|
||||||
|
Row int
|
||||||
|
Col int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range is a range of characters in the text area.
|
||||||
|
type Range struct {
|
||||||
|
Start Position
|
||||||
|
End Position
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalCommand is a helper for keeping track of the various relevant information
|
||||||
|
// when performing vim motions in the textarea.
|
||||||
|
type NormalCommand struct {
|
||||||
|
// Buffer is the buffer of keys that have been press for the current
|
||||||
|
// command.
|
||||||
|
Buffer string
|
||||||
|
// Count is the number of times to replay the action. This is usually
|
||||||
|
// optional and defaults to 1.
|
||||||
|
Count int
|
||||||
|
// Action is the action to be performed.
|
||||||
|
Action Action
|
||||||
|
// Range is the range of characters to perform the action on.
|
||||||
|
Range Range
|
||||||
|
}
|
150
textarea/normal.go
Normal file
150
textarea/normal.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package textarea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Model) normalUpdate(msg tea.Msg) tea.Cmd {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc":
|
||||||
|
m.command = &NormalCommand{}
|
||||||
|
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
|
||||||
|
v := m.command.Buffer + msg.String()
|
||||||
|
count, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
count, _ = strconv.Atoi(msg.String())
|
||||||
|
m.command = &NormalCommand{Buffer: msg.String(), Count: count}
|
||||||
|
} else {
|
||||||
|
m.command = &NormalCommand{Buffer: v, Count: count}
|
||||||
|
}
|
||||||
|
case "G":
|
||||||
|
var row int
|
||||||
|
if m.command.Count > 0 {
|
||||||
|
row = m.command.Count - 1
|
||||||
|
} else {
|
||||||
|
row = len(m.value) - 1
|
||||||
|
}
|
||||||
|
m.row = clamp(row, 0, len(m.value)-1)
|
||||||
|
return nil
|
||||||
|
case "g":
|
||||||
|
if m.command.Buffer == "g" {
|
||||||
|
m.command = &NormalCommand{}
|
||||||
|
m.row = clamp(m.command.Count-1, 0, len(m.value)-1)
|
||||||
|
} else {
|
||||||
|
m.command = &NormalCommand{Buffer: "g"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "d":
|
||||||
|
if m.command.Action == ActionDelete {
|
||||||
|
for i := 0; i < max(m.command.Count, 1); i++ {
|
||||||
|
m.CursorStart()
|
||||||
|
m.deleteAfterCursor()
|
||||||
|
m.mergeLineBelow(m.row)
|
||||||
|
}
|
||||||
|
m.command = &NormalCommand{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.command.Action = ActionDelete
|
||||||
|
case "y":
|
||||||
|
m.command.Action = ActionYank
|
||||||
|
case "r":
|
||||||
|
m.command.Action = ActionReplace
|
||||||
|
case "i":
|
||||||
|
return m.SetMode(ModeInsert)
|
||||||
|
case "I":
|
||||||
|
m.CursorStart()
|
||||||
|
return m.SetMode(ModeInsert)
|
||||||
|
case "a":
|
||||||
|
m.SetCursor(m.col + 1)
|
||||||
|
return m.SetMode(ModeInsert)
|
||||||
|
case "A":
|
||||||
|
m.CursorEnd()
|
||||||
|
return m.SetMode(ModeInsert)
|
||||||
|
case "^":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row, 0},
|
||||||
|
}
|
||||||
|
case "$":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row, len(m.value[m.row]) - 1},
|
||||||
|
}
|
||||||
|
case "e", "E":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: m.findWordEnd(m.command.Count, msg.String() == "E"),
|
||||||
|
}
|
||||||
|
case "w", "W":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: m.findWordStart(m.command.Count, msg.String() == "W"),
|
||||||
|
}
|
||||||
|
case "b", "B":
|
||||||
|
direction := -1
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: m.findWordStart(direction*m.command.Count, msg.String() == "B"),
|
||||||
|
}
|
||||||
|
case "h":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row, m.col - max(m.command.Count, 1)},
|
||||||
|
}
|
||||||
|
case "j":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row + max(m.command.Count, 1), m.col},
|
||||||
|
}
|
||||||
|
case "k":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row - max(m.command.Count, 1), m.col},
|
||||||
|
}
|
||||||
|
case "l":
|
||||||
|
m.command.Range = Range{
|
||||||
|
Start: Position{m.row, m.col},
|
||||||
|
End: Position{m.row, m.col + max(m.command.Count, 1)},
|
||||||
|
}
|
||||||
|
case "J":
|
||||||
|
m.CursorEnd()
|
||||||
|
m.mergeLineBelow(m.row)
|
||||||
|
return nil
|
||||||
|
case "p":
|
||||||
|
cmd = Paste
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg.String() {
|
||||||
|
case "i", "I", "a", "A", "e", "w", "W", "b", "B", "h", "j", "k", "l", "p", "$", "^":
|
||||||
|
switch m.command.Action {
|
||||||
|
case ActionDelete:
|
||||||
|
m.deleteRange(m.command.Range)
|
||||||
|
case ActionMove:
|
||||||
|
rowDelta := m.command.Range.End.Row - m.command.Range.Start.Row
|
||||||
|
if rowDelta > 0 {
|
||||||
|
for i := 0; i < rowDelta; i++ {
|
||||||
|
m.CursorDown()
|
||||||
|
}
|
||||||
|
} else if rowDelta < 0 {
|
||||||
|
for i := 0; i < -rowDelta; i++ {
|
||||||
|
m.CursorUp()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.SetCursor(m.command.Range.End.Col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.command = &NormalCommand{}
|
||||||
|
}
|
||||||
|
|
||||||
|
case pasteMsg:
|
||||||
|
m.handlePaste(string(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
@ -2,7 +2,6 @@ package textarea
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -24,16 +23,6 @@ const (
|
|||||||
maxWidth = 500
|
maxWidth = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mode is the possible modes of the textarea for modal editing.
|
|
||||||
type Mode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ModeNormal is the normal mode for navigating around text.
|
|
||||||
ModeNormal Mode = "normal"
|
|
||||||
// ModeInsert is the insert mode for inserting text.
|
|
||||||
ModeInsert Mode = "insert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Internal messages for clipboard operations.
|
// Internal messages for clipboard operations.
|
||||||
type pasteMsg string
|
type pasteMsg string
|
||||||
type pasteErrMsg struct{ error }
|
type pasteErrMsg struct{ error }
|
||||||
@ -63,52 +52,6 @@ type LineInfo struct {
|
|||||||
CharOffset int
|
CharOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action is the type of action that will be performed when the user completes
|
|
||||||
// a key combination.
|
|
||||||
type Action int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ActionMove moves the cursor.
|
|
||||||
ActionMove Action = iota
|
|
||||||
// ActionSeek seeks the cursor to the desired character.
|
|
||||||
// Used in conjunction with f/F/t/T.
|
|
||||||
ActionSeek
|
|
||||||
// ActionReplace replaces text.
|
|
||||||
ActionReplace
|
|
||||||
// ActionDelete deletes text.
|
|
||||||
ActionDelete
|
|
||||||
// ActionYank yanks text.
|
|
||||||
ActionYank
|
|
||||||
)
|
|
||||||
|
|
||||||
// Position is a (row, column) pair representing a position of the cursor or
|
|
||||||
// any character.
|
|
||||||
type Position struct {
|
|
||||||
Row int
|
|
||||||
Col int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range is a range of characters in the text area.
|
|
||||||
type Range struct {
|
|
||||||
Start Position
|
|
||||||
End Position
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalCommand is a helper for keeping track of the various relevant information
|
|
||||||
// when performing vim motions in the textarea.
|
|
||||||
type NormalCommand struct {
|
|
||||||
// Buffer is the buffer of keys that have been press for the current
|
|
||||||
// command.
|
|
||||||
Buffer string
|
|
||||||
// Count is the number of times to replay the action. This is usually
|
|
||||||
// optional and defaults to 1.
|
|
||||||
Count int
|
|
||||||
// Action is the action to be performed.
|
|
||||||
Action Action
|
|
||||||
// Range is the range of characters to perform the action on.
|
|
||||||
Range Range
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style that will be applied to the text area.
|
// Style that will be applied to the text area.
|
||||||
//
|
//
|
||||||
// Style can be applied to focused and unfocused states to change the styles
|
// Style can be applied to focused and unfocused states to change the styles
|
||||||
@ -790,264 +733,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMode sets the mode of the textarea.
|
func (m *Model) deleteRange(r Range) {
|
||||||
func (m *Model) SetMode(mode Mode) tea.Cmd {
|
|
||||||
switch mode {
|
|
||||||
case ModeInsert:
|
|
||||||
m.mode = ModeInsert
|
|
||||||
return m.Cursor.SetCursorMode(cursor.CursorBlink)
|
|
||||||
case ModeNormal:
|
|
||||||
m.mode = ModeNormal
|
|
||||||
return m.Cursor.SetCursorMode(cursor.CursorStatic)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) normalUpdate(msg tea.Msg) tea.Cmd {
|
|
||||||
var cmd tea.Cmd
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "esc":
|
|
||||||
m.command = &NormalCommand{}
|
|
||||||
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
|
|
||||||
v := m.command.Buffer + msg.String()
|
|
||||||
count, err := strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
count, _ = strconv.Atoi(msg.String())
|
|
||||||
m.command = &NormalCommand{Buffer: msg.String(), Count: count}
|
|
||||||
} else {
|
|
||||||
m.command = &NormalCommand{Buffer: v, Count: count}
|
|
||||||
}
|
|
||||||
case "G":
|
|
||||||
var row int
|
|
||||||
if m.command.Count > 0 {
|
|
||||||
row = m.command.Count - 1
|
|
||||||
} else {
|
|
||||||
row = len(m.value) - 1
|
|
||||||
}
|
|
||||||
m.row = clamp(row, 0, len(m.value)-1)
|
|
||||||
return nil
|
|
||||||
case "g":
|
|
||||||
if m.command.Buffer == "g" {
|
|
||||||
m.command = &NormalCommand{}
|
|
||||||
m.row = clamp(m.command.Count-1, 0, len(m.value)-1)
|
|
||||||
} else {
|
|
||||||
m.command = &NormalCommand{Buffer: "g"}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case "d":
|
|
||||||
if m.command.Action == ActionDelete {
|
|
||||||
for i := 0; i < m.command.Count; i++ {
|
|
||||||
m.CursorStart()
|
|
||||||
m.deleteAfterCursor()
|
|
||||||
m.mergeLineBelow(m.row)
|
|
||||||
}
|
|
||||||
m.command = &NormalCommand{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.command.Action = ActionDelete
|
|
||||||
case "y":
|
|
||||||
m.command.Action = ActionYank
|
|
||||||
case "r":
|
|
||||||
m.command.Action = ActionReplace
|
|
||||||
case "i":
|
|
||||||
return m.SetMode(ModeInsert)
|
|
||||||
case "I":
|
|
||||||
m.CursorStart()
|
|
||||||
return m.SetMode(ModeInsert)
|
|
||||||
case "a":
|
|
||||||
m.SetCursor(m.col + 1)
|
|
||||||
return m.SetMode(ModeInsert)
|
|
||||||
case "A":
|
|
||||||
m.CursorEnd()
|
|
||||||
return m.SetMode(ModeInsert)
|
|
||||||
case "^":
|
|
||||||
m.CursorStart()
|
|
||||||
return nil
|
|
||||||
case "$":
|
|
||||||
m.CursorEnd()
|
|
||||||
return nil
|
|
||||||
case "e", "E":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: m.findWordEnd(m.command.Count, msg.String() == "E"),
|
|
||||||
}
|
|
||||||
case "w", "W":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: m.findWordStart(m.command.Count, msg.String() == "W"),
|
|
||||||
}
|
|
||||||
case "b", "B":
|
|
||||||
direction := -1
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: m.findWordStart(direction*m.command.Count, msg.String() == "B"),
|
|
||||||
}
|
|
||||||
case "h":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: Position{m.row, m.col - max(m.command.Count, 1)},
|
|
||||||
}
|
|
||||||
case "j":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: Position{m.row + max(m.command.Count, 1), m.col},
|
|
||||||
}
|
|
||||||
case "k":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: Position{m.row - max(m.command.Count, 1), m.col},
|
|
||||||
}
|
|
||||||
case "l":
|
|
||||||
m.command.Range = Range{
|
|
||||||
Start: Position{m.row, m.col},
|
|
||||||
End: Position{m.row, m.col + max(m.command.Count, 1)},
|
|
||||||
}
|
|
||||||
case "J":
|
|
||||||
m.CursorEnd()
|
|
||||||
m.mergeLineBelow(m.row)
|
|
||||||
return nil
|
|
||||||
case "p":
|
|
||||||
cmd = Paste
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg.String() {
|
|
||||||
case "i", "I", "a", "A", "e", "w", "W", "b", "B", "h", "j", "k", "l", "p":
|
|
||||||
if m.command.Action == ActionMove {
|
|
||||||
rowDelta := m.command.Range.End.Row - m.command.Range.Start.Row
|
|
||||||
if rowDelta > 0 {
|
|
||||||
for i := 0; i < rowDelta; i++ {
|
|
||||||
m.CursorDown()
|
|
||||||
}
|
|
||||||
} else if rowDelta < 0 {
|
|
||||||
for i := 0; i < -rowDelta; i++ {
|
|
||||||
m.CursorUp()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m.SetCursor(m.command.Range.End.Col)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.command = &NormalCommand{}
|
|
||||||
}
|
|
||||||
|
|
||||||
case pasteMsg:
|
|
||||||
m.handlePaste(string(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) insertUpdate(msg tea.Msg) tea.Cmd {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "esc":
|
|
||||||
return m.SetMode(ModeNormal)
|
|
||||||
case "ctrl+k":
|
|
||||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
|
||||||
if m.col >= len(m.value[m.row]) {
|
|
||||||
m.mergeLineBelow(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.deleteAfterCursor()
|
|
||||||
case "ctrl+u":
|
|
||||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
|
||||||
if m.col <= 0 {
|
|
||||||
m.mergeLineAbove(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.deleteBeforeCursor()
|
|
||||||
case "backspace", "ctrl+h":
|
|
||||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
|
||||||
if m.col <= 0 {
|
|
||||||
m.mergeLineAbove(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(m.value[m.row]) > 0 {
|
|
||||||
m.value[m.row] = append(m.value[m.row][:max(0, m.col-1)], m.value[m.row][m.col:]...)
|
|
||||||
if m.col > 0 {
|
|
||||||
m.SetCursor(m.col - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "delete", "ctrl+d":
|
|
||||||
if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
|
|
||||||
m.value[m.row] = append(m.value[m.row][:m.col], m.value[m.row][m.col+1:]...)
|
|
||||||
}
|
|
||||||
if m.col >= len(m.value[m.row]) {
|
|
||||||
m.mergeLineBelow(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case "alt+backspace", "ctrl+w":
|
|
||||||
if m.col <= 0 {
|
|
||||||
m.mergeLineAbove(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.deleteWordLeft()
|
|
||||||
case "alt+delete", "alt+d":
|
|
||||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
|
||||||
if m.col >= len(m.value[m.row]) {
|
|
||||||
m.mergeLineBelow(m.row)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.deleteWordRight()
|
|
||||||
case "enter", "ctrl+m":
|
|
||||||
if len(m.value) >= maxHeight {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.col = clamp(m.col, 0, len(m.value[m.row]))
|
|
||||||
m.splitLine(m.row, m.col)
|
|
||||||
case "end", "ctrl+e":
|
|
||||||
m.CursorEnd()
|
|
||||||
case "home", "ctrl+a":
|
|
||||||
m.CursorStart()
|
|
||||||
case "right", "ctrl+f":
|
|
||||||
if m.col < len(m.value[m.row]) {
|
|
||||||
m.SetCursor(m.col + 1)
|
|
||||||
} else {
|
|
||||||
if m.row < len(m.value)-1 {
|
|
||||||
m.row++
|
|
||||||
m.CursorStart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "down", "ctrl+n":
|
|
||||||
m.CursorDown()
|
|
||||||
case "alt+right", "alt+f":
|
|
||||||
m.wordRight()
|
|
||||||
case "ctrl+v":
|
|
||||||
return Paste
|
|
||||||
case "left", "ctrl+b":
|
|
||||||
if m.col == 0 && m.row != 0 {
|
|
||||||
m.row--
|
|
||||||
m.CursorEnd()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if m.col > 0 {
|
|
||||||
m.SetCursor(m.col - 1)
|
|
||||||
}
|
|
||||||
case "up", "ctrl+p":
|
|
||||||
m.CursorUp()
|
|
||||||
case "alt+left", "alt+b":
|
|
||||||
m.wordLeft()
|
|
||||||
default:
|
|
||||||
if m.CharLimit > 0 && rw.StringWidth(m.Value()) >= m.CharLimit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
m.col = min(m.col, len(m.value[m.row]))
|
|
||||||
m.value[m.row] = append(m.value[m.row][:m.col], append(msg.Runes, m.value[m.row][m.col:]...)...)
|
|
||||||
m.SetCursor(m.col + len(msg.Runes))
|
|
||||||
}
|
|
||||||
|
|
||||||
case pasteMsg:
|
|
||||||
m.handlePaste(string(msg))
|
|
||||||
|
|
||||||
case pasteErrMsg:
|
|
||||||
m.Err = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// View renders the text area in its current state.
|
// View renders the text area in its current state.
|
||||||
|
Loading…
Reference in New Issue
Block a user