// Package detail is the right-hand pane: tabs for Headers/Body/Timing
// plus a shared scroll viewport.
//
// mercemay.top/src/httptap/
package detail
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
"mercemay.top/httptap/internal/parser"
"mercemay.top/httptap/internal/tui/theme"
"mercemay.top/httptap/internal/tui/view/request"
"mercemay.top/httptap/internal/tui/view/response"
"mercemay.top/httptap/internal/tui/viewport"
)
// Model is the detail pane state.
type Model struct {
pal theme.Palette
tabs Tabs
active int
msg parser.Message
vp viewport.Model
width int
height int
}
// New returns an empty detail pane.
func New(pal theme.Palette) Model {
return Model{
pal: pal,
tabs: NewTabs(pal, []string{"Headers", "Body", "Timing"}),
vp: viewport.New(0, 0),
}
}
// SetSize responds to App.relayout.
func (m *Model) SetSize(w, h int) {
m.width, m.height = w, h
// Reserve a row for the tab strip.
m.vp.SetSize(w-2, h-2)
}
// Height is exposed for app tests.
func (m Model) Height() int { return m.height }
// SetMessage refreshes the message shown in the pane and re-renders the
// active tab's contents into the viewport.
func (m *Model) SetMessage(msg parser.Message) {
m.msg = msg
m.refresh()
}
// Update handles key input: tab switching and viewport scrolling.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if k, ok := msg.(tea.KeyMsg); ok {
switch k.String() {
case "tab", "l":
m.active = (m.active + 1) % len(m.tabs.Labels())
m.refresh()
return m, nil
case "shift+tab", "h":
m.active = (m.active - 1 + len(m.tabs.Labels())) % len(m.tabs.Labels())
m.refresh()
return m, nil
}
}
var cmd tea.Cmd
m.vp, cmd = m.vp.Update(msg)
return m, cmd
}
// refresh re-paints the viewport body for the active tab.
func (m *Model) refresh() {
switch m.active {
case 0:
m.vp.SetContent(request.RenderHeaders(m.pal, m.msg.Headers))
case 1:
m.vp.SetContent(request.RenderBody(m.pal, m.msg))
case 2:
m.vp.SetContent(response.RenderTiming(m.pal, m.msg))
}
}
// View paints the tab strip above the viewport.
func (m Model) View() string {
strip := m.tabs.Render(m.active, m.width)
body := m.pal.Detail.Width(m.width).Height(m.height - 2).Render(m.vp.View())
return strings.Join([]string{strip, body}, "\n")
}