diff --git a/common/common.go b/common/common.go index d5f8c4c..92544f5 100644 --- a/common/common.go +++ b/common/common.go @@ -4,24 +4,47 @@ import ( "context" "tasksquire/taskwarrior" - - tea "github.com/charmbracelet/bubbletea" ) type Common struct { - Ctx context.Context - PageStack *Stack[tea.Model] - TW taskwarrior.TaskWarrior - Keymap *Keymap - Styles *Styles + Ctx context.Context + TW taskwarrior.TaskWarrior + Keymap *Keymap + Styles *Styles + + pageStack *Stack[Component] + width int + height int } func NewCommon(ctx context.Context, tw taskwarrior.TaskWarrior) *Common { return &Common{ - Ctx: ctx, - PageStack: NewStack[tea.Model](), - TW: tw, - Keymap: NewKeymap(), - Styles: NewStyles(tw.GetConfig()), + Ctx: ctx, + TW: tw, + Keymap: NewKeymap(), + Styles: NewStyles(tw.GetConfig()), + + pageStack: NewStack[Component](), } } + +func (c *Common) SetSize(width, height int) { + c.width = width + c.height = height +} + +func (c *Common) Width() int { + return c.width +} + +func (c *Common) Height() int { + return c.height +} + +func (c *Common) PushPage(page Component) { + c.pageStack.Push(page) +} + +func (c *Common) PopPage() (Component, error) { + return c.pageStack.Pop() +} diff --git a/common/component.go b/common/component.go new file mode 100644 index 0000000..1ec547b --- /dev/null +++ b/common/component.go @@ -0,0 +1,9 @@ +package common + +import tea "github.com/charmbracelet/bubbletea" + +type Component interface { + tea.Model + //help.KeyMap + SetSize(width int, height int) +} diff --git a/common/keymap.go b/common/keymap.go index 487e0c1..849e0b6 100644 --- a/common/keymap.go +++ b/common/keymap.go @@ -8,12 +8,19 @@ import ( type Keymap struct { Quit key.Binding Back key.Binding + Ok key.Binding + Input key.Binding Add key.Binding Edit key.Binding + Up key.Binding + Down key.Binding + Left key.Binding + Right key.Binding SetReport key.Binding SetContext key.Binding SetProject key.Binding Select key.Binding + Insert key.Binding } // NewKeymap creates a new Keymap. @@ -29,6 +36,16 @@ func NewKeymap() *Keymap { key.WithHelp("esc", "Back"), ), + Ok: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "Ok"), + ), + + Input: key.NewBinding( + key.WithKeys(":"), + key.WithHelp(":", "Input"), + ), + Add: key.NewBinding( key.WithKeys("a"), key.WithHelp("a", "Add new task"), @@ -39,6 +56,26 @@ func NewKeymap() *Keymap { key.WithHelp("e", "Edit task"), ), + Up: key.NewBinding( + key.WithKeys("k", "up"), + key.WithHelp("↑/k", "Up"), + ), + + Down: key.NewBinding( + key.WithKeys("j", "down"), + key.WithHelp("↓/j", "Down"), + ), + + Left: key.NewBinding( + key.WithKeys("h", "left"), + key.WithHelp("←/h", "Left"), + ), + + Right: key.NewBinding( + key.WithKeys("l", "right"), + key.WithHelp("→/l", "Right"), + ), + SetReport: key.NewBinding( key.WithKeys("r"), key.WithHelp("r", "Set report"), @@ -58,5 +95,10 @@ func NewKeymap() *Keymap { key.WithKeys("enter"), key.WithHelp("enter", "Select"), ), + + Insert: key.NewBinding( + key.WithKeys("i"), + key.WithHelp("insert", "Insert mode"), + ), } } diff --git a/common/styles.go b/common/styles.go index e2f809f..0d5ab8e 100644 --- a/common/styles.go +++ b/common/styles.go @@ -72,7 +72,6 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles { styles.Main = lipgloss.NewStyle() formTheme := huh.ThemeBase() - formTheme.Focused.Card = formTheme.Focused.Card.BorderStyle(lipgloss.RoundedBorder()).BorderBottom(true).BorderTop(true) formTheme.Focused.Title = formTheme.Focused.Title.Bold(true) formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("> ") formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true) diff --git a/main.go b/main.go index 818cd54..e0d8faa 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "os" "tasksquire/common" - "tasksquire/model" + "tasksquire/pages" "tasksquire/taskwarrior" tea "github.com/charmbracelet/bubbletea" @@ -32,7 +32,7 @@ func main() { // slog.Error("Uh oh:", err) // os.Exit(1) // } - m := model.NewMainModel(common) + m := pages.NewMainPage(common) if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil { fmt.Println("Error running program:", err) diff --git a/model/model.go b/model/model.go deleted file mode 100644 index 54abdc4..0000000 --- a/model/model.go +++ /dev/null @@ -1,53 +0,0 @@ -package model - -import ( - tea "github.com/charmbracelet/bubbletea" - - "tasksquire/common" - "tasksquire/pages" - "tasksquire/taskwarrior" -) - -type MainModel struct { - common *common.Common - selectedPage tea.Model - selectedTask *taskwarrior.Task - selectedReport *taskwarrior.Report - selectedContext *taskwarrior.Context -} - -func NewMainModel(common *common.Common) *MainModel { - m := &MainModel{ - common: common, - selectedReport: common.TW.GetReport("next"), - selectedContext: common.TW.GetActiveContext(), - } - - m.selectedPage = pages.NewReportPage(common, m.selectedReport) - - return m - -} - -func (m MainModel) Init() tea.Cmd { - return m.selectedPage.Init() -} - -func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - // switch msg := msg.(type) { - // case tea.KeyMsg: - // switch { - // case key.Matches(msg, m.common.Keymap.Add): - // case key.Matches(msg, m.common.Keymap.Edit): - // } - // } - - m.selectedPage, cmd = m.selectedPage.Update(msg) - - return m, cmd -} - -func (m MainModel) View() string { - return m.selectedPage.View() -} diff --git a/pages/contextPicker.go b/pages/contextPicker.go index ef07278..1e59769 100644 --- a/pages/contextPicker.go +++ b/pages/contextPicker.go @@ -23,15 +23,6 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage { contexts: common.TW.GetContexts(), } - // if allowAdd { - // fields = append(fields, huh.NewInput(). - // Key("input"). - // Title("Input"). - // Prompt(fmt.Sprintf("Enter a new %s", header)). - // Inline(false), - // ) - // } - selected := common.TW.GetActiveContext().Name options := make([]string, 0) for _, c := range p.contexts { @@ -59,6 +50,10 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage { return p } +func (p *ContextPickerPage) SetSize(width, height int) { + p.common.SetSize(width, height) +} + func (p *ContextPickerPage) Init() tea.Cmd { return p.form.Init() } @@ -67,10 +62,12 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + p.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { case key.Matches(msg, p.common.Keymap.Back): - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit @@ -87,7 +84,7 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if p.form.State == huh.StateCompleted { cmds = append(cmds, p.updateContextCmd) - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit diff --git a/pages/main.go b/pages/main.go new file mode 100644 index 0000000..b1c17a1 --- /dev/null +++ b/pages/main.go @@ -0,0 +1,45 @@ +package pages + +import ( + tea "github.com/charmbracelet/bubbletea" + + "tasksquire/common" +) + +type MainPage struct { + common *common.Common + activePage common.Component +} + +func NewMainPage(common *common.Common) *MainPage { + m := &MainPage{ + common: common, + } + + m.activePage = NewReportPage(common, common.TW.GetReport(common.TW.GetConfig().Get("uda.tasksquire.report.default"))) + + return m + +} + +func (m *MainPage) Init() tea.Cmd { + return m.activePage.Init() +} + +func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.common.SetSize(msg.Width, msg.Height) + } + + activePage, cmd := m.activePage.Update(msg) + m.activePage = activePage.(common.Component) + + return m, cmd +} + +func (m *MainPage) View() string { + return m.activePage.View() +} diff --git a/pages/projectPicker.go b/pages/projectPicker.go index 49305a7..b804d5e 100644 --- a/pages/projectPicker.go +++ b/pages/projectPicker.go @@ -46,6 +46,10 @@ func NewProjectPickerPage(common *common.Common, activeProject string) *ProjectP return p } +func (p *ProjectPickerPage) SetSize(width, height int) { + p.common.SetSize(width, height) +} + func (p *ProjectPickerPage) Init() tea.Cmd { return p.form.Init() } @@ -54,10 +58,12 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + p.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { case key.Matches(msg, p.common.Keymap.Back): - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit @@ -74,7 +80,7 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if p.form.State == huh.StateCompleted { cmds = append(cmds, p.updateProjectCmd) - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit diff --git a/pages/report.go b/pages/report.go index 601945f..fc181de 100644 --- a/pages/report.go +++ b/pages/report.go @@ -80,16 +80,22 @@ func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage { } } -func (p ReportPage) Init() tea.Cmd { +func (p *ReportPage) SetSize(width int, height int) { + p.common.SetSize(width, height) + + p.taskTable.SetWidth(width - 2) + p.taskTable.SetHeight(height - 4) +} + +func (p *ReportPage) Init() tea.Cmd { return p.getTasks() } -func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: - p.taskTable.SetWidth(msg.Width - 2) - p.taskTable.SetHeight(msg.Height - 4) + p.SetSize(msg.Width, msg.Height) case BackMsg: p.subpageActive = false case TaskMsg: @@ -105,9 +111,7 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case UpdateProjectMsg: p.activeProject = string(msg) cmds = append(cmds, p.getTasks()) - case AddedTaskMsg: - cmds = append(cmds, p.getTasks()) - case EditedTaskMsg: + case UpdatedTasksMsg: cmds = append(cmds, p.getTasks()) case tea.KeyMsg: switch { @@ -117,31 +121,31 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.subpage = NewReportPickerPage(p.common, p.activeReport) p.subpage.Init() p.subpageActive = true - p.common.PageStack.Push(p) + p.common.PushPage(p) return p.subpage, nil case key.Matches(msg, p.common.Keymap.SetContext): p.subpage = NewContextPickerPage(p.common) p.subpage.Init() p.subpageActive = true - p.common.PageStack.Push(p) + p.common.PushPage(p) return p.subpage, nil case key.Matches(msg, p.common.Keymap.Add): p.subpage = NewTaskEditorPage(p.common, taskwarrior.Task{}) p.subpage.Init() p.subpageActive = true - p.common.PageStack.Push(p) + p.common.PushPage(p) return p.subpage, nil case key.Matches(msg, p.common.Keymap.Edit): p.subpage = NewTaskEditorPage(p.common, *p.selectedTask) p.subpage.Init() p.subpageActive = true - p.common.PageStack.Push(p) + p.common.PushPage(p) return p.subpage, nil case key.Matches(msg, p.common.Keymap.SetProject): p.subpage = NewProjectPickerPage(p.common, p.activeProject) p.subpage.Init() p.subpageActive = true - p.common.PageStack.Push(p) + p.common.PushPage(p) return p.subpage, nil } } @@ -150,15 +154,21 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.taskTable, cmd = p.taskTable.Update(msg) cmds = append(cmds, cmd) - if p.tasks != nil { + if p.tasks != nil && len(p.tasks) > 0 { p.selectedTask = (*taskwarrior.Task)(p.tasks[p.taskTable.Cursor()]) + } else { + p.selectedTask = nil } return p, tea.Batch(cmds...) } -func (p ReportPage) View() string { - return p.common.Styles.Main.Render(p.taskTable.View()) + "\n" +func (p *ReportPage) View() string { + // return p.common.Styles.Main.Render(p.taskTable.View()) + "\n" + if p.tasks == nil || len(p.tasks) == 0 { + return p.common.Styles.Main.Render("No tasks found") + } + return p.common.Styles.Main.Render(p.taskTable.View()) } func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) { diff --git a/pages/reportPicker.go b/pages/reportPicker.go index 02cc466..972b402 100644 --- a/pages/reportPicker.go +++ b/pages/reportPicker.go @@ -47,6 +47,10 @@ func NewReportPickerPage(common *common.Common, activeReport *taskwarrior.Report return p } +func (p *ReportPickerPage) SetSize(width, height int) { + p.common.SetSize(width, height) +} + func (p *ReportPickerPage) Init() tea.Cmd { return p.form.Init() } @@ -55,10 +59,12 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + p.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { case key.Matches(msg, p.common.Keymap.Back): - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit @@ -75,7 +81,7 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if p.form.State == huh.StateCompleted { cmds = append(cmds, []tea.Cmd{BackCmd, p.updateReportCmd}...) - model, err := p.common.PageStack.Pop() + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit diff --git a/pages/taskEditor.go b/pages/taskEditor.go index 4c22fd7..3aff2e7 100644 --- a/pages/taskEditor.go +++ b/pages/taskEditor.go @@ -8,15 +8,27 @@ import ( "time" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" +) + +type Mode int + +const ( + ModeNormal Mode = iota + ModeInsert + ModeAddTag + ModeAddProject ) type TaskEditorPage struct { - common *common.Common - task taskwarrior.Task - form *huh.Form - edit bool + common *common.Common + task taskwarrior.Task + form *huh.Form + mode Mode + statusline tea.Model } type TaskEditorKeys struct { @@ -31,10 +43,7 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor p := &TaskEditorPage{ common: common, task: task, - } - - if task.Uuid != "" { - p.edit = true + mode: ModeInsert, } if p.task.Priority == "" { @@ -53,6 +62,12 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor huh.NewInput(). Title("Task"). Value(&p.task.Description). + Validate(func(desc string) error { + if desc == "" { + return fmt.Errorf("task description is required") + } + return nil + }). Inline(true), huh.NewSelect[string](). @@ -90,12 +105,21 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor ), ). WithShowHelp(false). - WithShowErrors(false). + WithShowErrors(true). + // use styles from common + WithHeight(30). + WithWidth(50). WithTheme(p.common.Styles.Form) + p.statusline = NewStatusLine(common, p.mode) + return p } +func (p *TaskEditorPage) SetSize(width, height int) { + p.common.SetSize(width, height) +} + func (p *TaskEditorPage) Init() tea.Cmd { return p.form.Init() } @@ -104,31 +128,61 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, p.common.Keymap.Back): - model, err := p.common.PageStack.Pop() - if err != nil { - slog.Error("page stack empty") - return nil, tea.Quit - } - return model, BackCmd + case SwitchModeMsg: + switch Mode(msg) { + case ModeNormal: + p.mode = ModeNormal + case ModeInsert: + p.mode = ModeInsert } } - f, cmd := p.form.Update(msg) - if f, ok := f.(*huh.Form); ok { - p.form = f - cmds = append(cmds, cmd) + switch p.mode { + case ModeNormal: + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, p.common.Keymap.Back): + model, err := p.common.PopPage() + if err != nil { + slog.Error("page stack empty") + return nil, tea.Quit + } + return model, BackCmd + case key.Matches(msg, p.common.Keymap.Insert): + return p, p.switchModeCmd(ModeInsert) + case key.Matches(msg, p.common.Keymap.Ok): + p.form.State = huh.StateCompleted + case key.Matches(msg, p.common.Keymap.Down): + p.form.NextField() + case key.Matches(msg, p.common.Keymap.Up): + p.form.PrevField() + } + } + case ModeInsert: + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, p.common.Keymap.Back): + return p, p.switchModeCmd(ModeNormal) + } + } + + f, cmd := p.form.Update(msg) + if f, ok := f.(*huh.Form); ok { + p.form = f + cmds = append(cmds, cmd) + } + } + var cmd tea.Cmd + p.statusline, cmd = p.statusline.Update(msg) + cmds = append(cmds, cmd) + if p.form.State == huh.StateCompleted { - if p.edit { - cmds = append(cmds, p.editTaskCmd) - } else { - cmds = append(cmds, p.addTaskCmd) - } - model, err := p.common.PageStack.Pop() + cmds = append(cmds, p.updateTasksCmd) + model, err := p.common.PopPage() if err != nil { slog.Error("page stack empty") return nil, tea.Quit @@ -140,22 +194,87 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (p *TaskEditorPage) View() string { - return p.common.Styles.Main.Render(p.form.View()) + return lipgloss.JoinVertical( + lipgloss.Left, + // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceBackground(p.common.Styles.Warning.GetForeground())), + lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(".")), + p.statusline.View(), + ) } -func (p *TaskEditorPage) addTaskCmd() tea.Msg { +func (p *TaskEditorPage) updateTasksCmd() tea.Msg { p.common.TW.AddTask(&p.task) - return AddedTaskMsg{} + return UpdatedTasksMsg{} } -func (p *TaskEditorPage) editTaskCmd() tea.Msg { - p.common.TW.ModifyTask(&p.task) - return EditedTaskMsg{} +func (p *TaskEditorPage) switchModeCmd(mode Mode) tea.Cmd { + return func() tea.Msg { + return SwitchModeMsg(mode) + } } -type AddedTaskMsg struct{} +type UpdatedTasksMsg struct{} +type SwitchModeMsg Mode -type EditedTaskMsg struct{} +type StatusLine struct { + common *common.Common + mode Mode + input textinput.Model +} + +func NewStatusLine(common *common.Common, mode Mode) *StatusLine { + input := textinput.New() + input.Placeholder = "" + input.Prompt = "" + input.Blur() + + return &StatusLine{ + input: textinput.New(), + common: common, + mode: mode, + } +} + +func (s *StatusLine) Init() tea.Cmd { + s.input.Blur() + return nil +} + +func (s *StatusLine) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case SwitchModeMsg: + s.mode = Mode(msg) + switch s.mode { + case ModeNormal: + s.input.Blur() + case ModeInsert: + s.input.Focus() + } + case tea.KeyMsg: + switch { + case key.Matches(msg, s.common.Keymap.Back): + s.input.Blur() + case key.Matches(msg, s.common.Keymap.Input): + s.input.Focus() + } + } + + s.input, cmd = s.input.Update(msg) + return s, cmd +} + +func (s *StatusLine) View() string { + var mode string + switch s.mode { + case ModeNormal: + mode = s.common.Styles.Main.Render("NORMAL") + case ModeInsert: + mode = s.common.Styles.Active.Inline(true).Render("INSERT") + } + return lipgloss.JoinHorizontal(lipgloss.Left, mode, s.input.View()) +} // TODO: move this to taskwarrior; add missing date formats func validateDate(s string) error { diff --git a/taskwarrior/config.go b/taskwarrior/config.go index b20aabd..ed92678 100644 --- a/taskwarrior/config.go +++ b/taskwarrior/config.go @@ -10,9 +10,23 @@ type TWConfig struct { config map[string]string } +var ( + defaultConfig = map[string]string{ + "uda.tasksquire.report.default": "next", + } +) + func NewConfig(config []string) *TWConfig { + cfg := parseConfig(config) + + for key, value := range defaultConfig { + if _, ok := cfg[key]; !ok { + cfg[key] = value + } + } + return &TWConfig{ - config: parseConfig(config), + config: cfg, } } diff --git a/taskwarrior/models.go b/taskwarrior/models.go index 963949d..32b97f3 100644 --- a/taskwarrior/models.go +++ b/taskwarrior/models.go @@ -10,25 +10,25 @@ import ( ) type Task struct { - Id int64 `json:"id"` - Uuid string `json:"uuid"` - Description string `json:"description"` - Project string `json:"project"` - Priority string `json:"priority"` - Status string `json:"status"` - Tags []string `json:"tags"` - Depends []string `json:"depends"` - Urgency float32 `json:"urgency"` - Parent string `json:"parent"` - Due string `json:"due"` - Wait string `json:"wait"` - Scheduled string `json:"scheduled"` - Until string `json:"until"` - Start string `json:"start"` - End string `json:"end"` - Entry string `json:"entry"` - Modified string `json:"modified"` - Recur string `json:"recur"` + Id int64 `json:"id,omitempty"` + Uuid string `json:"uuid,omitempty"` + Description string `json:"description,omitempty"` + Project string `json:"project,omitempty"` + Priority string `json:"priority,omitempty"` + Status string `json:"status,omitempty"` + Tags []string `json:"tags,omitempty"` + Depends []string `json:"depends,omitempty"` + Urgency float32 `json:"urgency,omitempty"` + Parent string `json:"parent,omitempty"` + Due string `json:"due,omitempty"` + Wait string `json:"wait,omitempty"` + Scheduled string `json:"scheduled,omitempty"` + Until string `json:"until,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + Entry string `json:"entry,omitempty"` + Modified string `json:"modified,omitempty"` + Recur string `json:"recur,omitempty"` } func (t *Task) GetString(fieldWFormat string) string { diff --git a/taskwarrior/taskwarrior.go b/taskwarrior/taskwarrior.go index 2686443..261260e 100644 --- a/taskwarrior/taskwarrior.go +++ b/taskwarrior/taskwarrior.go @@ -1,6 +1,7 @@ package taskwarrior import ( + "bytes" "encoding/json" "fmt" "log/slog" @@ -87,7 +88,7 @@ type TaskWarrior interface { GetTasks(report *Report, filter ...string) Tasks AddTask(task *Task) error - ModifyTask(task *Task) + ImportTask(task *Task) } type TaskSquire struct { @@ -325,20 +326,20 @@ func (ts *TaskSquire) AddTask(task *Task) error { } // TODO error handling -func (ts *TaskSquire) ModifyTask(task *Task) { +func (ts *TaskSquire) ImportTask(task *Task) { ts.mutex.Lock() defer ts.mutex.Unlock() - jsonStr, err := json.Marshal(Tasks{task}) + tasks, err := json.Marshal(Tasks{task}) if err != nil { slog.Error("Failed marshalling task:", err) } - cmd := exec.Command(twBinary, append([]string{"echo", string(jsonStr), "|"}, append(ts.defaultArgs, []string{"import", "-"}...)...)...) + cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"import", "-"}...)...) + cmd.Stdin = bytes.NewBuffer(tasks) out, err := cmd.CombinedOutput() - strOut := string(out) if err != nil { - slog.Error("Failed modifying task:", err, strOut) + slog.Error("Failed modifying task:", err, string(out)) } }