internal/tui/view/filterbar/filterbar.go

// Package filterbar is the single-line input shown above the request
// list. It delegates the actual expression parsing to internal/filter.
//
// mercemay.top/src/httptap/
package filterbar

import (
	"strings"

	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"

	"mercemay.top/httptap/internal/tui/theme"
)

// Model is a thin wrapper around bubbles/textinput.
type Model struct {
	pal   theme.Palette
	input textinput.Model
	width int
}

// New returns a model with sensible defaults.
func New(pal theme.Palette) Model {
	ti := textinput.New()
	ti.Prompt = "/ "
	ti.Placeholder = "method=GET and host=example.com"
	ti.CharLimit = 512
	return Model{pal: pal, input: ti}
}

// SetWidth is called by App.relayout.
func (m *Model) SetWidth(w int) {
	m.width = w
	m.input.Width = w - 2
}

// Update handles editing keys. The bubbletea textinput does the heavy
// lifting; we intercept Enter to blur and Esc to clear.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
	if k, ok := msg.(tea.KeyMsg); ok {
		switch k.String() {
		case "enter":
			m.input.Blur()
			return m, nil
		case "esc":
			m.input.Blur()
			m.input.SetValue("")
			return m, nil
		}
	}
	var cmd tea.Cmd
	m.input, cmd = m.input.Update(msg)
	return m, cmd
}

// Focus marks the filter bar active.
func (m *Model) Focus() tea.Cmd { return m.input.Focus() }

// Blur marks the filter bar inactive.
func (m *Model) Blur() { m.input.Blur() }

// Expr returns the currently entered filter expression.
func (m Model) Expr() string { return strings.TrimSpace(m.input.Value()) }

// View paints the bar.
func (m Model) View() string {
	v := m.input.View()
	if m.width <= 0 {
		return m.pal.FilterBar.Render(v)
	}
	if lw := len(v); lw < m.width {
		v += strings.Repeat(" ", m.width-lw)
	}
	return m.pal.FilterBar.Render(v)
}

// Focused reports whether the input has focus (i.e. user typing).
func (m Model) Focused() bool { return m.input.Focused() }