fix: make the queue push the messages above

Kujtim Hoxha created

Change summary

internal/tui/components/chat/chat.go | 106 ++++++++++++++++++-----------
1 file changed, 64 insertions(+), 42 deletions(-)

Detailed changes

internal/tui/components/chat/chat.go 🔗

@@ -2,6 +2,7 @@ package chat
 
 import (
 	"context"
+	"strings"
 	"time"
 
 	"github.com/atotto/clipboard"
@@ -18,7 +19,6 @@ import (
 	"github.com/charmbracelet/crush/internal/tui/exp/list"
 	"github.com/charmbracelet/crush/internal/tui/styles"
 	"github.com/charmbracelet/crush/internal/tui/util"
-	"github.com/charmbracelet/lipgloss/v2"
 )
 
 type SendMsg struct {
@@ -72,6 +72,7 @@ type messageListCmp struct {
 	lastClickX    int
 	lastClickY    int
 	clickCount    int
+	promptQueue   int
 }
 
 // New creates a new message list component with custom keybindings
@@ -101,14 +102,24 @@ func (m *messageListCmp) Init() tea.Cmd {
 
 // Update handles incoming messages and updates the component state.
 func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	var cmds []tea.Cmd
+	if m.session.ID != "" && m.app.CoderAgent != nil {
+		queueSize := m.app.CoderAgent.QueuedPrompts(m.session.ID)
+		if queueSize != m.promptQueue {
+			m.promptQueue = queueSize
+			cmds = append(cmds, m.SetSize(m.width, m.height))
+		}
+	}
 	switch msg := msg.(type) {
 	case tea.KeyPressMsg:
 		if m.listCmp.IsFocused() && m.listCmp.HasSelection() {
 			switch {
 			case key.Matches(msg, messages.CopyKey):
-				return m, m.CopySelectedText(true)
+				cmds = append(cmds, m.CopySelectedText(true))
+				return m, tea.Batch(cmds...)
 			case key.Matches(msg, messages.ClearSelectionKey):
-				return m, m.SelectionClear()
+				cmds = append(cmds, m.SelectionClear())
+				return m, tea.Batch(cmds...)
 			}
 		}
 	case tea.MouseClickMsg:
@@ -118,39 +129,45 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return m, nil // Ignore clicks outside the component
 		}
 		if msg.Button == tea.MouseLeft {
-			return m, m.handleMouseClick(x, y)
+			cmds = append(cmds, m.handleMouseClick(x, y))
+			return m, tea.Batch(cmds...)
 		}
-		return m, nil
+		return m, tea.Batch(cmds...)
 	case tea.MouseMotionMsg:
 		x := msg.X - 1 // Adjust for padding
 		y := msg.Y - 1 // Adjust for padding
 		if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
 			if y < 0 {
-				return m, m.listCmp.MoveUp(1)
+				cmds = append(cmds, m.listCmp.MoveUp(1))
+				return m, tea.Batch(cmds...)
 			}
 			if y >= m.height-1 {
-				return m, m.listCmp.MoveDown(1)
+				cmds = append(cmds, m.listCmp.MoveDown(1))
+				return m, tea.Batch(cmds...)
 			}
 			return m, nil // Ignore clicks outside the component
 		}
 		if msg.Button == tea.MouseLeft {
 			m.listCmp.EndSelection(x, y)
 		}
-		return m, nil
+		return m, tea.Batch(cmds...)
 	case tea.MouseReleaseMsg:
 		x := msg.X - 1 // Adjust for padding
 		y := msg.Y - 1 // Adjust for padding
 		if msg.Button == tea.MouseLeft {
 			clickCount := m.clickCount
 			if x < 0 || y < 0 || x >= m.width-2 || y >= m.height-1 {
-				return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
+				tick := tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
 					return SelectionCopyMsg{
 						clickCount:   clickCount,
 						endSelection: false,
 					}
 				})
+
+				cmds = append(cmds, tick)
+				return m, tea.Batch(cmds...)
 			}
-			return m, tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
+			tick := tea.Tick(doubleClickThreshold, func(time.Time) tea.Msg {
 				return SelectionCopyMsg{
 					clickCount:   clickCount,
 					endSelection: true,
@@ -158,6 +175,8 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 					y:            y,
 				}
 			})
+			cmds = append(cmds, tick)
+			return m, tea.Batch(cmds...)
 		}
 		return m, nil
 	case SelectionCopyMsg:
