on main help sign in
feat(ui): simplify editor and embed into main UI model
commitd9e67367226436d629078fe392e38bc2d61aefeb authorAyman Bagabas <ayman.bagabas@gmail.com> dateMon 17 Nov 2025, 17:59 (1 year ago) parent35d678c
3 files changed+285−281
internal/ui/model/editor.go−235 internal/ui/model/keys.go+63−1 internal/ui/model/ui.go+222−45
internal/ui/model/editor.godeleted−235
@@ -1,235 +0,0 @@
1−package model
2−
3−import (
4− "math/rand"
5−
6− "charm.land/bubbles/v2/key"
7− "charm.land/bubbles/v2/textarea"
8− tea "charm.land/bubbletea/v2"
9− "github.com/charmbracelet/crush/internal/ui/common"
10−)
11−
12−type EditorKeyMap struct {
13− AddFile key.Binding
14− SendMessage key.Binding
15− OpenEditor key.Binding
16− Newline key.Binding
17−
18− // Attachments key maps
19− AttachmentDeleteMode key.Binding
20− Escape key.Binding
21− DeleteAllAttachments key.Binding
22−}
23−
24−func DefaultEditorKeyMap() EditorKeyMap {
25− return EditorKeyMap{
26− AddFile: key.NewBinding(
27− key.WithKeys("/"),
28− key.WithHelp("/", "add file"),
29− ),
30− SendMessage: key.NewBinding(
31− key.WithKeys("enter"),
32− key.WithHelp("enter", "send"),
33− ),
34− OpenEditor: key.NewBinding(
35− key.WithKeys("ctrl+o"),
36− key.WithHelp("ctrl+o", "open editor"),
37− ),
38− Newline: key.NewBinding(
39− key.WithKeys("shift+enter", "ctrl+j"),
40− // "ctrl+j" is a common keybinding for newline in many editors. If
41− // the terminal supports "shift+enter", we substitute the help text
42− // to reflect that.
43− key.WithHelp("ctrl+j", "newline"),
44− ),
45− AttachmentDeleteMode: key.NewBinding(
46− key.WithKeys("ctrl+r"),
47− key.WithHelp("ctrl+r+{i}", "delete attachment at index i"),
48− ),
49− Escape: key.NewBinding(
50− key.WithKeys("esc", "alt+esc"),
51− key.WithHelp("esc", "cancel delete mode"),
52− ),
53− DeleteAllAttachments: key.NewBinding(
54− key.WithKeys("r"),
55− key.WithHelp("ctrl+r+r", "delete all attachments"),
56− ),
57− }
58−}
59−
60−// EditorModel represents the editor UI model.
61−type EditorModel struct {
62− com *common.Common
63−
64− keyMap EditorKeyMap
65− textarea *textarea.Model
66−
67− attachments []any // TODO: Implement attachments
68−
69− readyPlaceholder string
70− workingPlaceholder string
71−}
72−
73−// NewEditorModel creates a new instance of EditorModel.
74−func NewEditorModel(com *common.Common) *EditorModel {
75− ta := textarea.New()
76− ta.SetStyles(com.Styles.TextArea)
77− ta.ShowLineNumbers = false
78− ta.CharLimit = -1
79− ta.SetVirtualCursor(false)
80− ta.Focus()
81− e := &EditorModel{
82− com: com,
83− keyMap: DefaultEditorKeyMap(),
84− textarea: ta,
85− }
86−
87− e.setEditorPrompt()
88− e.randomizePlaceholders()
89− e.textarea.Placeholder = e.readyPlaceholder
90−
91− return e
92−}
93−
94−// Init initializes the editor model.
95−func (m *EditorModel) Init() tea.Cmd {
96− return nil
97−}
98−
99−// Update handles updates to the editor model.
100−func (m *EditorModel) Update(msg tea.Msg) (*EditorModel, tea.Cmd) {
101− var cmds []tea.Cmd
102− var cmd tea.Cmd
103−
104− m.textarea, cmd = m.textarea.Update(msg)
105− cmds = append(cmds, cmd)
106−
107− // Textarea placeholder logic
108− if m.com.App.AgentCoordinator != nil && m.com.App.AgentCoordinator.IsBusy() {
109− m.textarea.Placeholder = m.workingPlaceholder
110− } else {
111− m.textarea.Placeholder = m.readyPlaceholder
112− }
113− if m.com.App.Permissions.SkipRequests() {
114− m.textarea.Placeholder = "Yolo mode!"
115− }
116−
117− // TODO: Add attachments
118−
119− return m, tea.Batch(cmds...)
120−}
121−
122−// View renders the editor model.
123−func (m *EditorModel) View() string {
124− return m.textarea.View()
125−}
126−
127−// ShortHelp returns the short help view for the editor model.
128−func (m *EditorModel) ShortHelp() []key.Binding {
129− k := m.keyMap
130− binds := []key.Binding{
131− k.AddFile,
132− k.SendMessage,
133− k.OpenEditor,
134− k.Newline,
135− }
136−
137− if len(m.attachments) > 0 {
138− binds = append(binds,
139− k.AttachmentDeleteMode,
140− k.DeleteAllAttachments,
141− k.Escape,
142− )
143− }
144−
145− return binds
146−}
147−
148−// FullHelp returns the full help view for the editor model.
149−func (m *EditorModel) FullHelp() [][]key.Binding {
150− return [][]key.Binding{
151− m.ShortHelp(),
152− }
153−}
154−
155−// Cursor returns the relative cursor position of the editor.
156−func (m *EditorModel) Cursor() *tea.Cursor {
157− return m.textarea.Cursor()
158−}
159−
160−// Blur implements Container.
161−func (m *EditorModel) Blur() tea.Cmd {
162− m.textarea.Blur()
163− return nil
164−}
165−
166−// Focus implements Container.
167−func (m *EditorModel) Focus() tea.Cmd {
168− return m.textarea.Focus()
169−}
170−
171−// Focused returns whether the editor is focused.
172−func (m *EditorModel) Focused() bool {
173− return m.textarea.Focused()
174−}
175−
176−// SetSize sets the size of the editor.
177−func (m *EditorModel) SetSize(width, height int) {
178− m.textarea.SetWidth(width)
179− m.textarea.SetHeight(height)
180−}
181−
182−func (m *EditorModel) setEditorPrompt() {
183− if m.com.App.Permissions.SkipRequests() {
184− m.textarea.SetPromptFunc(4, m.yoloPromptFunc)
185− return
186− }
187− m.textarea.SetPromptFunc(4, m.normalPromptFunc)
188−}
189−
190−func (m *EditorModel) normalPromptFunc(info textarea.PromptInfo) string {
191− t := m.com.Styles
192− if info.LineNumber == 0 {
193− return " > "
194− }
195− if info.Focused {
196− return t.EditorPromptNormalFocused.Render()
197− }
198− return t.EditorPromptNormalBlurred.Render()
199−}
200−
201−func (m *EditorModel) yoloPromptFunc(info textarea.PromptInfo) string {
202− t := m.com.Styles
203− if info.LineNumber == 0 {
204− if info.Focused {
205− return t.EditorPromptYoloIconFocused.Render()
206− } else {
207− return t.EditorPromptYoloIconBlurred.Render()
208− }
209− }
210− if info.Focused {
211− return t.EditorPromptYoloDotsFocused.Render()
212− }
213− return t.EditorPromptYoloDotsBlurred.Render()
214−}
215−
216−var readyPlaceholders = [...]string{
217− "Ready!",
218− "Ready...",
219− "Ready?",
220− "Ready for instructions",
221−}
222−
223−var workingPlaceholders = [...]string{
224− "Working!",
225− "Working...",
226− "Brrrrr...",
227− "Prrrrrrrr...",
228− "Processing...",
229− "Thinking...",
230−}
231−
232−func (m *EditorModel) randomizePlaceholders() {
233− m.workingPlaceholder = workingPlaceholders[rand.Intn(len(workingPlaceholders))]
234− m.readyPlaceholder = readyPlaceholders[rand.Intn(len(readyPlaceholders))]
235−}
internal/ui/model/keys.go+63−1
@@ -3,16 +3,32 @@ package model
33import "charm.land/bubbles/v2/key"
44
55type KeyMap struct {
6+ Editor struct {
7+ AddFile key.Binding
8+ SendMessage key.Binding
9+ OpenEditor key.Binding
10+ Newline key.Binding
11+ AddImage key.Binding
12+ MentionFile key.Binding
13+
14+ // Attachments key maps
15+ AttachmentDeleteMode key.Binding
16+ Escape key.Binding
17+ DeleteAllAttachments key.Binding
18+ }
19+
20+ // Global key maps
621 Quit key.Binding
722 Help key.Binding
823 Commands key.Binding
24+ Models key.Binding
925 Suspend key.Binding
1026 Sessions key.Binding
1127 Tab key.Binding
1228}
1329
1430func DefaultKeyMap() KeyMap {
15− return KeyMap{
31+ km := KeyMap{
1632 Quit: key.NewBinding(
1733 key.WithKeys("ctrl+c"),
1834 key.WithHelp("ctrl+c", "quit"),
@@ -25,6 +41,10 @@ func DefaultKeyMap() KeyMap {
2541 key.WithKeys("ctrl+p"),
2642 key.WithHelp("ctrl+p", "commands"),
2743 ),
44+ Models: key.NewBinding(
45+ key.WithKeys("ctrl+m", "ctrl+l"),
46+ key.WithHelp("ctrl+l", "models"),
47+ ),
2848 Suspend: key.NewBinding(
2949 key.WithKeys("ctrl+z"),
3050 key.WithHelp("ctrl+z", "suspend"),
@@ -38,4 +58,46 @@ func DefaultKeyMap() KeyMap {
3858 key.WithHelp("tab", "change focus"),
3959 ),
4060 }
61+
62+ km.Editor.AddFile = key.NewBinding(
63+ key.WithKeys("/"),
64+ key.WithHelp("/", "add file"),
65+ )
66+ km.Editor.SendMessage = key.NewBinding(
67+ key.WithKeys("enter"),
68+ key.WithHelp("enter", "send"),
69+ )
70+ km.Editor.OpenEditor = key.NewBinding(
71+ key.WithKeys("ctrl+o"),
72+ key.WithHelp("ctrl+o", "open editor"),
73+ )
74+ km.Editor.Newline = key.NewBinding(
75+ key.WithKeys("shift+enter", "ctrl+j"),
76+ // "ctrl+j" is a common keybinding for newline in many editors. If
77+ // the terminal supports "shift+enter", we substitute the help tex
78+ // to reflect that.
79+ key.WithHelp("ctrl+j", "newline"),
80+ )
81+ km.Editor.AddImage = key.NewBinding(
82+ key.WithKeys("ctrl+f"),
83+ key.WithHelp("ctrl+f", "add image"),
84+ )
85+ km.Editor.MentionFile = key.NewBinding(
86+ key.WithKeys("@"),
87+ key.WithHelp("@", "mention file"),
88+ )
89+ km.Editor.AttachmentDeleteMode = key.NewBinding(
90+ key.WithKeys("ctrl+r"),
91+ key.WithHelp("ctrl+r+{i}", "delete attachment at index i"),
92+ )
93+ km.Editor.Escape = key.NewBinding(
94+ key.WithKeys("esc", "alt+esc"),
95+ key.WithHelp("esc", "cancel delete mode"),
96+ )
97+ km.Editor.DeleteAllAttachments = key.NewBinding(
98+ key.WithKeys("r"),
99+ key.WithHelp("ctrl+r+r", "delete all attachments"),
100+ )
101+
102+ return km
41103}
internal/ui/model/ui.go+222−45
@@ -8,8 +8,10 @@ import (
88
99 "charm.land/bubbles/v2/help"
1010 "charm.land/bubbles/v2/key"
11+ "charm.land/bubbles/v2/textarea"
1112 tea "charm.land/bubbletea/v2"
1213 "charm.land/lipgloss/v2"
14+ "github.com/charmbracelet/crush/internal/session"
1315 "github.com/charmbracelet/crush/internal/ui/common"
1416 "github.com/charmbracelet/crush/internal/ui/dialog"
1517 uv "github.com/charmbracelet/ultraviolet"
@@ -20,20 +22,21 @@ type uiState uint8
2022
2123// Possible uiState values.
2224const (
23− uiChat uiState = iota
24− uiEdit
25+ uiEdit uiState = iota
26+ uiChat
2527)
2628
2729// UI represents the main user interface model.
2830type UI struct {
29− com *common.Common
31+ com *common.Common
32+ sess *session.Session
3033
3134 state uiState
3235
3336 keyMap KeyMap
37+ keyenh tea.KeyboardEnhancementsMsg
3438
3539 chat *ChatModel
36− editor *EditorModel
3740 side *SidebarModel
3841 dialog *dialog.Overlay
3942 help help.Model
@@ -47,18 +50,40 @@ type UI struct {
4750 // QueryVersion instructs the TUI to query for the terminal version when it
4851 // starts.
4952 QueryVersion bool
53+
54+ // Editor components
55+ textarea textarea.Model
56+
57+ attachments []any // TODO: Implement attachments
58+
59+ readyPlaceholder string
60+ workingPlaceholder string
5061}
5162
5263// New creates a new instance of the [UI] model.
5364func New(com *common.Common) *UI {
54− return &UI{
55− com: com,
56− dialog: dialog.NewOverlay(),
57− keyMap: DefaultKeyMap(),
58− editor: NewEditorModel(com),
59− side: NewSidebarModel(com),
60− help: help.New(),
65+ // Editor components
66+ ta := textarea.New()
67+ ta.SetStyles(com.Styles.TextArea)
68+ ta.ShowLineNumbers = false
69+ ta.CharLimit = -1
70+ ta.SetVirtualCursor(false)
71+ ta.Focus()
72+
73+ ui := &UI{
74+ com: com,
75+ dialog: dialog.NewOverlay(),
76+ keyMap: DefaultKeyMap(),
77+ side: NewSidebarModel(com),
78+ help: help.New(),
79+ textarea: ta,
6180 }
81+
82+ ui.setEditorPrompt()
83+ ui.randomizePlaceholders()
84+ ui.textarea.Placeholder = ui.readyPlaceholder
85+
86+ return ui
6287}
6388
6489// Init initializes the UI model.
@@ -73,6 +98,7 @@ func (m *UI) Init() tea.Cmd {
7398// Update handles updates to the UI model.
7499func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
75100 var cmds []tea.Cmd
101+ hasDialogs := m.dialog.HasDialogs()
76102 switch msg := msg.(type) {
77103 case tea.EnvMsg:
78104 // Is this Windows Terminal?
@@ -88,18 +114,30 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
88114 return m, nil
89115 case tea.WindowSizeMsg:
90116 m.updateLayoutAndSize(msg.Width, msg.Height)
117+ case tea.KeyboardEnhancementsMsg:
118+ m.keyenh = msg
119+ if msg.SupportsKeyDisambiguation() {
120+ m.keyMap.Models.SetHelp("ctrl+m", "models")
121+ m.keyMap.Editor.Newline.SetHelp("shift+enter", "newline")
122+ }
91123 case tea.KeyPressMsg:
92− if m.dialog.HasDialogs() {
124+ if hasDialogs {
93125 m.updateDialogs(msg, &cmds)
94− } else {
126+ }
127+ }
128+
129+ if !hasDialogs {
130+ // This branch only handles UI elements when there's no dialog shown.
131+ switch msg := msg.(type) {
132+ case tea.KeyPressMsg:
95133 switch {
96134 case key.Matches(msg, m.keyMap.Tab):
97135 if m.state == uiChat {
98136 m.state = uiEdit
99− cmds = append(cmds, m.editor.Focus())
137+ cmds = append(cmds, m.textarea.Focus())
100138 } else {
101139 m.state = uiChat
102− cmds = append(cmds, m.editor.Blur())
140+ m.textarea.Blur()
103141 }
104142 case key.Matches(msg, m.keyMap.Help):
105143 m.help.ShowAll = !m.help.ShowAll
@@ -109,10 +147,31 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
109147 m.dialog.AddDialog(dialog.NewQuit(m.com))
110148 return m, nil
111149 }
150+ case key.Matches(msg, m.keyMap.Commands):
151+ // TODO: Implement me
152+ case key.Matches(msg, m.keyMap.Models):
153+ // TODO: Implement me
154+ case key.Matches(msg, m.keyMap.Sessions):
155+ // TODO: Implement me
112156 default:
113157 m.updateFocused(msg, &cmds)
114158 }
115159 }
160+
161+ // This logic gets triggered on any message type, but should it?
162+ switch m.state {
163+ case uiChat:
164+ case uiEdit:
165+ // Textarea placeholder logic
166+ if m.com.App.AgentCoordinator != nil && m.com.App.AgentCoordinator.IsBusy() {
167+ m.textarea.Placeholder = m.workingPlaceholder
168+ } else {
169+ m.textarea.Placeholder = m.readyPlaceholder
170+ }
171+ if m.com.App.Permissions.SkipRequests() {
172+ m.textarea.Placeholder = "Yolo mode!"
173+ }
174+ }
116175 }
117176
118177 return m, tea.Batch(cmds...)
@@ -126,7 +185,7 @@ func (m *UI) View() tea.View {
126185 layers := []*lipgloss.Layer{}
127186
128187 // Determine the help key map based on focus
129− helpKeyMap := m.focusedKeyMap()
188+ var helpKeyMap help.KeyMap = m
130189
131190 // The screen areas we're working with
132191 area := m.layout.area
@@ -137,11 +196,6 @@ func (m *UI) View() tea.View {
137196
138197 if m.dialog.HasDialogs() {
139198 if dialogView := m.dialog.View(); dialogView != "" {
140− // If the dialog has its own help, use that instead
141− if len(m.dialog.FullHelp()) > 0 || len(m.dialog.ShortHelp()) > 0 {
142− helpKeyMap = m.dialog
143− }
144−
145199 dialogWidth, dialogHeight := lipgloss.Width(dialogView), lipgloss.Height(dialogView)
146200 dialogArea := common.CenterRect(area, dialogWidth, dialogHeight)
147201 layers = append(layers,
@@ -153,8 +207,8 @@ func (m *UI) View() tea.View {
153207 }
154208 }
155209
156− if m.state == uiEdit && m.editor.Focused() {
157− cur := m.editor.Cursor()
210+ if m.state == uiEdit && m.textarea.Focused() {
211+ cur := m.textarea.Cursor()
158212 cur.X++ // Adjust for app margins
159213 cur.Y += editRect.Min.Y
160214 v.Cursor = cur
@@ -171,7 +225,7 @@ func (m *UI) View() tea.View {
171225 ).X(chatRect.Min.X).Y(chatRect.Min.Y),
172226 lipgloss.NewLayer(m.side.View()).
173227 X(sideRect.Min.X).Y(sideRect.Min.Y),
174− lipgloss.NewLayer(m.editor.View()).
228+ lipgloss.NewLayer(m.textarea.View()).
175229 X(editRect.Min.X).Y(editRect.Min.Y),
176230 lipgloss.NewLayer(m.help.View(helpKeyMap)).
177231 X(helpRect.Min.X).Y(helpRect.Min.Y),
@@ -189,11 +243,82 @@ func (m *UI) View() tea.View {
189243 return v
190244}
191245
192−func (m *UI) focusedKeyMap() help.KeyMap {
193− if m.state == uiChat {
194− return m.chat
246+// ShortHelp implements [help.KeyMap].
247+func (m *UI) ShortHelp() []key.Binding {
248+ var binds []key.Binding
249+ k := &m.keyMap
250+
251+ if m.sess == nil {
252+ // no session selected
253+ binds = append(binds,
254+ k.Commands,
255+ k.Models,
256+ k.Editor.Newline,
257+ k.Quit,
258+ k.Help,
259+ )
260+ } else {
261+ // we have a session
195262 }
196− return m.editor
263+
264+ // switch m.state {
265+ // case uiChat:
266+ // case uiEdit:
267+ // binds = append(binds,
268+ // k.Editor.AddFile,
269+ // k.Editor.SendMessage,
270+ // k.Editor.OpenEditor,
271+ // k.Editor.Newline,
272+ // )
273+ //
274+ // if len(m.attachments) > 0 {
275+ // binds = append(binds,
276+ // k.Editor.AttachmentDeleteMode,
277+ // k.Editor.DeleteAllAttachments,
278+ // k.Editor.Escape,
279+ // )
280+ // }
281+ // }
282+
283+ return binds
284+}
285+
286+// FullHelp implements [help.KeyMap].
287+func (m *UI) FullHelp() [][]key.Binding {
288+ var binds [][]key.Binding
289+ k := &m.keyMap
290+ help := k.Help
291+ help.SetHelp("ctrl+g", "less")
292+
293+ if m.sess == nil {
294+ // no session selected
295+ binds = append(binds,
296+ []key.Binding{
297+ k.Commands,
298+ k.Models,
299+ k.Sessions,
300+ },
301+ []key.Binding{
302+ k.Editor.Newline,
303+ k.Editor.AddImage,
304+ k.Editor.MentionFile,
305+ k.Editor.OpenEditor,
306+ },
307+ []key.Binding{
308+ help,
309+ },
310+ )
311+ } else {
312+ // we have a session
313+ }
314+
315+ // switch m.state {
316+ // case uiChat:
317+ // case uiEdit:
318+ // binds = append(binds, m.ShortHelp())
319+ // }
320+
321+ return binds
197322}
198323
199324// updateDialogs updates the dialog overlay with the given message and appends
@@ -213,7 +338,16 @@ func (m *UI) updateFocused(msg tea.KeyPressMsg, cmds *[]tea.Cmd) {
213338 case uiChat:
214339 m.updateChat(msg, cmds)
215340 case uiEdit:
216− m.updateEditor(msg, cmds)
341+ switch {
342+ case key.Matches(msg, m.keyMap.Editor.Newline):
343+ m.textarea.InsertRune('\n')
344+ }
345+
346+ ta, cmd := m.textarea.Update(msg)
347+ m.textarea = ta
348+ if cmd != nil {
349+ *cmds = append(*cmds, cmd)
350+ }
217351 }
218352}
219353
@@ -227,26 +361,13 @@ func (m *UI) updateChat(msg tea.KeyPressMsg, cmds *[]tea.Cmd) {
227361 }
228362}
229363
230−// updateEditor updates the editor model with the given message and appends any
231−// resulting commands to the cmds slice.
232−func (m *UI) updateEditor(msg tea.KeyPressMsg, cmds *[]tea.Cmd) {
233− updatedEditor, cmd := m.editor.Update(msg)
234− m.editor = updatedEditor
235− if cmd != nil {
236− *cmds = append(*cmds, cmd)
237− }
238−}
239−
240364// updateLayoutAndSize updates the layout and sub-models sizes based on the
241365// given terminal width and height given in cells.
242366func (m *UI) updateLayoutAndSize(w, h int) {
243367 // The screen area we're working with
244368 area := image.Rect(0, 0, w, h)
245− helpKeyMap := m.focusedKeyMap()
369+ var helpKeyMap help.KeyMap = m
246370 helpHeight := 1
247− if m.dialog.HasDialogs() && len(m.dialog.FullHelp()) > 0 && len(m.dialog.ShortHelp()) > 0 {
248− helpKeyMap = m.dialog
249− }
250371 if m.help.ShowAll {
251372 for _, row := range helpKeyMap.FullHelp() {
252373 helpHeight = max(helpHeight, len(row))
@@ -280,8 +401,9 @@ func (m *UI) updateLayoutAndSize(w, h int) {
280401
281402 // Update sub-model sizes
282403 m.side.SetWidth(m.layout.sidebar.Dx())
283− m.editor.SetSize(m.layout.editor.Dx(), m.layout.editor.Dy())
284− m.help.Width = m.layout.help.Dx()
404+ m.textarea.SetWidth(m.layout.editor.Dx())
405+ m.textarea.SetHeight(m.layout.editor.Dy())
406+ m.help.SetWidth(m.layout.help.Dx())
285407}
286408
287409// layout defines the positioning of UI elements.
@@ -304,3 +426,58 @@ type layout struct {
304426 // help is the area for the help view.
305427 help uv.Rectangle
306428}
429+
430+func (m *UI) setEditorPrompt() {
431+ if m.com.App.Permissions.SkipRequests() {
432+ m.textarea.SetPromptFunc(4, m.yoloPromptFunc)
433+ return
434+ }
435+ m.textarea.SetPromptFunc(4, m.normalPromptFunc)
436+}
437+
438+func (m *UI) normalPromptFunc(info textarea.PromptInfo) string {
439+ t := m.com.Styles
440+ if info.LineNumber == 0 {
441+ return " > "
442+ }
443+ if info.Focused {
444+ return t.EditorPromptNormalFocused.Render()
445+ }
446+ return t.EditorPromptNormalBlurred.Render()
447+}
448+
449+func (m *UI) yoloPromptFunc(info textarea.PromptInfo) string {
450+ t := m.com.Styles
451+ if info.LineNumber == 0 {
452+ if info.Focused {
453+ return t.EditorPromptYoloIconFocused.Render()
454+ } else {
455+ return t.EditorPromptYoloIconBlurred.Render()
456+ }
457+ }
458+ if info.Focused {
459+ return t.EditorPromptYoloDotsFocused.Render()
460+ }
461+ return t.EditorPromptYoloDotsBlurred.Render()
462+}
463+
464+var readyPlaceholders = [...]string{
465+ "Ready!",
466+ "Ready...",
467+ "Ready?",
468+ "Ready for instructions",
469+}
470+
471+var workingPlaceholders = [...]string{
472+ "Working!",
473+ "Working...",
474+ "Brrrrr...",
475+ "Prrrrrrrr...",
476+ "Processing...",
477+ "Thinking...",
478+}
479+
480+func (m *UI) randomizePlaceholders() {
481+ m.workingPlaceholder = workingPlaceholders[rand.Intn(len(workingPlaceholders))]
482+ m.readyPlaceholder = readyPlaceholders[rand.Intn(len(readyPlaceholders))]
483+}