@@ -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
}