diff --git a/internal/tui/components/chat/list.go b/internal/tui/components/chat/list.go index 762fdd247af9bac5c55e562e56bd11eb307357db..e039e259f3223a5c3f6ef61cab4d7c77a228c062 100644 --- a/internal/tui/components/chat/list.go +++ b/internal/tui/components/chat/list.go @@ -46,13 +46,19 @@ type messageListCmp struct { // and reverse ordering (newest messages at bottom). func NewMessagesListCmp(app *app.App) MessageListCmp { defaultKeymaps := list.DefaultKeymap() - defaultKeymaps.NDown.SetEnabled(false) - defaultKeymaps.NUp.SetEnabled(false) + defaultKeymaps.Up.SetEnabled(false) + defaultKeymaps.Down.SetEnabled(false) + defaultKeymaps.NDown = key.NewBinding( + key.WithKeys("ctrl+j"), + ) + defaultKeymaps.NUp = key.NewBinding( + key.WithKeys("ctrl+k"), + ) defaultKeymaps.Home = key.NewBinding( - key.WithKeys("ctrl+g"), + key.WithKeys("ctrl+shift+up"), ) defaultKeymaps.End = key.NewBinding( - key.WithKeys("ctrl+G"), + key.WithKeys("ctrl+shift+down"), ) return &messageListCmp{ app: app, diff --git a/internal/tui/components/chat/messages/renderer.go b/internal/tui/components/chat/messages/renderer.go index 836d9fb90cebc130f78dfe54a82da2c8be4e439f..e559ff11f1c2345f61733ae21b4f1c1a2035f65f 100644 --- a/internal/tui/components/chat/messages/renderer.go +++ b/internal/tui/components/chat/messages/renderer.go @@ -7,6 +7,7 @@ import ( "time" "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/lipgloss/v2/tree" "github.com/charmbracelet/x/ansi" "github.com/opencode-ai/opencode/internal/config" "github.com/opencode-ai/opencode/internal/diff" @@ -93,7 +94,7 @@ func (pb *paramBuilder) build() []string { func (br baseRenderer) renderWithParams(v *toolCallCmp, toolName string, args []string, contentRenderer func() string) string { width := v.textWidth() if v.isNested { - width -= 3 // Adjust for nested tool call indentation + width -= 4 // Adjust for nested tool call indentation } header := makeHeader(toolName, width, args...) if v.isNested { @@ -495,11 +496,15 @@ func (tr agentRenderer) Render(v *toolCallCmp) string { args := newParamBuilder().addMain(prompt).build() header := makeHeader("Task", v.textWidth(), args...) - parts := []string{header} + t := tree.Root(header) + for _, call := range v.nestedToolCalls { - parts = append(parts, call.View()) + t.Child(call.View()) } + parts := []string{ + t.Enumerator(tree.RoundedEnumerator).String(), + } if v.result.ToolCallID == "" { v.spinning = true parts = append(parts, v.anim.View()) diff --git a/internal/tui/components/chat/messages/tool.go b/internal/tui/components/chat/messages/tool.go index 15c1d6d143c8c7d0622413872c0f72b9949d6210..333f85864c3a5ae48e7cf8ff8303dbb587c43d39 100644 --- a/internal/tui/components/chat/messages/tool.go +++ b/internal/tui/components/chat/messages/tool.go @@ -138,18 +138,16 @@ func (m *toolCallCmp) View() string { box := m.style() if !m.call.Finished && !m.cancelled { + if m.isNested { + return box.Render(m.renderPending()) + } return box.PaddingLeft(1).Render(m.renderPending()) } r := registry.lookup(m.call.Name) if m.isNested { - return box.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - " └ ", - r.Render(m), - ), - ) + return box.Render(r.Render(m)) } return box.PaddingLeft(1).Render(r.Render(m)) } @@ -212,9 +210,6 @@ func (m *toolCallCmp) SetIsNested(isNested bool) { // renderPending displays the tool name with a loading animation for pending tool calls func (m *toolCallCmp) renderPending() string { - if m.isNested { - return fmt.Sprintf("└ %s: %s", prettifyToolName(m.call.Name), m.anim.View()) - } return fmt.Sprintf("%s: %s", prettifyToolName(m.call.Name), m.anim.View()) } diff --git a/internal/tui/components/core/list/keys.go b/internal/tui/components/core/list/keys.go index db1eeafc973f85218b36750c79d337bf9ed41e02..4e534fed54d8112649bd785f112b29ce796ec394 100644 --- a/internal/tui/components/core/list/keys.go +++ b/internal/tui/components/core/list/keys.go @@ -1,6 +1,9 @@ package list -import "github.com/charmbracelet/bubbles/v2/key" +import ( + "github.com/charmbracelet/bubbles/v2/key" + "github.com/opencode-ai/opencode/internal/tui/layout" +) type KeyMap struct { Down, @@ -12,8 +15,7 @@ type KeyMap struct { HalfPageDown, HalfPageUp, Home, - End, - Submit key.Binding + End key.Binding } func DefaultKeymap() KeyMap { @@ -48,23 +50,24 @@ func DefaultKeymap() KeyMap { End: key.NewBinding( key.WithKeys("shift+g", "end"), ), - Submit: key.NewBinding( - key.WithKeys("enter", "space"), - key.WithHelp("enter/space", "select"), - ), } } // FullHelp implements help.KeyMap. -func (k KeyMap) FullHelp() [][]key.Binding { return nil } +func (k KeyMap) FullHelp() [][]key.Binding { + m := [][]key.Binding{} + slice := layout.KeyMapToSlice(k) + for i := 0; i < len(slice); i += 4 { + end := min(i+4, len(slice)) + m = append(m, slice[i:end]) + } + return m +} // ShortHelp implements help.KeyMap. func (k KeyMap) ShortHelp() []key.Binding { return []key.Binding{ - key.NewBinding( - key.WithKeys("up", "down"), - key.WithHelp("↓↑", "navigate"), - ), - k.Submit, + k.Up, + k.Down, } }