1package chat
2
3import (
4 "github.com/charmbracelet/bubbles/key"
5 "github.com/charmbracelet/bubbles/textarea"
6 tea "github.com/charmbracelet/bubbletea"
7 "github.com/charmbracelet/lipgloss"
8 "github.com/kujtimiihoxha/termai/internal/app"
9 "github.com/kujtimiihoxha/termai/internal/session"
10 "github.com/kujtimiihoxha/termai/internal/tui/layout"
11 "github.com/kujtimiihoxha/termai/internal/tui/styles"
12 "github.com/kujtimiihoxha/termai/internal/tui/util"
13)
14
15type editorCmp struct {
16 app *app.App
17 session session.Session
18 textarea textarea.Model
19}
20
21type focusedEditorKeyMaps struct {
22 Send key.Binding
23 Blur key.Binding
24}
25
26type bluredEditorKeyMaps struct {
27 Send key.Binding
28 Focus key.Binding
29}
30
31var focusedKeyMaps = focusedEditorKeyMaps{
32 Send: key.NewBinding(
33 key.WithKeys("ctrl+s"),
34 key.WithHelp("ctrl+s", "send message"),
35 ),
36 Blur: key.NewBinding(
37 key.WithKeys("esc"),
38 key.WithHelp("esc", "focus messages"),
39 ),
40}
41
42var bluredKeyMaps = bluredEditorKeyMaps{
43 Send: key.NewBinding(
44 key.WithKeys("ctrl+s", "enter"),
45 key.WithHelp("ctrl+s/enter", "send message"),
46 ),
47 Focus: key.NewBinding(
48 key.WithKeys("i"),
49 key.WithHelp("i", "focus editor"),
50 ),
51}
52
53func (m *editorCmp) Init() tea.Cmd {
54 return textarea.Blink
55}
56
57func (m *editorCmp) send() tea.Cmd {
58 if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
59 return util.ReportWarn("Agent is working, please wait...")
60 }
61
62 value := m.textarea.Value()
63 m.textarea.Reset()
64 m.textarea.Blur()
65 if value == "" {
66 return nil
67 }
68 return tea.Batch(
69 util.CmdHandler(SendMsg{
70 Text: value,
71 }),
72 util.CmdHandler(EditorFocusMsg(false)),
73 )
74}
75
76func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
77 var cmd tea.Cmd
78 switch msg := msg.(type) {
79 case SessionSelectedMsg:
80 if msg.ID != m.session.ID {
81 m.session = msg
82 }
83 return m, nil
84 case tea.KeyMsg:
85 // if the key does not match any binding, return
86 if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Send) {
87 return m, m.send()
88 }
89 if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Send) {
90 return m, m.send()
91 }
92 if m.textarea.Focused() && key.Matches(msg, focusedKeyMaps.Blur) {
93 m.textarea.Blur()
94 return m, util.CmdHandler(EditorFocusMsg(false))
95 }
96 if !m.textarea.Focused() && key.Matches(msg, bluredKeyMaps.Focus) {
97 m.textarea.Focus()
98 return m, tea.Batch(textarea.Blink, util.CmdHandler(EditorFocusMsg(true)))
99 }
100 }
101 m.textarea, cmd = m.textarea.Update(msg)
102 return m, cmd
103}
104
105func (m *editorCmp) View() string {
106 style := lipgloss.NewStyle().Padding(0, 0, 0, 1).Bold(true)
107
108 return lipgloss.JoinHorizontal(lipgloss.Top, style.Render(">"), m.textarea.View())
109}
110
111func (m *editorCmp) SetSize(width, height int) {
112 m.textarea.SetWidth(width - 3) // account for the prompt and padding right
113 m.textarea.SetHeight(height)
114}
115
116func (m *editorCmp) GetSize() (int, int) {
117 return m.textarea.Width(), m.textarea.Height()
118}
119
120func (m *editorCmp) BindingKeys() []key.Binding {
121 bindings := layout.KeyMapToSlice(m.textarea.KeyMap)
122 if m.textarea.Focused() {
123 bindings = append(bindings, layout.KeyMapToSlice(focusedKeyMaps)...)
124 } else {
125 bindings = append(bindings, layout.KeyMapToSlice(bluredKeyMaps)...)
126 }
127 return bindings
128}
129
130func NewEditorCmp(app *app.App) tea.Model {
131 ti := textarea.New()
132 ti.Prompt = " "
133 ti.ShowLineNumbers = false
134 ti.BlurredStyle.Base = ti.BlurredStyle.Base.Background(styles.Background)
135 ti.BlurredStyle.CursorLine = ti.BlurredStyle.CursorLine.Background(styles.Background)
136 ti.BlurredStyle.Placeholder = ti.BlurredStyle.Placeholder.Background(styles.Background)
137 ti.BlurredStyle.Text = ti.BlurredStyle.Text.Background(styles.Background)
138
139 ti.FocusedStyle.Base = ti.FocusedStyle.Base.Background(styles.Background)
140 ti.FocusedStyle.CursorLine = ti.FocusedStyle.CursorLine.Background(styles.Background)
141 ti.FocusedStyle.Placeholder = ti.FocusedStyle.Placeholder.Background(styles.Background)
142 ti.FocusedStyle.Text = ti.BlurredStyle.Text.Background(styles.Background)
143 ti.CharLimit = -1
144 ti.Focus()
145 return &editorCmp{
146 app: app,
147 textarea: ti,
148 }
149}