summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-21 20:36:08 +0300
committerPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-21 20:36:08 +0300
commit011f4e7880b1548abb86a3d2f78f80e167004ebd (patch)
tree5d87d646137057740f50afc328d7cf53f27409fa
parent273d493170abe8838252c61a40b558ef4edc3db8 (diff)
Add in-table editing shortcuts
-rw-r--r--internal/task/task.go44
-rw-r--r--internal/ui/table.go126
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 {