diff --git a/crush.json b/crush.json index 1b04ea6c24f8b64a3a12ceb47551f3177fa66302..1857ae8fe1bc925326aeccb1a0ceb26362f1f062 100644 --- a/crush.json +++ b/crush.json @@ -3,5 +3,14 @@ "Go": { "command": "gopls" } + }, + "mcp": { + "linear": { + "type": "stdio", + "command": "mcp-remote", + "args": [ + "https://mcp.linear.app/sse" + ] + } } } diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index 1629bc4c5638e24274c0a658f0f8c5de5bb71b59..fe1df1fb4fc9014407095db465e79409e693462d 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -40,6 +40,7 @@ type MessageListCmp interface { layout.Help SetSession(session.Session) tea.Cmd + GoToBottom() tea.Cmd } // messageListCmp implements MessageListCmp, providing a virtualized list @@ -64,6 +65,7 @@ func New(app *app.App) MessageListCmp { []list.Item{}, list.WithGap(1), list.WithDirectionBackward(), + list.WithFocus(false), list.WithKeyMap(defaultListKeyMap), ) return &messageListCmp{ @@ -76,7 +78,7 @@ func New(app *app.App) MessageListCmp { // Init initializes the component. func (m *messageListCmp) Init() tea.Cmd { - return tea.Sequence(m.listCmp.Init(), m.listCmp.Blur()) + return m.listCmp.Init() } // Update handles incoming messages and updates the component state. @@ -531,3 +533,7 @@ func (m *messageListCmp) IsFocused() bool { func (m *messageListCmp) Bindings() []key.Binding { return m.defaultListKeyMap.KeyBindings() } + +func (m *messageListCmp) GoToBottom() tea.Cmd { + return m.listCmp.GoToBottom() +} diff --git a/internal/tui/exp/list/list.go b/internal/tui/exp/list/list.go index 201e02ca04dbc8f7d2e5b6152cebb835fe16c347..afcf0a2bc9c1148b559d878445d8be169ca6ea9f 100644 --- a/internal/tui/exp/list/list.go +++ b/internal/tui/exp/list/list.go @@ -1,10 +1,12 @@ package list import ( + "slices" "strings" "github.com/charmbracelet/bubbles/v2/key" tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/crush/internal/tui/components/anim" "github.com/charmbracelet/crush/internal/tui/components/core/layout" "github.com/charmbracelet/crush/internal/tui/util" "github.com/charmbracelet/lipgloss/v2" @@ -16,6 +18,10 @@ type Item interface { ID() string } +type HasAnim interface { + Item + Spinning() bool +} type ( renderedMsg struct{} List[T Item] interface { @@ -172,6 +178,18 @@ func (l *list[T]) Init() tea.Cmd { // Update implements List. func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case anim.StepMsg: + var cmds []tea.Cmd + for _, item := range l.items { + if i, ok := any(item).(HasAnim); ok && i.Spinning() { + updated, cmd := i.Update(msg) + cmds = append(cmds, cmd) + if u, ok := updated.(T); ok { + cmds = append(cmds, l.UpdateItem(u.ID(), u)) + } + } + } + return l, tea.Batch(cmds...) case tea.KeyPressMsg: if l.focused { switch { @@ -553,8 +571,44 @@ func (l *list[T]) renderItem(item Item) renderedItem { } // AppendItem implements List. -func (l *list[T]) AppendItem(T) tea.Cmd { - panic("unimplemented") +func (l *list[T]) AppendItem(item T) tea.Cmd { + var cmds []tea.Cmd + cmd := item.Init() + if cmd != nil { + cmds = append(cmds, cmd) + } + + l.items = append(l.items, item) + l.indexMap = make(map[string]int) + for inx, item := range l.items { + l.indexMap[item.ID()] = inx + } + if l.width > 0 && l.height > 0 { + cmd = item.SetSize(l.width, l.height) + if cmd != nil { + cmds = append(cmds, cmd) + } + } + cmd = l.render() + if cmd != nil { + cmds = append(cmds, cmd) + } + if l.direction == DirectionBackward { + if l.offset == 0 { + cmd = l.GoToBottom() + if cmd != nil { + cmds = append(cmds, cmd) + } + } else { + newItem := l.renderedItems[item.ID()] + newLines := newItem.height + if len(l.items) > 1 { + newLines += l.gap + } + l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines) + } + } + return tea.Sequence(cmds...) } // Blur implements List. @@ -564,8 +618,34 @@ func (l *list[T]) Blur() tea.Cmd { } // DeleteItem implements List. -func (l *list[T]) DeleteItem(string) tea.Cmd { - panic("unimplemented") +func (l *list[T]) DeleteItem(id string) tea.Cmd { + inx := l.indexMap[id] + l.items = slices.Delete(l.items, inx, inx+1) + delete(l.renderedItems, id) + for inx, item := range l.items { + l.indexMap[item.ID()] = inx + } + + if l.selectedItem == id { + if inx > 0 { + l.selectedItem = l.items[inx-1].ID() + } else { + l.selectedItem = "" + } + } + cmd := l.render() + if l.rendered != "" { + renderedHeight := lipgloss.Height(l.rendered) + if renderedHeight <= l.height { + l.offset = 0 + } else { + maxOffset := renderedHeight - l.height + if l.offset > maxOffset { + l.offset = maxOffset + } + } + } + return cmd } // Focus implements List. @@ -651,8 +731,35 @@ func (l *list[T]) MoveUp(n int) tea.Cmd { } // PrependItem implements List. -func (l *list[T]) PrependItem(T) tea.Cmd { - panic("unimplemented") +func (l *list[T]) PrependItem(item T) tea.Cmd { + cmds := []tea.Cmd{ + item.Init(), + } + l.items = append([]T{item}, l.items...) + l.indexMap = make(map[string]int) + for inx, item := range l.items { + l.indexMap[item.ID()] = inx + } + if l.width > 0 && l.height > 0 { + cmds = append(cmds, item.SetSize(l.width, l.height)) + } + cmds = append(cmds, l.render()) + if l.direction == DirectionForward { + if l.offset == 0 { + cmd := l.GoToTop() + if cmd != nil { + cmds = append(cmds, cmd) + } + } else { + newItem := l.renderedItems[item.ID()] + newLines := newItem.height + if len(l.items) > 1 { + newLines += l.gap + } + l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines) + } + } + return tea.Batch(cmds...) } // SelectItemAbove implements List. @@ -707,7 +814,12 @@ func (l *list[T]) SelectedItem() *T { // SetItems implements List. func (l *list[T]) SetItems(items []T) tea.Cmd { l.items = items - return l.reset() + var cmds []tea.Cmd + for _, item := range l.items { + cmds = append(cmds, item.Init()) + } + cmds = append(cmds, l.reset()) + return tea.Batch(cmds...) } // SetSelected implements List. @@ -745,6 +857,42 @@ func (l *list[T]) SetSize(width int, height int) tea.Cmd { } // UpdateItem implements List. -func (l *list[T]) UpdateItem(string, T) tea.Cmd { - panic("unimplemented") +func (l *list[T]) UpdateItem(id string, item T) tea.Cmd { + var cmds []tea.Cmd + if inx, ok := l.indexMap[id]; ok { + l.items[inx] = item + oldItem := l.renderedItems[id] + oldPosition := l.offset + if l.direction == DirectionBackward { + oldPosition = (lipgloss.Height(l.rendered) - 1) - l.offset + } + + delete(l.renderedItems, id) + cmd := l.render() + + // need to check for nil because of sequence not handling nil + if cmd != nil { + cmds = append(cmds, cmd) + } + if l.direction == DirectionBackward { + // if we are the last item and there is no offset + // make sure to go to the bottom + if inx == len(l.items)-1 && l.offset == 0 { + cmd = l.GoToBottom() + if cmd != nil { + cmds = append(cmds, cmd) + } + // if the item is at least partially below the viewport + } else if oldPosition < oldItem.end { + newItem := l.renderedItems[item.ID()] + newLines := newItem.height - oldItem.height + l.offset = util.Clamp(l.offset+newLines, 0, lipgloss.Height(l.rendered)-1) + } + } else if l.offset > oldItem.start { + newItem := l.renderedItems[item.ID()] + newLines := newItem.height - oldItem.height + l.offset = util.Clamp(l.offset+newLines, 0, lipgloss.Height(l.rendered)-1) + } + } + return tea.Sequence(cmds...) } diff --git a/internal/tui/exp/list/list_test.go b/internal/tui/exp/list/list_test.go index e822632502b8ee26b884e825cf3219a411af20dc..933f061760653f38c8dc49787017740bcc5d58e5 100644 --- a/internal/tui/exp/list/list_test.go +++ b/internal/tui/exp/list/list_test.go @@ -315,6 +315,243 @@ func TestListMovement(t *testing.T) { assert.Equal(t, 0, l.offset) golden.RequireEqual(t, []byte(l.View())) }) + + t.Run("should not change offset when new items are appended and we are at the bottom in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + content := strings.Repeat(fmt.Sprintf("Item %d\n", i), i+1) + content = strings.TrimSuffix(content, "\n") + item := NewSelectableItem(content) + items = append(items, item) + } + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + execCmd(l, l.AppendItem(NewSelectableItem("Testing"))) + + assert.Equal(t, 0, l.offset) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should stay at the position it is when new items are added but we moved up in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveUp(2)) + viewBefore := l.View() + execCmd(l, l.AppendItem(NewSelectableItem("Testing\nHello\n"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 5, l.offset) + assert.Equal(t, 33, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should stay at the position it is when the hight of an item below is increased in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveUp(2)) + viewBefore := l.View() + item := items[29] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 4, l.offset) + assert.Equal(t, 32, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should stay at the position it is when the hight of an item below is decreases in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + items = append(items, NewSelectableItem("Item 30\nLine 2\nLine 3")) + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveUp(2)) + viewBefore := l.View() + item := items[30] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 30"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 0, l.offset) + assert.Equal(t, 31, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should stay at the position it is when the hight of an item above is increased in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveUp(2)) + viewBefore := l.View() + item := items[1] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 1\nLine 2\nLine 3"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 2, l.offset) + assert.Equal(t, 32, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should stay at the position it is if an item is prepended and we are in backwards list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionBackward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveUp(2)) + viewBefore := l.View() + execCmd(l, l.PrependItem(NewSelectableItem("New"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 2, l.offset) + assert.Equal(t, 31, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should not change offset when new items are prepended and we are at the top in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + content := strings.Repeat(fmt.Sprintf("Item %d\n", i), i+1) + content = strings.TrimSuffix(content, "\n") + item := NewSelectableItem(content) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + execCmd(l, l.PrependItem(NewSelectableItem("Testing"))) + + assert.Equal(t, 0, l.offset) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should stay at the position it is when new items are added but we moved down in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveDown(2)) + viewBefore := l.View() + execCmd(l, l.PrependItem(NewSelectableItem("Testing\nHello\n"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 5, l.offset) + assert.Equal(t, 33, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should stay at the position it is when the hight of an item above is increased in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveDown(2)) + viewBefore := l.View() + item := items[0] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 4, l.offset) + assert.Equal(t, 32, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should stay at the position it is when the hight of an item above is decreases in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + items = append(items, NewSelectableItem("At top\nLine 2\nLine 3")) + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveDown(3)) + viewBefore := l.View() + item := items[0] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("At top"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 1, l.offset) + assert.Equal(t, 31, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + + t.Run("should stay at the position it is when the hight of an item below is increased in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveDown(2)) + viewBefore := l.View() + item := items[29] + execCmd(l, l.UpdateItem(item.ID(), NewSelectableItem("Item 29\nLine 2\nLine 3"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 2, l.offset) + assert.Equal(t, 32, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) + t.Run("should stay at the position it is if an item is appended and we are in forward list", func(t *testing.T) { + t.Parallel() + items := []Item{} + for i := range 30 { + item := NewSelectableItem(fmt.Sprintf("Item %d", i)) + items = append(items, item) + } + l := New(items, WithDirectionForward(), WithSize(10, 10)).(*list[Item]) + execCmd(l, l.Init()) + + execCmd(l, l.MoveDown(2)) + viewBefore := l.View() + execCmd(l, l.AppendItem(NewSelectableItem("New"))) + viewAfter := l.View() + assert.Equal(t, viewBefore, viewAfter) + assert.Equal(t, 2, l.offset) + assert.Equal(t, 31, lipgloss.Height(l.rendered)) + golden.RequireEqual(t, []byte(l.View())) + }) } type SelectableItem interface { diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_bottom_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_bottom_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..03dce1dac791cad0516fd70cfa5bf5d1ec73bee4 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_bottom_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +│Testing \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_botton.golden b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_botton.golden new file mode 100644 index 0000000000000000000000000000000000000000..03dce1dac791cad0516fd70cfa5bf5d1ec73bee4 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_appended_and_we_are_at_the_botton.golden @@ -0,0 +1,10 @@ +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +Item 29 +│Testing \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_prepended_and_we_are_at_the_top_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_prepended_and_we_are_at_the_top_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..efcdc73a7d9573692365723a1ba65a8773d3a3c2 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_not_change_offset_when_new_items_are_prepended_and_we_are_at_the_top_in_forward_list.golden @@ -0,0 +1,10 @@ +│Testing +Item 0 +Item 1 +Item 1 +Item 2 +Item 2 +Item 2 +Item 3 +Item 3 +Item 3 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_appended_and_we_are_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_appended_and_we_are_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..90740d28955595061e79772cf8011a7571712205 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_appended_and_we_are_in_forward_list.golden @@ -0,0 +1,10 @@ +│Item 2 +Item 3 +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 +Item 10 +Item 11 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_prepended_and_we_are_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_prepended_and_we_are_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_if_an_item_is_prepended_and_we_are_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_down_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_down_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..90740d28955595061e79772cf8011a7571712205 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_down_in_forward_list.golden @@ -0,0 +1,10 @@ +│Item 2 +Item 3 +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 +Item 10 +Item 11 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_new_items_are_added_but_we_moved_up_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_decreases_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_decreases_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..46269dd405b643eef664dafb388d2001ffacc923 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_decreases_in_forward_list.golden @@ -0,0 +1,10 @@ +│Item 0 +Item 1 +Item 2 +Item 3 +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..90740d28955595061e79772cf8011a7571712205 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_above_is_increased_in_forward_list.golden @@ -0,0 +1,10 @@ +│Item 2 +Item 3 +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 +Item 10 +Item 11 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases.golden new file mode 100644 index 0000000000000000000000000000000000000000..77d3450cede66562f85e422c7c4199240231f11b --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases.golden @@ -0,0 +1,10 @@ +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +Item 27 +Item 28 +│Item 29 +Item 30 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..77d3450cede66562f85e422c7c4199240231f11b --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_decreases_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +Item 27 +Item 28 +│Item 29 +Item 30 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased#01.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased#01.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased#01.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_backwards_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_backwards_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..a0ed052f256cca2d93c47364d1e719c112819d86 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_backwards_list.golden @@ -0,0 +1,10 @@ +Item 18 +Item 19 +Item 20 +Item 21 +Item 22 +Item 23 +Item 24 +Item 25 +Item 26 +│Item 27 \ No newline at end of file diff --git a/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_forward_list.golden b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_forward_list.golden new file mode 100644 index 0000000000000000000000000000000000000000..90740d28955595061e79772cf8011a7571712205 --- /dev/null +++ b/internal/tui/exp/list/testdata/TestListMovement/should_stay_at_the_position_it_is_when_the_hight_of_an_item_below_is_increased_in_forward_list.golden @@ -0,0 +1,10 @@ +│Item 2 +Item 3 +Item 4 +Item 5 +Item 6 +Item 7 +Item 8 +Item 9 +Item 10 +Item 11 \ No newline at end of file diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 9deac1e9e48c1cff576e84746d3976b4b670a700..0ec245be671e78803bb86cfbf28d2d0eb342bd67 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -604,6 +604,7 @@ func (p *chatPage) sendMessage(text string, attachments []message.Attachment) te if err != nil { return util.ReportError(err) } + cmds = append(cmds, p.chat.GoToBottom()) return tea.Batch(cmds...) }