cleanup nested tool rendering

Kujtim Hoxha created

Change summary

internal/tui/components/chat/list.go              | 14 +++++--
internal/tui/components/chat/messages/renderer.go | 11 ++++-
internal/tui/components/chat/messages/tool.go     | 13 ++-----
internal/tui/components/core/list/keys.go         | 29 +++++++++-------
4 files changed, 38 insertions(+), 29 deletions(-)

Detailed changes

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,

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())

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())
 }
 

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