diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-08 15:41:33 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-08 16:14:11 +0300 |
| commit | e2394dab62d54a3ae40044c06a7fec0b953a78e0 (patch) | |
| tree | 3125e82250aeb388ff28f377c4e1358aa12cce4f /internal/ui | |
| parent | 02f45bd1707639a081d68f90ecc00c429de6d962 (diff) | |
m0: fix ultra open-url hotkey
Diffstat (limited to 'internal/ui')
| -rw-r--r-- | internal/ui/handlers.go | 18 | ||||
| -rw-r--r-- | internal/ui/keyhandlers.go | 2 | ||||
| -rw-r--r-- | internal/ui/table_test.go | 73 | ||||
| -rw-r--r-- | internal/ui/ultra.go | 3 |
4 files changed, 92 insertions, 4 deletions
diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index 8c417d3..5428ebd 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -582,6 +582,24 @@ func (m *Model) getTaskAtCursor() *task.Task { return &m.tasks[cursor] } +// getTaskForOpenURL returns the task that should be used by the open-URL +// hotkey, honoring the active view's highlighted task. +func (m *Model) getTaskForOpenURL() *task.Task { + if m.showTaskDetail && m.currentTaskDetail != nil { + return m.currentTaskDetail + } + + if m.showUltra { + tasks := m.ultraTaskList() + if m.ultraCursor < 0 || m.ultraCursor >= len(tasks) { + return nil + } + return &tasks[m.ultraCursor] + } + + return m.getTaskAtCursor() +} + // handleTaskDetailMode handles keyboard input in task detail view func (m *Model) handleTaskDetailMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { if m.detailSearching { diff --git a/internal/ui/keyhandlers.go b/internal/ui/keyhandlers.go index 890140d..33c7dc0 100644 --- a/internal/ui/keyhandlers.go +++ b/internal/ui/keyhandlers.go @@ -307,7 +307,7 @@ func (m *Model) handleMarkDone() (tea.Model, tea.Cmd) { } func (m *Model) handleOpenURL() (tea.Model, tea.Cmd) { - task := m.getTaskAtCursor() + task := m.getTaskForOpenURL() if task == nil { return m, nil } diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 6adaad1..3d3601e 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -877,9 +877,8 @@ func TestUltraHelpUsesUltraBindingsAndClosesBeforeLeavingUltra(t *testing.T) { if !strings.Contains(view, "exit ultra mode") { t.Fatalf("ultra help content missing ultra exit binding: %q", view) } - // "open URL from description" is now available in ultra mode (o key). if !strings.Contains(view, "open URL from description") { - t.Fatalf("ultra help missing o/open-URL binding: %q", view) + t.Fatalf("ultra help content missing open-url binding: %q", view) } if strings.Contains(view, "edit current field") { t.Fatalf("ultra help rendered normal-only inline edit binding: %q", view) @@ -927,7 +926,7 @@ func TestUltraHelpSearchUsesUltraHelpLines(t *testing.T) { step(tea.KeyPressMsg{Code: 'u', Text: "u"}) step(tea.KeyPressMsg{Code: 'H', Text: "H"}) step(tea.KeyPressMsg{Code: '/', Text: "/"}) - for _, r := range "URL" { + for _, r := range "view task details" { step(tea.KeyPressMsg{Code: r, Text: string(r)}) } step(tea.KeyPressMsg{Code: tea.KeyEnter}) @@ -1527,6 +1526,74 @@ func TestUltraPriorityOpUsesUltraSelection(t *testing.T) { } } +func TestUltraOpenURLHotkeyUsesUltraSelection(t *testing.T) { + tmp := t.TempDir() + taskPath := filepath.Join(tmp, "task") + openLog := filepath.Join(tmp, "open.log") + browserPath := filepath.Join(tmp, "browser") + + taskScript := "#!/bin/sh\n" + + "if echo \"$@\" | grep -q export; then\n" + + " echo '{\"id\":1,\"uuid\":\"1\",\"description\":\"alpha\",\"status\":\"pending\",\"entry\":\"\",\"priority\":\"\",\"urgency\":0}'\n" + + " echo '{\"id\":2,\"uuid\":\"2\",\"description\":\"beta https://example.com\",\"status\":\"pending\",\"entry\":\"\",\"priority\":\"\",\"urgency\":0}'\n" + + " exit 0\n" + + "fi\n" + if err := os.WriteFile(taskPath, []byte(taskScript), 0o755); err != nil { + t.Fatal(err) + } + + browserScript := "#!/bin/sh\n" + + "echo \"$1\" >> " + openLog + "\n" + if err := os.WriteFile(browserPath, []byte(browserScript), 0o755); err != nil { + t.Fatal(err) + } + + setupEnv(t, taskPath) + + m, err := New(nil, browserPath) + if err != nil { + t.Fatalf("New: %v", err) + } + + m.showUltra = true + m.tbl.SetCursor(0) + m.ultraCursor = 1 + + mv, cmd := (&m).Update(tea.KeyPressMsg{Code: 'o', Text: "o"}) + if cmd == nil { + // Opening a URL starts the blink animation, so a command is expected. + t.Fatalf("ultra open URL unexpectedly returned no command") + } + m = *mv.(*Model) + + data, err := os.ReadFile(openLog) + if err != nil { + t.Fatalf("read open log: %v", err) + } + if strings.TrimSpace(string(data)) != "https://example.com" { + t.Fatalf("browser not called with ultra-selected url: %q", data) + } + + // Clear the blink state so we can test the no-URL path with a second keypress. + m.blinkID = 0 + m.blinkOn = false + m.ultraCursor = 0 + + mv, cmd = (&m).Update(tea.KeyPressMsg{Code: 'o', Text: "o"}) + if cmd != nil { + t.Fatalf("ultra open URL for task without url unexpectedly returned a command") + } + m = *mv.(*Model) + + data, err = os.ReadFile(openLog) + if err != nil { + t.Fatalf("read open log after no-url task: %v", err) + } + if strings.TrimSpace(string(data)) != "https://example.com" { + t.Fatalf("browser was called for task without url: %q", data) + } +} + func TestUltraReloadPreservesFilteredSelection(t *testing.T) { tmp := t.TempDir() taskPath, phaseFile := setupUltraReloadTaskSet(t, tmp) diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go index 5c7b6d1..4bb9542 100644 --- a/internal/ui/ultra.go +++ b/internal/ui/ultra.go @@ -91,6 +91,7 @@ func (m Model) ultraHelpSections() []helpSection { title: "Task Management", items: []helpItem{ {key: "Enter, e, E", desc: "edit selected task"}, + {key: "o", desc: "open URL from description"}, {key: "s", desc: "start/stop task"}, {key: "d", desc: "mark task done"}, {key: "U", desc: "undo last done"}, @@ -1059,6 +1060,8 @@ func (m *Model) handleUltraMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { return m.handleUltraToggleStart() case "d": return m.handleUltraMarkDone() + case "o": + return m.handleOpenURL() case "p": return m.handleUltraSetPriority() case "w": |
