diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 17:32:24 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 17:32:24 +0200 |
| commit | 1561987330cb898f5ff64383a9c78e7e6559f118 (patch) | |
| tree | 69a823e8f98dce572566c97e6879c11c9d591bda /internal/tui/pidpicker | |
| parent | 96225fb6159212a8851043a08d781aba721b4e78 (diff) | |
| parent | 110a193e04b81abb8d8e159abd73f9f6ed1acd7e (diff) | |
Merge branch 'feat/bubbletea-v2-migration'
Diffstat (limited to 'internal/tui/pidpicker')
| -rw-r--r-- | internal/tui/pidpicker/doc.go | 2 | ||||
| -rw-r--r-- | internal/tui/pidpicker/model.go | 59 | ||||
| -rw-r--r-- | internal/tui/pidpicker/model_test.go | 29 |
3 files changed, 65 insertions, 25 deletions
diff --git a/internal/tui/pidpicker/doc.go b/internal/tui/pidpicker/doc.go new file mode 100644 index 0000000..5f863c0 --- /dev/null +++ b/internal/tui/pidpicker/doc.go @@ -0,0 +1,2 @@ +// Package pidpicker implements PID and TID selection workflows before tracing starts. +package pidpicker diff --git a/internal/tui/pidpicker/model.go b/internal/tui/pidpicker/model.go index 73f21ae..f4bb414 100644 --- a/internal/tui/pidpicker/model.go +++ b/internal/tui/pidpicker/model.go @@ -2,14 +2,15 @@ package pidpicker import ( "fmt" + "strings" + common "ior/internal/tui/common" "ior/internal/tui/messages" - "strings" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) const allPIDsLabel = "All PIDs" @@ -50,6 +51,14 @@ var ( errorStyle = common.ErrorStyle ) +func syncPickerStyles() { + screenStyle = common.ScreenStyle + headerStyle = common.HeaderStyle + helpBarStyle = common.HelpBarStyle + highlightStyle = common.HighlightStyle + errorStyle = common.ErrorStyle +} + type processesLoadedMsg struct { processes []ProcessInfo err error @@ -67,6 +76,7 @@ type Model struct { height int keys KeyMap lastErr error + isDark bool } // New creates a PID picker model with default shared key bindings. @@ -81,12 +91,14 @@ func NewWithKeys(keys KeyMap) Model { // NewPIDWithKeys creates a PID picker model with the provided key bindings. func NewPIDWithKeys(keys KeyMap) Model { + syncPickerStyles() input := textinput.New() input.Prompt = "Filter: " input.Placeholder = "pid, comm, or cmdline" input.Focus() input.CharLimit = 0 - input.Width = 40 + input.SetWidth(40) + input.SetStyles(textinput.DefaultStyles(true)) return Model{ input: input, @@ -94,6 +106,7 @@ func NewPIDWithKeys(keys KeyMap) Model { filtered: []ProcessInfo{}, mode: PickerModePID, targetPID: -1, + isDark: true, } } @@ -117,14 +130,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height - m.input.Width = clamp(msg.Width-16, 10, 100) + inputWidth := msg.Width - 16 + if inputWidth < 10 { + inputWidth = 10 + } + m.input.SetWidth(inputWidth) return m, nil case processesLoadedMsg: m.processes = msg.processes m.lastErr = msg.err m.applyFilter() return m, nil - case tea.KeyMsg: + case tea.KeyPressMsg: return m.updateKey(msg) } @@ -134,21 +151,21 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { +func (m Model) updateKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, m.keys.Esc): return m, tea.Quit - case msg.Type == tea.KeyCtrlR: + case msg.Key().Mod&tea.ModCtrl != 0 && (msg.Key().Code == 'r' || msg.Key().Code == 'R'): return m, m.scanCmd() case key.Matches(msg, m.keys.Enter): return m, m.emitSelection() - case msg.Type == tea.KeyUp: + case msg.Key().Code == tea.KeyUp: if m.selectedIndex > 0 { m.selectedIndex-- } m.input.Blur() return m, nil - case msg.Type == tea.KeyDown: + case msg.Key().Code == tea.KeyDown: maxIndex := len(m.filtered) if m.selectedIndex < maxIndex { m.selectedIndex++ @@ -157,7 +174,7 @@ func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } - if msg.Type == tea.KeyRunes && !m.input.Focused() { + if msg.Key().Text != "" && !m.input.Focused() { if key.Matches(msg, m.keys.Refresh) { return m, m.scanCmd() } @@ -240,7 +257,7 @@ func cloneProcesses(in []ProcessInfo) []ProcessInfo { } // View renders the PID picker with filter input, list, and help bar. -func (m Model) View() string { +func (m Model) View() tea.View { var b strings.Builder if m.mode == PickerModeTID { if m.targetPID > 0 { @@ -264,8 +281,18 @@ func (m Model) View() string { } b.WriteString("\n") - b.WriteString(helpBarStyle.Render(renderHelp(m.keys.PickerShortHelp()))) - return screenStyle.Render(b.String()) + viewWidth, _ := common.EffectiveViewport(m.width, m.height) + helpStyle := helpBarStyle.Copy().Width(viewWidth) + b.WriteString(helpStyle.Render(renderHelp(m.keys.PickerShortHelp()))) + return tea.NewView(screenStyle.Render(b.String())) +} + +// SetDarkMode updates picker theme and text input styles. +func (m Model) SetDarkMode(isDark bool) Model { + m.isDark = isDark + syncPickerStyles() + m.input.SetStyles(textinput.DefaultStyles(isDark)) + return m } func (m Model) renderRows() string { diff --git a/internal/tui/pidpicker/model_test.go b/internal/tui/pidpicker/model_test.go index 2d76508..695e5bd 100644 --- a/internal/tui/pidpicker/model_test.go +++ b/internal/tui/pidpicker/model_test.go @@ -1,11 +1,12 @@ package pidpicker import ( - "ior/internal/tui/messages" "strings" "testing" - tea "github.com/charmbracelet/bubbletea" + "ior/internal/tui/messages" + + tea "charm.land/bubbletea/v2" ) func TestApplyFilterByPIDCommAndCmdline(t *testing.T) { @@ -39,7 +40,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) { m.processes = []ProcessInfo{{Pid: 7, Comm: "vim"}, {Pid: 9, Comm: "top"}} m.applyFilter() - modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelAny msgAny := cmdAny() pidAny, ok := msgAny.(messages.PidSelectedMsg) @@ -51,7 +52,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) { } m.selectedIndex = 2 - modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelOne msgOne := cmdOne() pidOne, ok := msgOne.(messages.PidSelectedMsg) @@ -71,7 +72,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { } m.applyFilter() - modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelAny msgAny := cmdAny() tidAny, ok := msgAny.(messages.TidSelectedMsg) @@ -86,7 +87,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { } m.selectedIndex = 2 - modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelOne msgOne := cmdOne() tidOne, ok := msgOne.(messages.TidSelectedMsg) @@ -104,7 +105,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { m := NewWithKeys(DefaultKeyMap()) - _, escCmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + _, escCmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) if escCmd == nil { t.Fatalf("expected esc to return quit cmd") } @@ -112,7 +113,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { t.Fatalf("expected quit msg from esc, got %T", msg) } - _, refreshCmd := m.Update(tea.KeyMsg{Type: tea.KeyCtrlR}) + _, refreshCmd := m.Update(tea.KeyPressMsg{Code: rune('r'), Text: "r", Mod: tea.ModCtrl}) if refreshCmd == nil { t.Fatalf("expected refresh cmd") } @@ -124,7 +125,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { func TestRuneRDoesNotTriggerRefreshWhileFilterFocused(t *testing.T) { m := NewWithKeys(DefaultKeyMap()) - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'r'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'r'}[0], Text: string([]rune{'r'})}) if cmd == nil { t.Fatalf("expected textinput update cmd") } @@ -152,3 +153,13 @@ func TestRenderRowsKeepsSelectionVisible(t *testing.T) { t.Fatalf("expected selected row to remain visible, got:\n%s", rows) } } + +func TestWindowSizeDoesNotCapInputWidthOnWideTerminals(t *testing.T) { + m := NewWithKeys(DefaultKeyMap()) + next, _ := m.Update(tea.WindowSizeMsg{Width: 160, Height: 40}) + updated := next.(Model) + + if got, want := updated.input.Width(), 144; got != want { + t.Fatalf("expected input width %d for 160-col terminal, got %d", want, got) + } +} |
