@@ -2,6 +2,7 @@ package chat
22
33import (
44 "context"
5+ "strings"
56 "time"
67
78 "github.com/atotto/clipboard"
@@ -18,7 +19,6 @@ import (
1819 "github.com/charmbracelet/crush/internal/tui/exp/list"
1920 "github.com/charmbracelet/crush/internal/tui/styles"
2021 "github.com/charmbracelet/crush/internal/tui/util"
21− "github.com/charmbracelet/lipgloss/v2"
2222)
2323
2424type SendMsg struct {
@@ -72,6 +72,7 @@ type messageListCmp struct {
7272 lastClickX int
7373 lastClickY int
7474 clickCount int
75+ promptQueue int
7576}
7677
7778// New creates a new message list component with custom keybindings
@@ -101,14 +102,24 @@ func (m *messageListCmp) Init() tea.Cmd {
101102
102103// Update handles incoming messages and updates the component state.
103104func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
105+ var cmds []tea.Cmd
106+ if m.session.ID != "" && m.app.CoderAgent != nil {
107+ queueSize := m.app.CoderAgent.QueuedPrompts(m.session.ID)
108+ if queueSize != m.promptQueue {
109+ m.promptQueue = queueSize
110+ cmds = append(cmds, m.SetSize(m.width, m.height))
111+ }
112+ }
104113 switch msg := msg.(type) {
105114 case tea.KeyPressMsg:
106115 if m.listCmp.IsFocused() && m.listCmp.HasSelection() {
107116 switch {
108117 case key.Matches(msg, messages.CopyKey):
109− return m, m.CopySelectedText(true)
118+ cmds = append(cmds, m.CopySelectedText(true))
119+ return m, tea.Batch(cmds...)
110120 case key.Matches(msg, messages.ClearSelectionKey):
111− return m, m.SelectionClear()
121+ cmds = append(cmds, m.SelectionClear())
122+ return m, tea.Batch(cmds...)
112123 }
113124 }
114125 case tea.MouseClickMsg:
@@ -118,39 +129,45 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
118129 return m, nil // Ignore clicks outside the component
119130 }
120131 if msg.Button == tea.MouseLeft {
121− return m, m.handleMouseClick(x, y)
132+ cmds = append(cmds, m.handleMouseClick(x, y))
133+ return m, tea.Batch(cmds...)
122134 }
123− return m, nil
135+ return m, tea.Batch(cmds...)
124136 case tea.MouseMotionMsg:
125137 x := msg.X - 1 // Adjust for padding
126138 y := msg.Y - 1 // Adjust for padding
127139 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
128140 if y < 0 {
129− return m, m.listCmp.MoveUp(1)
141+ cmds = append(cmds, m.listCmp.MoveUp(1))
142+ return m, tea.Batch(cmds...)
130143 }
131144 if y >= m.height-1 {
132− return m, m.listCmp.MoveDown(1)
145+ cmds = append(cmds, m.listCmp.MoveDown(1))
146+ return m, tea.Batch(cmds...)
133147 }
134148 return m, nil // Ignore clicks outside the component
135149 }
136150 if msg.Button == tea.MouseLeft {
137151 m.listCmp.EndSelection(x, y)
138152 }
139− return m, nil
153+ return m, tea.Batch(cmds...)
140154 case tea.MouseReleaseMsg:
141155 x := msg.X - 1 // Adjust for padding
142156 y := msg.Y - 1 // Adjust for padding
143157 if msg.Button == tea.MouseLeft {
144158 clickCount := m.clickCount
145159 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
146− return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
160+ tick := tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
147161 return SelectionCopyMsg{
148162 clickCount: clickCount,
149163 endSelection: false,
150164 }
151165 })
166+
167+ cmds = append(cmds, tick)
168+ return m, tea.Batch(cmds...)
152169 }
153− return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
170+ tick := tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
154171 return SelectionCopyMsg{
155172 clickCount: clickCount,
156173 endSelection: true,
@@ -158,6 +175,8 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
158175 y: y,
159176 }
160177 })
178+ cmds = append(cmds, tick)
179+ return m, tea.Batch(cmds...)
161180 }
162181 return m, nil
163182 case SelectionCopyMsg:
@@ -167,61 +186,60 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
167186 m.listCmp.EndSelection(msg.x, msg.y)
168187 }
169188 m.listCmp.SelectionStop()
170− return m, m.CopySelectedText(true)
189+ cmds = append(cmds, m.CopySelectedText(true))
190+ return m, tea.Batch(cmds...)
171191 }
172192 case pubsub.Event[permission.PermissionNotification]:
173− return m, m.handlePermissionRequest(msg.Payload)
193+ cmds = append(cmds, m.handlePermissionRequest(msg.Payload))
194+ return m, tea.Batch(cmds...)
174195 case SessionSelectedMsg:
175196 if msg.ID != m.session.ID {
176− cmd := m.SetSession(msg)
177− return m, cmd
197+ cmds = append(cmds, m.SetSession(msg))
178198 }
179− return m, nil
199+ return m, tea.Batch(cmds...)
180200 case SessionClearedMsg:
181201 m.session = session.Session{}
182− return m, m.listCmp.SetItems([]list.Item{})
202+ cmds = append(cmds, m.listCmp.SetItems([]list.Item{}))
203+ return m, tea.Batch(cmds...)
183204
184205 case pubsub.Event[message.Message]:
185− cmd := m.handleMessageEvent(msg)
186− return m, cmd
206+ cmds = append(cmds, m.handleMessageEvent(msg))
207+ return m, tea.Batch(cmds...)
187208
188209 case tea.MouseWheelMsg:
189210 u, cmd := m.listCmp.Update(msg)
190211 m.listCmp = u.(list.List[list.Item])
191− return m, cmd
212+ cmds = append(cmds, cmd)
213+ return m, tea.Batch(cmds...)
192214 }
193215
194216 u, cmd := m.listCmp.Update(msg)
195217 m.listCmp = u.(list.List[list.Item])
196− return m, cmd
218+ cmds = append(cmds, cmd)
219+ return m, tea.Batch(cmds...)
197220}
198221
199222// View renders the message list or an initial screen if empty.
200223func (m *messageListCmp) View() string {
201224 t := styles.CurrentTheme()
202− listView := t.S().Base.
203− Padding(1, 1, 0, 1).
204− Width(m.width).
205− Height(m.height).
206− Render(
207− m.listCmp.View(),
208− )
209−
210− if m.app.CoderAgent != nil && m.app.CoderAgent.QueuedPrompts(m.session.ID) > 0 {
211− queue := m.app.CoderAgent.QueuedPrompts(m.session.ID)
212− queuePill := queuePill(queue, t)
213− layers := []*lipgloss.Layer{
214− lipgloss.NewLayer(listView),
215− lipgloss.NewLayer(
216− queuePill,
217− ).X(4).Y(m.height - 3),
218− }
219− canvas := lipgloss.NewCanvas(
220− layers...,
221− )
222− return canvas.Render()
225+ height := m.height
226+ if m.promptQueue > 0 {
227+ height -= 3
228+ }
229+ view := []string{
230+ t.S().Base.
231+ Padding(1, 1, 0, 1).
232+ Width(m.width).
233+ Height(height).
234+ Render(
235+ m.listCmp.View(),
236+ ),
237+ }
238+ if m.app.CoderAgent != nil && m.promptQueue > 0 {
239+ queuePill := queuePill(m.promptQueue, t)
240+ view = append(view, t.S().Base.PaddingLeft(4).Render(queuePill))
223241 }
224− return listView
242+ return strings.Join(view, "\n")
225243}
226244
227245func (m *messageListCmp) handlePermissionRequest(permission permission.PermissionNotification) tea.Cmd {
@@ -638,6 +656,10 @@ func (m *messageListCmp) GetSize() (int, int) {
638656func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
639657 m.width = width
640658 m.height = height
659+ if m.promptQueue > 0 {
660+ queueHeight := 3
661+ return m.listCmp.SetSize(width-2, height-(1+queueHeight))
662+ }
641663 return m.listCmp.SetSize(width-2, height-1) // for padding
642664}
643665