diff options
| author | Paul Bütow <1224732+snonux@users.noreply.github.com> | 2025-06-21 20:36:08 +0300 |
|---|---|---|
| committer | Paul Bütow <1224732+snonux@users.noreply.github.com> | 2025-06-21 20:36:08 +0300 |
| commit | 011f4e7880b1548abb86a3d2f78f80e167004ebd (patch) | |
| tree | 5d87d646137057740f50afc328d7cf53f27409fa | |
| parent | 273d493170abe8838252c61a40b558ef4edc3db8 (diff) | |
Add in-table editing shortcuts
| -rw-r--r-- | internal/task/task.go | 44 | ||||
| -rw-r--r-- | internal/ui/table.go | 126 |
2 files changed, 168 insertions, 2 deletions
diff --git a/internal/task/task.go b/internal/task/task.go index 64dfc0d..603002d 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -167,6 +167,50 @@ func RemoveTags(id int, tags []string) error { return run(args...) } +// SetTags sets the tags of the task with the given id to exactly the provided set. +// Tags not present will be removed and new tags added as needed. +func SetTags(id int, tags []string) error { + tasks, err := Export(strconv.Itoa(id)) + if err != nil { + return err + } + if len(tasks) == 0 { + return fmt.Errorf("task %d not found", id) + } + current := make(map[string]struct{}) + for _, t := range tasks[0].Tags { + current[t] = struct{}{} + } + desired := make(map[string]struct{}) + for _, t := range tags { + desired[t] = struct{}{} + } + + var adds, removes []string + for t := range desired { + if _, ok := current[t]; !ok { + adds = append(adds, t) + } + } + for t := range current { + if _, ok := desired[t]; !ok { + removes = append(removes, t) + } + } + + if len(adds) > 0 { + if err := AddTags(id, adds); err != nil { + return err + } + } + if len(removes) > 0 { + if err := RemoveTags(id, removes); err != nil { + return err + } + } + return nil +} + // SetRecurrence sets the recurrence for the task with the given id. func SetRecurrence(id int, rec string) error { return run(strconv.Itoa(id), "modify", "recur:"+rec) diff --git a/internal/ui/table.go b/internal/ui/table.go index 4a92876..4f7c2d6 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -42,6 +42,14 @@ type Model struct { annotateInput textinput.Model replaceAnnotations bool + descEditing bool + descID int + descInput textinput.Model + + tagsEditing bool + tagsID int + tagsInput textinput.Model + dueEditing bool dueID int dueDate time.Time @@ -94,6 +102,10 @@ func New(filters []string) (Model, error) { m := Model{filters: filters} m.annotateInput = textinput.New() m.annotateInput.Prompt = "annotation: " + m.descInput = textinput.New() + m.descInput.Prompt = "description: " + m.tagsInput = textinput.New() + m.tagsInput.Prompt = "tags: " m.dueDate = time.Now() m.searchInput = textinput.New() m.searchInput.Prompt = "search: " @@ -225,6 +237,45 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.annotateInput, cmd = m.annotateInput.Update(msg) return m, cmd } + if m.descEditing { + switch msg.Type { + case tea.KeyEnter: + task.SetDescription(m.descID, m.descInput.Value()) + m.descEditing = false + m.descInput.Blur() + m.reload() + m.updateTableHeight() + return m, nil + case tea.KeyEsc: + m.descEditing = false + m.descInput.Blur() + m.updateTableHeight() + return m, nil + } + var cmd tea.Cmd + m.descInput, cmd = m.descInput.Update(msg) + return m, cmd + } + if m.tagsEditing { + switch msg.Type { + case tea.KeyEnter: + tags := strings.Fields(m.tagsInput.Value()) + task.SetTags(m.tagsID, tags) + m.tagsEditing = false + m.tagsInput.Blur() + m.reload() + m.updateTableHeight() + return m, nil + case tea.KeyEsc: + m.tagsEditing = false + m.tagsInput.Blur() + m.updateTableHeight() + return m, nil + } + var cmd tea.Cmd + m.tagsInput, cmd = m.tagsInput.Update(msg) + return m, cmd + } if m.dueEditing { switch msg.Type { case tea.KeyEnter: @@ -461,7 +512,66 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateSelectionHighlight(prevRow, m.tbl.Cursor(), prevCol, m.tbl.ColumnCursor()) return m, nil } - case "enter": + case "enter", "i": + if row := m.tbl.SelectedRow(); row != nil { + idStr := ansi.Strip(row[0]) + if id, err := strconv.Atoi(idStr); err == nil { + col := m.tbl.ColumnCursor() + switch col { + case 1: + m.priorityID = id + m.prioritySelecting = true + switch m.tasks[m.tbl.Cursor()].Priority { + case "H": + m.priorityIndex = 0 + case "M": + m.priorityIndex = 1 + case "L": + m.priorityIndex = 2 + default: + m.priorityIndex = 3 + } + m.updateTableHeight() + return m, nil + case 4: + m.dueID = id + if ts, err := time.Parse("20060102T150405Z", m.tasks[m.tbl.Cursor()].Due); err == nil { + m.dueDate = ts + } else { + m.dueDate = time.Now() + } + m.dueEditing = true + m.updateTableHeight() + return m, nil + case 5: + m.tagsID = id + m.tagsEditing = true + m.tagsInput.SetValue(strings.Join(m.tasks[m.tbl.Cursor()].Tags, " ")) + m.tagsInput.Focus() + m.updateTableHeight() + return m, nil + case 6: + m.annotateID = id + m.annotating = true + m.replaceAnnotations = true + var anns []string + for _, a := range m.tasks[m.tbl.Cursor()].Annotations { + anns = append(anns, a.Description) + } + m.annotateInput.SetValue(strings.Join(anns, "; ")) + m.annotateInput.Focus() + m.updateTableHeight() + return m, nil + case 7: + m.descID = id + m.descEditing = true + m.descInput.SetValue(m.tasks[m.tbl.Cursor()].Description) + m.descInput.Focus() + m.updateTableHeight() + return m, nil + } + } + } m.cellExpanded = !m.cellExpanded m.updateTableHeight() return m, nil @@ -532,6 +642,18 @@ func (m Model) View() string { m.priorityView(), ) } + if m.descEditing { + view = lipgloss.JoinVertical(lipgloss.Left, + view, + m.descInput.View(), + ) + } + if m.tagsEditing { + view = lipgloss.JoinVertical(lipgloss.Left, + view, + m.tagsInput.View(), + ) + } if m.searching { view = lipgloss.JoinVertical(lipgloss.Left, view, @@ -817,7 +939,7 @@ func (m *Model) updateTableHeight() { if m.cellExpanded { h-- } - if m.annotating || m.dueEditing || m.prioritySelecting || m.searching { + if m.annotating || m.dueEditing || m.prioritySelecting || m.searching || m.descEditing || m.tagsEditing { h-- } if h < 1 { |
