@@ -2,6 +2,7 @@ package chat
7 "github.com/atotto/clipboard"
8 "github.com/atotto/clipboard"
@@ -18,7 +19,6 @@ import (
18 "github.com/charmbracelet/crush/internal/tui/exp/list"
19 "github.com/charmbracelet/crush/internal/tui/exp/list"
19 "github.com/charmbracelet/crush/internal/tui/styles"
20 "github.com/charmbracelet/crush/internal/tui/styles"
20 "github.com/charmbracelet/crush/internal/tui/util"
21 "github.com/charmbracelet/crush/internal/tui/util"
21− "github.com/charmbracelet/lipgloss/v2"
24type SendMsg struct {
24type SendMsg struct {
@@ -72,6 +72,7 @@ type messageListCmp struct {
72 lastClickX int
72 lastClickX int
73 lastClickY int
73 lastClickY int
74 clickCount int
74 clickCount int
77// New creates a new message list component with custom keybindings
78// New creates a new message list component with custom keybindings
@@ -101,14 +102,24 @@ func (m *messageListCmp) Init() tea.Cmd {
102// Update handles incoming messages and updates the component state.
103// Update handles incoming messages and updates the component state.
103func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, 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))
104 switch msg := msg.(type) {
113 switch msg := msg.(type) {
105 case tea.KeyPressMsg:
114 case tea.KeyPressMsg:
106 if m.listCmp.IsFocused() && m.listCmp.HasSelection() {
115 if m.listCmp.IsFocused() && m.listCmp.HasSelection() {
108 case key.Matches(msg, messages.CopyKey):
117 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...)
110 case key.Matches(msg, messages.ClearSelectionKey):
120 case key.Matches(msg, messages.ClearSelectionKey):
111− return m, m.SelectionClear()
121+ cmds = append(cmds, m.SelectionClear())
122+ return m, tea.Batch(cmds...)
114 case tea.MouseClickMsg:
125 case tea.MouseClickMsg:
@@ -118,39 +129,45 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
118 return m, nil // Ignore clicks outside the component
129 return m, nil // Ignore clicks outside the component
120 if msg.Button == tea.MouseLeft {
131 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...)
123− return m, nil
135+ return m, tea.Batch(cmds...)
124 case tea.MouseMotionMsg:
136 case tea.MouseMotionMsg:
125 x := msg.X - 1 // Adjust for padding
137 x := msg.X - 1 // Adjust for padding
126 y := msg.Y - 1 // Adjust for padding
138 y := msg.Y - 1 // Adjust for padding
127 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
139 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
128 if y < 0 {
140 if y < 0 {
129− return m, m.listCmp.MoveUp(1)
141+ cmds = append(cmds, m.listCmp.MoveUp(1))
142+ return m, tea.Batch(cmds...)
131 if y >= m.height-1 {
144 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...)
134 return m, nil // Ignore clicks outside the component
148 return m, nil // Ignore clicks outside the component
136 if msg.Button == tea.MouseLeft {
150 if msg.Button == tea.MouseLeft {
137 m.listCmp.EndSelection(x, y)
151 m.listCmp.EndSelection(x, y)
139− return m, nil
153+ return m, tea.Batch(cmds...)
140 case tea.MouseReleaseMsg:
154 case tea.MouseReleaseMsg:
141 x := msg.X - 1 // Adjust for padding
155 x := msg.X - 1 // Adjust for padding
142 y := msg.Y - 1 // Adjust for padding
156 y := msg.Y - 1 // Adjust for padding
143 if msg.Button == tea.MouseLeft {
157 if msg.Button == tea.MouseLeft {
144 clickCount := m.clickCount
158 clickCount := m.clickCount
145 if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
159 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 {
147 return SelectionCopyMsg{
161 return SelectionCopyMsg{
148 clickCount: clickCount,
162 clickCount: clickCount,
149 endSelection: false,
163 endSelection: false,
167+ cmds = append(cmds, tick)
168+ return m, tea.Batch(cmds...)
153− return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
170+ tick := tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
154 return SelectionCopyMsg{
171 return SelectionCopyMsg{
155 clickCount: clickCount,
172 clickCount: clickCount,
156 endSelection: true,
173 endSelection: true,
@@ -158,6 +175,8 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
178+ cmds = append(cmds, tick)
179+ return m, tea.Batch(cmds...)
162 return m, nil
181 return m, nil
163 case SelectionCopyMsg:
182 case SelectionCopyMsg:
@@ -167,61 +186,60 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
167 m.listCmp.EndSelection(msg.x, msg.y)
186 m.listCmp.EndSelection(msg.x, msg.y)
169 m.listCmp.SelectionStop()
188 m.listCmp.SelectionStop()
170− return m, m.CopySelectedText(true)
189+ cmds = append(cmds, m.CopySelectedText(true))
190+ return m, tea.Batch(cmds...)
172 case pubsub.Event[permission.PermissionNotification]:
192 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...)
174 case SessionSelectedMsg:
195 case SessionSelectedMsg:
175 if msg.ID != m.session.ID {
196 if msg.ID != m.session.ID {
176− cmd := m.SetSession(msg)
197+ cmds = append(cmds, m.SetSession(msg))
179− return m, nil
199+ return m, tea.Batch(cmds...)
180 case SessionClearedMsg:
200 case SessionClearedMsg:
181 m.session = session.Session{}
201 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...)
184 case pubsub.Event[message.Message]:
205 case pubsub.Event[message.Message]:
185− cmd := m.handleMessageEvent(msg)
206+ cmds = append(cmds, m.handleMessageEvent(msg))
186− return m, cmd
207+ return m, tea.Batch(cmds...)
188 case tea.MouseWheelMsg:
209 case tea.MouseWheelMsg:
189 u, cmd := m.listCmp.Update(msg)
210 u, cmd := m.listCmp.Update(msg)
190 m.listCmp = u.(list.List[list.Item])
211 m.listCmp = u.(list.List[list.Item])
212+ cmds = append(cmds, cmd)
191− return m, cmd
213+ return m, tea.Batch(cmds...)
194 u, cmd := m.listCmp.Update(msg)
216 u, cmd := m.listCmp.Update(msg)
195 m.listCmp = u.(list.List[list.Item])
217 m.listCmp = u.(list.List[list.Item])
218+ cmds = append(cmds, cmd)
196− return m, cmd
219+ return m, tea.Batch(cmds...)
199// View renders the message list or an initial screen if empty.
222// View renders the message list or an initial screen if empty.
200func (m *messageListCmp) View() string {
223func (m *messageListCmp) View() string {
201 t := styles.CurrentTheme()
224 t := styles.CurrentTheme()
226+ if m.promptQueue > 0 {
202− listView := t.S().Base.
230+ t.S().Base.
203− Padding(1, 1, 0, 1).
231+ Padding(1, 1, 0, 1).
204− Width(m.width).
232+ Width(m.width).
205− Height(m.height).
233+ Height(height).
207− m.listCmp.View(),
235+ m.listCmp.View(),
210− if m.app.CoderAgent != nil && m.app.CoderAgent.QueuedPrompts(m.session.ID) > 0 {
238+ if m.app.CoderAgent != nil && m.promptQueue > 0 {
211− queue := m.app.CoderAgent.QueuedPrompts(m.session.ID)
239+ queuePill := queuePill(m.promptQueue, t)
212− queuePill := queuePill(queue, t)
240+ view = append(view, t.S().Base.PaddingLeft(4).Render(queuePill))
213− layers := []*lipgloss.Layer{
214− lipgloss.NewLayer(listView),
217− ).X(4).Y(m.height - 3),
219− canvas := lipgloss.NewCanvas(
222− return canvas.Render()
224− return listView
242+ return strings.Join(view, "\n")
227func (m *messageListCmp) handlePermissionRequest(permission permission.PermissionNotification) tea.Cmd {
245func (m *messageListCmp) handlePermissionRequest(permission permission.PermissionNotification) tea.Cmd {
@@ -638,6 +656,10 @@ func (m *messageListCmp) GetSize() (int, int) {
638func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
656func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
639 m.width = width
657 m.width = width
640 m.height = height
658 m.height = height
659+ if m.promptQueue > 0 {
661+ return m.listCmp.SetSize(width-2, height-(1+queueHeight))
641 return m.listCmp.SetSize(width-2, height-1) // for padding
663 return m.listCmp.SetSize(width-2, height-1) // for padding