@@ -167,61 +186,60 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 				m.listCmp.EndSelection(msg.x, msg.y)
 			}
 			m.listCmp.SelectionStop()
-			return m, m.CopySelectedText(true)
+			cmds = append(cmds, m.CopySelectedText(true))
+			return m, tea.Batch(cmds...)
 		}
 	case pubsub.Event[permission.PermissionNotification]:
-		return m, m.handlePermissionRequest(msg.Payload)
+		cmds = append(cmds, m.handlePermissionRequest(msg.Payload))
+		return m, tea.Batch(cmds...)
 	case SessionSelectedMsg:
 		if msg.ID != m.session.ID {
-			cmd := m.SetSession(msg)
-			return m, cmd
+			cmds = append(cmds, m.SetSession(msg))
 		}
-		return m, nil
+		return m, tea.Batch(cmds...)
 	case SessionClearedMsg:
 		m.session = session.Session{}
-		return m, m.listCmp.SetItems([]list.Item{})
+		cmds = append(cmds, m.listCmp.SetItems([]list.Item{}))
+		return m, tea.Batch(cmds...)
 
 	case pubsub.Event[message.Message]:
-		cmd := m.handleMessageEvent(msg)
-		return m, cmd
+		cmds = append(cmds, m.handleMessageEvent(msg))
+		return m, tea.Batch(cmds...)
 
 	case tea.MouseWheelMsg:
 		u, cmd := m.listCmp.Update(msg)
 		m.listCmp = u.(list.List[list.Item])
-		return m, cmd
+		cmds = append(cmds, cmd)
+		return m, tea.Batch(cmds...)
 	}
 
 	u, cmd := m.listCmp.Update(msg)
 	m.listCmp = u.(list.List[list.Item])
-	return m, cmd
+	cmds = append(cmds, cmd)
+	return m, tea.Batch(cmds...)
 }
 
 // View renders the message list or an initial screen if empty.
 func (m *messageListCmp) View() string {
 	t := styles.CurrentTheme()
-	listView := t.S().Base.
-		Padding(1, 1, 0, 1).
-		Width(m.width).
-		Height(m.height).
-		Render(
-			m.listCmp.View(),
-		)
-
-	if m.app.CoderAgent != nil && m.app.CoderAgent.QueuedPrompts(m.session.ID) > 0 {
-		queue := m.app.CoderAgent.QueuedPrompts(m.session.ID)
-		queuePill := queuePill(queue, t)
-		layers := []*lipgloss.Layer{
-			lipgloss.NewLayer(listView),
-			lipgloss.NewLayer(
-				queuePill,
-			).X(4).Y(m.height - 3),
-		}
-		canvas := lipgloss.NewCanvas(
-			layers...,
-		)
-		return canvas.Render()
+	height := m.height
+	if m.promptQueue > 0 {
+		height -= 3
+	}
+	view := []string{
+		t.S().Base.
+			Padding(1, 1, 0, 1).
+			Width(m.width).
+			Height(height).
+			Render(
+				m.listCmp.View(),
+			),
+	}
+	if m.app.CoderAgent != nil && m.promptQueue > 0 {
+		queuePill := queuePill(m.promptQueue, t)
+		view = append(view, t.S().Base.PaddingLeft(4).Render(queuePill))
 	}
-	return listView
+	return strings.Join(view, "\n")
 }
 
 func (m *messageListCmp) handlePermissionRequest(permission permission.PermissionNotification) tea.Cmd {
@@ -638,6 +656,10 @@ func (m *messageListCmp) GetSize() (int, int) {
 func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
 	m.width = width
 	m.height = height
+	if m.promptQueue > 0 {
+		queueHeight := 3
+		return m.listCmp.SetSize(width-2, height-(1+queueHeight))
+	}
 	return m.listCmp.SetSize(width-2, height-1) // for padding
 }