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 | | `↑↓` / `Space` / `PgUp` / `PgDn` | Scroll |
| `H` | Toggle full headers / short headers | | `H` | Toggle full headers / short headers |
| `s` | Save raw EML to `~/QUEUEID.eml` | | `s` | Save raw EML to `~/QUEUEID.eml` |
| `h` | Toggle hold (`postsuper -h`/`-H`) |
| `D` | Delete message (`postsuper -d`) | | `D` | Delete message (`postsuper -d`) |
| `F` | Requeue message (`postsuper -r`) | | `F` | Requeue message (`postsuper -r`) |
| `v` | Browse MIME parts | | `v` | Browse MIME parts |

View file

@ -262,6 +262,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.refreshViewport() m.refreshViewport()
m.viewport.GotoTop() m.viewport.GotoTop()
return m, nil 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": case "D":
id := m.currentID id := m.currentID
m.state = stateList m.state = stateList
@ -398,8 +407,12 @@ func (m Model) View() string {
if m.showFullHeaders { if m.showFullHeaders {
headersHint = "H: short headers" 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( 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 := "" notice := ""
if m.messageSaving { 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 { func deleteMessageCmd(id string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
exec.Command("postsuper", "-d", id).Run() exec.Command("postsuper", "-d", id).Run()

View file

@ -11,6 +11,7 @@ import (
type QueueEntry struct { type QueueEntry struct {
ID string ID string
OnHold bool
Size int Size int
Date time.Time Date time.Time
Sender string Sender string
@ -114,9 +115,10 @@ func parseQueueLine(line string) *QueueEntry {
return nil return nil
} }
id := fields[0] raw := fields[0]
onHold := strings.HasSuffix(raw, "!")
// Strip trailing status character (* or !) // Strip trailing status character (* or !)
id = strings.TrimRight(id, "*!") id := strings.TrimRight(raw, "*!")
var size int var size int
fmt.Sscanf(fields[1], "%d", &size) fmt.Sscanf(fields[1], "%d", &size)
@ -137,6 +139,7 @@ func parseQueueLine(line string) *QueueEntry {
return &QueueEntry{ return &QueueEntry{
ID: id, ID: id,
OnHold: onHold,
Size: size, Size: size,
Date: t, Date: t,
Sender: sender, Sender: sender,