summaryrefslogtreecommitdiff
path: root/internal/client/ui
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2024-12-05 17:20:32 +0200
committerKyren223 <Kyren223@proton.me>2024-12-05 17:20:32 +0200
commitbb105cb16fe81ae29df5a5a5570e55c3e3ce0873 (patch)
tree011538d2e0bb119b90801f36cf376a3482793935 /internal/client/ui
parent8087907b4b942c7d8c9296c3fd0e1f08d5d88694 (diff)
Basic vim movement and text input
Diffstat (limited to 'internal/client/ui')
-rw-r--r--internal/client/ui/core/chat/chat.go4
-rw-r--r--internal/client/ui/viminput/viminput.go132
2 files changed, 121 insertions, 15 deletions
diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go
index 04eedb7..a93272f 100644
--- a/internal/client/ui/core/chat/chat.go
+++ b/internal/client/ui/core/chat/chat.go
@@ -23,7 +23,7 @@ func New() Model {
return lineNumber + " ┃ "
}
- vi.SetLines(viminput.Line("test"), viminput.Line("best"))
+ vi.SetLines([]rune("test"), []rune("best"))
// ta.Cursor.SetChar()
// vi.CharLimit = 280
@@ -54,8 +54,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m *Model) Focus() {
m.focus = true
+ m.vi.Focus()
}
func (m *Model) Blur() {
m.focus = false
+ m.vi.Blur()
}
diff --git a/internal/client/ui/viminput/viminput.go b/internal/client/ui/viminput/viminput.go
index 94c5413..1c3d8a9 100644
--- a/internal/client/ui/viminput/viminput.go
+++ b/internal/client/ui/viminput/viminput.go
@@ -1,31 +1,38 @@
package viminput
import (
+ "slices"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
+ "github.com/kyren223/eko/internal/client/ui/colors"
)
+var DefaultCursorStyle = lipgloss.NewStyle().Background(colors.White).Foreground(colors.Background)
+
type (
- Keymap struct{}
LineDecoration = func(lnum int, m Model) string
)
-type Line []byte
+const (
+ NormalMode = iota
+ InsertMode
+ VisualMode
+)
type Model struct {
- keymap Keymap
-
PlaceholderStyle lipgloss.Style
PromptStyle lipgloss.Style
Placeholder string
LineDecoration LineDecoration
- lines []Line
- CursorLine int
- CursorColumn int
+ lines [][]rune
+ cursorLine int
+ cursorColumn int
+ goalColumn int
+ mode int
width int
height int
@@ -34,11 +41,15 @@ type Model struct {
func New(width, height int) Model {
return Model{
- keymap: Keymap{},
PlaceholderStyle: lipgloss.NewStyle(),
PromptStyle: lipgloss.NewStyle(),
Placeholder: "",
LineDecoration: EmptyLineDecoration,
+ lines: [][]rune{},
+ cursorLine: 0,
+ cursorColumn: 0,
+ goalColumn: -1,
+ mode: NormalMode,
width: width,
height: height,
focus: false,
@@ -50,10 +61,10 @@ func (m Model) Init() tea.Cmd {
}
func (m Model) View() string {
- var lines []Line
+ var lines [][]rune
if len(m.lines) == 0 {
placeholder := m.PlaceholderStyle.Render(m.Placeholder)
- lines = append(lines, Line(placeholder))
+ lines = append(lines, []rune(placeholder))
} else {
lines = m.lines
}
@@ -62,7 +73,18 @@ func (m Model) View() string {
for i, line := range lines {
lineDecoration := m.LineDecoration(i, m)
builder.WriteString(lineDecoration)
- builder.Write(line)
+
+ if m.CursorLine() != i {
+ builder.WriteString(string(line))
+ } else if m.CursorColumn() == len(m.lines[m.CursorLine()]) {
+ builder.WriteString(string(line))
+ builder.WriteString(DefaultCursorStyle.Render(" "))
+ } else {
+ builder.WriteString(string(line[:m.CursorColumn()]))
+ builder.WriteString(DefaultCursorStyle.Render(string(line[m.CursorColumn()])))
+ builder.WriteString(string(line[m.CursorColumn()+1:]))
+ }
+
builder.WriteByte('\n')
}
@@ -77,6 +99,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ cmd := m.handleKeys(msg)
+ return m, cmd
+ }
+
return m, nil
}
@@ -104,14 +132,90 @@ func (m *Model) Blur() {
m.focus = false
}
-func (m *Model) SetLines(lines ...Line) {
+func (m *Model) SetLines(lines ...[]rune) {
m.lines = lines
}
-func (m *Model) SetLine(lnum int, line Line) {
+func (m *Model) SetLine(lnum int, line []rune) {
m.lines[lnum] = line
}
-func (m *Model) Line(lnum int) Line {
+func (m *Model) Line(lnum int) []rune {
return m.lines[lnum]
}
+
+func (m *Model) SetCursorColumn(col int) {
+ m.cursorColumn = col
+ m.goalColumn = -1
+}
+
+func (m *Model) SetCursorLine(line int) {
+ fromLength := m.CursorColumn()
+ toLength := len(m.lines[line])
+ if fromLength > toLength && m.goalColumn == -1 {
+ m.goalColumn = fromLength
+ }
+ if m.goalColumn != -1 {
+ m.cursorColumn = min(toLength-1, m.goalColumn)
+ }
+ m.cursorLine = line
+}
+
+func (m *Model) CursorColumn() int {
+ return m.cursorColumn
+}
+
+func (m *Model) CursorLine() int {
+ return m.cursorLine
+}
+
+func (m *Model) handleKeys(key tea.KeyMsg) tea.Cmd {
+ switch m.mode {
+ case NormalMode:
+ return m.handleNormalModeKeys(key)
+ case InsertMode:
+ return m.handleInsertModeKeys(key)
+ }
+
+ return nil
+}
+
+func (m *Model) handleNormalModeKeys(key tea.KeyMsg) tea.Cmd {
+ switch key.String() {
+ case "h":
+ m.SetCursorColumn(max(m.CursorColumn()-1, 0))
+ case "j":
+ m.SetCursorLine(min(m.CursorLine()+1, len(m.lines)-1))
+ case "k":
+ m.SetCursorLine(max(m.CursorLine()-1, 0))
+ case "l":
+ m.SetCursorColumn(min(m.CursorColumn()+1, len(m.lines[m.CursorLine()])-1))
+ case "i":
+ m.mode = InsertMode
+ case "a":
+ m.mode = InsertMode
+ m.SetCursorColumn(m.CursorColumn() + 1)
+ case "0":
+ m.SetCursorColumn(0)
+ }
+
+ return nil
+}
+
+func (m *Model) handleInsertModeKeys(key tea.KeyMsg) tea.Cmd {
+ if key.Type == tea.KeyEscape {
+ m.SetCursorColumn(max(m.CursorColumn()-1, 0))
+ m.mode = NormalMode
+ return nil
+ }
+
+ keyStr := key.String()
+ length := len(keyStr)
+ if length == 1 && 32 <= keyStr[0] && keyStr[0] <= 126 {
+ line := m.lines[m.CursorLine()]
+ m.lines[m.CursorLine()] = slices.Insert(line, m.CursorColumn(), rune(keyStr[0]))
+ m.SetCursorColumn(m.CursorColumn() + 1)
+ }
+
+ return nil
+}