editor.go

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