From 3916c49a0cff0b38f7aaf864741fdcbb272ebe05 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 29 Mar 2026 20:00:51 +0700 Subject: [PATCH] Add h keybinding to toggle hold/release via postsuper -h/-H Preserves the ! suffix from postqueue -p output to track hold status in QueueEntry, and optimistically toggles it in the UI. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 1 + model.go | 15 ++++++++++++++- msgs.go | 14 ++++++++++++++ queue.go | 7 +++++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e46f27..7a6b490 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Subjects are fetched lazily in parallel via `postcat` as the list loads; a progr | `↑↓` / `Space` / `PgUp` / `PgDn` | Scroll | | `H` | Toggle full headers / short headers | | `s` | Save raw EML to `~/QUEUEID.eml` | +| `h` | Toggle hold (`postsuper -h`/`-H`) | | `D` | Delete message (`postsuper -d`) | | `F` | Requeue message (`postsuper -r`) | | `v` | Browse MIME parts | diff --git a/model.go b/model.go index 65d5242..ff36455 100644 --- a/model.go +++ b/model.go @@ -262,6 +262,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.refreshViewport() m.viewport.GotoTop() return m, nil + case "h": + if idx, ok := m.entryIndex[m.currentID]; ok { + if m.entries[idx].OnHold { + m.entries[idx].OnHold = false + return m, releaseMessageCmd(m.currentID) + } + m.entries[idx].OnHold = true + return m, holdMessageCmd(m.currentID) + } case "D": id := m.currentID m.state = stateList @@ -398,8 +407,12 @@ func (m Model) View() string { if m.showFullHeaders { headersHint = "H: short headers" } + holdHint := "h: hold" + if idx, ok := m.entryIndex[m.currentID]; ok && m.entries[idx].OnHold { + holdHint = "h: release" + } status := statusBarStyle.Render( - fmt.Sprintf(" ↑↓/SPC/PgUp/Dn: scroll │ s: save EML │ D: delete │ F: requeue │ v: parts │ %s │ q: back │ %d%% ", headersHint, scrollPct), + fmt.Sprintf(" ↑↓/SPC/PgUp/Dn: scroll │ s: save EML │ D: delete │ F: requeue │ %s │ v: parts │ %s │ q: back │ %d%% ", holdHint, headersHint, scrollPct), ) notice := "" if m.messageSaving { diff --git a/msgs.go b/msgs.go index 1780287..9d5114b 100644 --- a/msgs.go +++ b/msgs.go @@ -47,6 +47,20 @@ func flushQueueCmd() tea.Cmd { } } +func holdMessageCmd(id string) tea.Cmd { + return func() tea.Msg { + exec.Command("postsuper", "-h", id).Run() + return nil + } +} + +func releaseMessageCmd(id string) tea.Cmd { + return func() tea.Msg { + exec.Command("postsuper", "-H", id).Run() + return nil + } +} + func deleteMessageCmd(id string) tea.Cmd { return func() tea.Msg { exec.Command("postsuper", "-d", id).Run() diff --git a/queue.go b/queue.go index ed14d3d..1c67787 100644 --- a/queue.go +++ b/queue.go @@ -11,6 +11,7 @@ import ( type QueueEntry struct { ID string + OnHold bool Size int Date time.Time Sender string @@ -114,9 +115,10 @@ func parseQueueLine(line string) *QueueEntry { return nil } - id := fields[0] + raw := fields[0] + onHold := strings.HasSuffix(raw, "!") // Strip trailing status character (* or !) - id = strings.TrimRight(id, "*!") + id := strings.TrimRight(raw, "*!") var size int fmt.Sscanf(fields[1], "%d", &size) @@ -137,6 +139,7 @@ func parseQueueLine(line string) *QueueEntry { return &QueueEntry{ ID: id, + OnHold: onHold, Size: size, Date: t, Sender: sender,