editor.go

  1package repl
  2
  3import (
  4	"strings"
  5
  6	"github.com/charmbracelet/bubbles/key"
  7	tea "github.com/charmbracelet/bubbletea"
  8	"github.com/cloudwego/eino/schema"
  9	"github.com/kujtimiihoxha/termai/internal/app"
 10	"github.com/kujtimiihoxha/termai/internal/tui/layout"
 11	"github.com/kujtimiihoxha/vimtea"
 12)
 13
 14type EditorCmp interface {
 15	tea.Model
 16	layout.Focusable
 17	layout.Sizeable
 18	layout.Bordered
 19	layout.Bindings
 20}
 21
 22type editorCmp struct {
 23	app        *app.App
 24	editor     vimtea.Editor
 25	editorMode vimtea.EditorMode
 26	sessionID  string
 27	focused    bool
 28	width      int
 29	height     int
 30}
 31
 32type editorKeyMap struct {
 33	SendMessage    key.Binding
 34	SendMessageI   key.Binding
 35	InsertMode     key.Binding
 36	NormaMode      key.Binding
 37	VisualMode     key.Binding
 38	VisualLineMode key.Binding
 39}
 40
 41var editorKeyMapValue = editorKeyMap{
 42	SendMessage: key.NewBinding(
 43		key.WithKeys("enter"),
 44		key.WithHelp("enter", "send message normal mode"),
 45	),
 46	SendMessageI: key.NewBinding(
 47		key.WithKeys("ctrl+s"),
 48		key.WithHelp("ctrl+s", "send message insert mode"),
 49	),
 50	InsertMode: key.NewBinding(
 51		key.WithKeys("i"),
 52		key.WithHelp("i", "insert mode"),
 53	),
 54	NormaMode: key.NewBinding(
 55		key.WithKeys("esc"),
 56		key.WithHelp("esc", "normal mode"),
 57	),
 58	VisualMode: key.NewBinding(
 59		key.WithKeys("v"),
 60		key.WithHelp("v", "visual mode"),
 61	),
 62	VisualLineMode: key.NewBinding(
 63		key.WithKeys("V"),
 64		key.WithHelp("V", "visual line mode"),
 65	),
 66}
 67
 68func (m *editorCmp) Init() tea.Cmd {
 69	return m.editor.Init()
 70}
 71
 72func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 73	switch msg := msg.(type) {
 74	case vimtea.EditorModeMsg:
 75		m.editorMode = msg.Mode
 76	case SelectedSessionMsg:
 77		if msg.SessionID != m.sessionID {
 78			m.sessionID = msg.SessionID
 79		}
 80	}
 81	if m.IsFocused() {
 82		switch msg := msg.(type) {
 83		case tea.KeyMsg:
 84			switch {
 85			case key.Matches(msg, editorKeyMapValue.SendMessage):
 86				if m.editorMode == vimtea.ModeNormal {
 87					return m, m.Send()
 88				}
 89			case key.Matches(msg, editorKeyMapValue.SendMessageI):
 90				if m.editorMode == vimtea.ModeInsert {
 91					return m, m.Send()
 92				}
 93			}
 94		}
 95		u, cmd := m.editor.Update(msg)
 96		m.editor = u.(vimtea.Editor)
 97		return m, cmd
 98	}
 99	return m, nil
100}
101
102func (m *editorCmp) Blur() tea.Cmd {
103	m.focused = false
104	return nil
105}
106
107func (m *editorCmp) BorderText() map[layout.BorderPosition]string {
108	return map[layout.BorderPosition]string{
109		layout.TopLeftBorder: "New Message",
110	}
111}
112
113func (m *editorCmp) Focus() tea.Cmd {
114	m.focused = true
115	return m.editor.Tick()
116}
117
118func (m *editorCmp) GetSize() (int, int) {
119	return m.width, m.height
120}
121
122func (m *editorCmp) IsFocused() bool {
123	return m.focused
124}
125
126func (m *editorCmp) SetSize(width int, height int) {
127	m.width = width
128	m.height = height
129	m.editor.SetSize(width, height)
130}
131
132func (m *editorCmp) Send() tea.Cmd {
133	return func() tea.Msg {
134		content := strings.Join(m.editor.GetBuffer().Lines(), "\n")
135		m.app.Messages.Create(m.sessionID, *schema.UserMessage(content))
136		m.app.LLM.SendRequest(m.sessionID, content)
137		return m.editor.Reset()
138	}
139}
140
141func (m *editorCmp) View() string {
142	return m.editor.View()
143}
144
145func (m *editorCmp) BindingKeys() []key.Binding {
146	return layout.KeyMapToSlice(editorKeyMapValue)
147}
148
149func NewEditorCmp(app *app.App) EditorCmp {
150	return &editorCmp{
151		app:    app,
152		editor: vimtea.NewEditor(),
153	}
154}