diff options
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mitmux/main.go | 87 |
1 files changed, 78 insertions, 9 deletions
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go index 6d7fe8c..f47461c 100644 --- a/cmd/mitmux/main.go +++ b/cmd/mitmux/main.go @@ -13,6 +13,7 @@ import ( "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -94,6 +95,11 @@ type model struct { entries []store.Summary table table.Model + searchInput textinput.Model + searching bool // actively typing a query + query string // applied filter, "" means unfiltered + pendingNew int // live entries captured while a filter hides them + viewport viewport.Model detail *ipc.EntryDetail activeTab detailTab @@ -135,12 +141,17 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary) *model { ta.Placeholder = "raw request bytes" ta.ShowLineNumbers = false + si := textinput.New() + si.Prompt = "/" + si.Placeholder = "search - plain text, or host:example.com / AND / OR / NOT" + return &model{ - client: client, - subCh: subCh, - mode: viewList, - table: t, - reqArea: ta, + client: client, + subCh: subCh, + mode: viewList, + table: t, + reqArea: ta, + searchInput: si, } } @@ -166,7 +177,13 @@ type repeatSentMsg struct { } func (m *model) loadList() tea.Msg { - entries, err := m.client.List(500, 0) + var entries []store.Summary + var err error + if m.query != "" { + entries, err = m.client.Search(m.query, 500, 0) + } else { + entries, err = m.client.List(500, 0) + } return listLoadedMsg{entries: entries, err: err} } @@ -220,7 +237,8 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width, m.height = msg.Width, msg.Height m.ready = true m.table.SetWidth(msg.Width) - m.table.SetHeight(msg.Height - 4) + m.table.SetHeight(msg.Height - 5) + m.searchInput.Width = msg.Width - 2 m.viewport = viewport.New(msg.Width, msg.Height-5) reqHeight := (msg.Height - 6) / 2 @@ -243,6 +261,14 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.statusMsg = "live updates disconnected" return m, nil } + if m.query != "" { + // Whether it matches the active filter isn't knowable without + // re-querying; rather than guess, just surface a count and let + // the user refresh the search deliberately. + m.pendingNew++ + m.statusMsg = fmt.Sprintf("+%d new captured (re-enter search to refresh)", m.pendingNew) + return m, m.waitForEntry + } m.entries = append([]store.Summary{msg.entry}, m.entries...) m.table.SetRows(rowsFor(m.entries)) return m, m.waitForEntry @@ -277,6 +303,25 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch m.mode { case viewList: + if m.searching { + switch msg.String() { + case "enter": + m.searching = false + m.query = m.searchInput.Value() + m.searchInput.Blur() + m.pendingNew = 0 + m.statusMsg = "" + return m, m.loadList + case "esc": + m.searching = false + m.searchInput.Blur() + return m, nil + } + var cmd tea.Cmd + m.searchInput, cmd = m.searchInput.Update(msg) + return m, cmd + } + switch msg.String() { case "q", "ctrl+c": return m, tea.Quit @@ -291,6 +336,18 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.statusMsg = "" return m, m.loadDetail(m.entries[row].ID, true) } + case "/": + m.searching = true + m.searchInput.SetValue(m.query) + m.searchInput.CursorEnd() + return m, m.searchInput.Focus() + case "esc": + if m.query != "" { + m.query = "" + m.pendingNew = 0 + m.statusMsg = "" + return m, m.loadList + } } var cmd tea.Cmd m.table, cmd = m.table.Update(msg) @@ -385,15 +442,27 @@ var ( func (m *model) listView() string { var b strings.Builder - b.WriteString(titleStyle.Render(fmt.Sprintf(" mitmux - history (%d) ", len(m.entries)))) + title := fmt.Sprintf(" mitmux - history (%d) ", len(m.entries)) + if m.query != "" && !m.searching { + title = fmt.Sprintf(" mitmux - history (%d matches for %q) ", len(m.entries), m.query) + } + b.WriteString(titleStyle.Render(title)) b.WriteString("\n") + if m.searching { + b.WriteString(m.searchInput.View()) + b.WriteString("\n") + } b.WriteString(m.table.View()) b.WriteString("\n") if m.statusMsg != "" { b.WriteString(statusStyle.Render(m.statusMsg)) b.WriteString("\n") } - b.WriteString(helpStyle.Render("↑/↓ navigate · enter view · r repeater · q quit")) + help := "↑/↓ navigate · enter view · r repeater · / search · q quit" + if m.query != "" { + help = "↑/↓ navigate · enter view · r repeater · / search · esc clear filter · q quit" + } + b.WriteString(helpStyle.Render(help)) return b.String() } |