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 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-03-29 20:00:51 +07:00
commit 3916c49a0c
4 changed files with 34 additions and 3 deletions

View file

@ -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 |

View file

@ -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 {

14
msgs.go
View file

@ -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()

View file

@ -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,