diff --git a/todoist/display.go b/todoist/display.go index 90e500b9..c6004c22 100644 --- a/todoist/display.go +++ b/todoist/display.go @@ -3,7 +3,6 @@ package todoist import ( "fmt" - "github.com/gdamore/tcell" "github.com/rivo/tview" "github.com/senorprogrammer/wtf/wtf" ) @@ -11,20 +10,21 @@ import ( const checkWidth = 4 func (w *Widget) display() { - if len(w.list) == 0 { + proj := w.CurrentProject() + + if proj == nil { return } - list := w.list[w.idx] - w.View.SetTitle(fmt.Sprintf("%s- [green]%s[white] ", w.Name, list.Project.Name)) - str := wtf.SigilStr(len(w.list), w.idx, w.View) + "\n" + w.View.SetTitle(fmt.Sprintf("%s- [green]%s[white] ", w.Name, proj.Project.Name)) + str := wtf.SigilStr(len(w.projects), w.idx, w.View) + "\n" - maxLen := w.list[w.idx].LongestLine() + maxLen := proj.LongestLine() - for index, item := range list.items { + for index, item := range proj.tasks { foreColor, backColor := "white", wtf.Config.UString("wtf.colors.background", "black") - if index == list.index { + if index == proj.index { foreColor = wtf.Config.UString("wtf.colors.highlight.fore", "black") backColor = wtf.Config.UString("wtf.colors.highlight.back", "orange") } @@ -36,45 +36,9 @@ func (w *Widget) display() { tview.Escape(item.Content), ) - row = row + wtf.PadRow((checkWidth+len(item.Content)), (checkWidth+maxLen+1)) + "\n" - str = str + row + str = str + row + wtf.PadRow((checkWidth+len(item.Content)), (checkWidth+maxLen+1)) + "\n" } w.View.Clear() w.View.SetText(str) } - -func (w *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { - if len(w.list) == 0 { - return event - } - - switch string(event.Rune()) { - case "r": - w.Refresh() - return nil - case "d": - w.Delete() - return nil - case "c": - w.Close() - return nil - } - - switch fromVim(event) { - case tcell.KeyLeft: - w.Prev() - return nil - case tcell.KeyRight: - w.Next() - return nil - case tcell.KeyUp: - w.UP() - return nil - case tcell.KeyDown: - w.Down() - return nil - } - - return event -} diff --git a/todoist/list.go b/todoist/list.go deleted file mode 100644 index 6f98b5c2..00000000 --- a/todoist/list.go +++ /dev/null @@ -1,91 +0,0 @@ -package todoist - -import ( - "fmt" - - "github.com/darkSasori/todoist" -) - -type List struct { - todoist.Project - items []todoist.Task - index int -} - -func NewList(id int) *List { - project, err := todoist.GetProject(id) - if err != nil { - panic(err) - } - - list := &List{ - Project: project, - index: -1, - } - list.loadItems() - return list -} - -func (l List) isFirst() bool { - return l.index == 0 -} - -func (l List) isLast() bool { - return l.index >= len(l.items)-1 -} - -func (l *List) up() { - l.index = l.index - 1 - if l.index < 0 { - l.index = len(l.items) - 1 - } -} - -func (l *List) down() { - if l.index == -1 { - l.index = 0 - return - } - - l.index = l.index + 1 - if l.index >= len(l.items) { - l.index = 0 - } -} - -func (l *List) loadItems() { - tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", l.ID)}) - if err != nil { - panic(err) - } - - l.items = tasks -} - -func (list *List) LongestLine() int { - maxLen := 0 - - for _, item := range list.items { - if len(item.Content) > maxLen { - maxLen = len(item.Content) - } - } - - return maxLen -} - -func (l *List) close() { - if err := l.items[l.index].Close(); err != nil { - panic(err) - } - - l.loadItems() -} - -func (l *List) delete() { - if err := l.items[l.index].Delete(); err != nil { - panic(err) - } - - l.loadItems() -} diff --git a/todoist/project.go b/todoist/project.go new file mode 100644 index 00000000..ce1a31ec --- /dev/null +++ b/todoist/project.go @@ -0,0 +1,109 @@ +package todoist + +import ( + "fmt" + + "github.com/darkSasori/todoist" +) + +type Project struct { + todoist.Project + + index int + tasks []todoist.Task +} + +func NewProject(id int) *Project { + project, err := todoist.GetProject(id) + if err != nil { + panic(err) + } + + proj := &Project{ + Project: project, + index: -1, + } + + proj.loadTasks() + + return proj +} + +func (proj *Project) isFirst() bool { + return proj.index == 0 +} + +func (proj *Project) isLast() bool { + return proj.index >= len(proj.tasks)-1 +} + +func (proj *Project) up() { + proj.index = proj.index - 1 + + if proj.index < 0 { + proj.index = len(proj.tasks) - 1 + } +} + +func (proj *Project) down() { + if proj.index == -1 { + proj.index = 0 + return + } + + proj.index = proj.index + 1 + if proj.index >= len(proj.tasks) { + proj.index = 0 + } +} + +func (proj *Project) loadTasks() { + tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", proj.ID)}) + if err == nil { + proj.tasks = tasks + } +} + +func (proj *Project) LongestLine() int { + maxLen := 0 + + for _, task := range proj.tasks { + if len(task.Content) > maxLen { + maxLen = len(task.Content) + } + } + + return maxLen +} + +func (proj *Project) currentTask() *todoist.Task { + if proj.index < 0 { + return nil + } + + return &proj.tasks[proj.index] +} + +func (proj *Project) closeSelectedTask() { + currTask := proj.currentTask() + + if currTask != nil { + if err := currTask.Close(); err != nil { + return + } + + proj.loadTasks() + } +} + +func (proj *Project) deleteSelectedTask() { + currTask := proj.currentTask() + + if currTask != nil { + if err := currTask.Delete(); err != nil { + return + } + + proj.loadTasks() + } +} diff --git a/todoist/widget.go b/todoist/widget.go index e40089fa..cc9635ab 100644 --- a/todoist/widget.go +++ b/todoist/widget.go @@ -12,10 +12,10 @@ import ( type Widget struct { wtf.TextWidget - app *tview.Application - pages *tview.Pages - list []*List - idx int + app *tview.Application + pages *tview.Pages + projects []*Project + idx int } func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { @@ -27,14 +27,46 @@ func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { } todoist.Token = os.Getenv("WTF_TODOIST_TOKEN") - widget.list = loadProjects() + widget.projects = loadProjects() widget.View.SetInputCapture(widget.keyboardIntercept) return &widget } +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) CurrentProject() *Project { + return widget.ProjectAt(widget.idx) +} + +func (widget *Widget) ProjectAt(idx int) *Project { + if len(widget.projects) == 0 { + return nil + } + + return widget.projects[idx] +} + +func (w *Widget) NextProject() { + w.idx = w.idx + 1 + if w.idx == len(w.projects) { + w.idx = 0 + } + + w.display() +} + +func (w *Widget) PreviousProject() { + w.idx = w.idx - 1 + if w.idx < 0 { + w.idx = len(w.projects) - 1 + } + + w.display() +} + func (w *Widget) Refresh() { - if w.Disabled() || len(w.list) == 0 { + if w.Disabled() || w.CurrentProject() == nil { return } @@ -42,63 +74,94 @@ func (w *Widget) Refresh() { w.display() } -func (w *Widget) Next() { - w.idx = w.idx + 1 - if w.idx == len(w.list) { - w.idx = 0 - } - - w.display() -} - -func (w *Widget) Prev() { - w.idx = w.idx - 1 - if w.idx < 0 { - w.idx = len(w.list) - 1 - } - - w.display() -} +/* -------------------- Keyboard Movement -------------------- */ +// Down selects the next item in the list func (w *Widget) Down() { - w.list[w.idx].down() + w.CurrentProject().down() w.display() } -func (w *Widget) UP() { - w.list[w.idx].up() +// Up selects the previous item in the list +func (w *Widget) Up() { + w.CurrentProject().up() w.display() } +// Close closes the currently-selected task in the currently-selected project func (w *Widget) Close() { - w.list[w.idx].close() - if w.list[w.idx].isLast() { - w.UP() + w.CurrentProject().closeSelectedTask() + + if w.CurrentProject().isLast() { + w.Up() return } + w.Down() } +// Delete deletes the currently-selected task in the currently-selected project func (w *Widget) Delete() { - w.list[w.idx].close() - if w.list[w.idx].isLast() { - w.UP() + w.CurrentProject().deleteSelectedTask() + + if w.CurrentProject().isLast() { + w.Up() return } + w.Down() } -func loadProjects() []*List { - lists := []*List{} - for _, id := range wtf.Config.UList("wtf.mods.todoist.projects") { - list := NewList(id.(int)) - lists = append(lists, list) +/* -------------------- Unexported Functions -------------------- */ + +func (w *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + if len(w.projects) == 0 { + return event } - return lists + switch string(event.Rune()) { + case "r": + w.Refresh() + return nil + case "d": + w.Delete() + return nil + case "c": + w.Close() + return nil + } + + switch w.vimBindings(event) { + case tcell.KeyLeft: + w.PreviousProject() + return nil + case tcell.KeyRight: + w.NextProject() + return nil + case tcell.KeyUp: + w.Up() + return nil + case tcell.KeyDown: + w.Down() + return nil + } + + return event } -func fromVim(event *tcell.EventKey) tcell.Key { +// TODO: Rename this List to Projects so the internal can be Checklist +func loadProjects() []*Project { + projects := []*Project{} + + for _, id := range wtf.Config.UList("wtf.mods.todoist.projects") { + proj := NewProject(id.(int)) + projects = append(projects, proj) + } + + return projects +} + +func (w *Widget) vimBindings(event *tcell.EventKey) tcell.Key { switch string(event.Rune()) { case "h": return tcell.KeyLeft diff --git a/wtf/checklist_widget.go b/wtf/checklist.go similarity index 74% rename from wtf/checklist_widget.go rename to wtf/checklist.go index 34b1cd38..4aa60c75 100644 --- a/wtf/checklist_widget.go +++ b/wtf/checklist.go @@ -1,5 +1,7 @@ package wtf +// Checklist is a module for creating generic checklist implementations +// See 'Todo' for an implementation example type Checklist struct { Selected int @@ -16,6 +18,7 @@ func NewChecklist() Checklist { /* -------------------- Exported Functions -------------------- */ +// Add creates a new item in the checklist func (list *Checklist) Add(checked bool, text string) { item := ChecklistItem{ Checked: checked, @@ -25,6 +28,7 @@ func (list *Checklist) Add(checked bool, text string) { list.Items = append([]*ChecklistItem{&item}, list.Items...) } +// CheckedItems returns a slice of all the checked items func (list *Checklist) CheckedItems() []*ChecklistItem { items := []*ChecklistItem{} @@ -37,11 +41,13 @@ func (list *Checklist) CheckedItems() []*ChecklistItem { return items } +// Delete removes the selected item from the checklist func (list *Checklist) Delete() { list.Items = append(list.Items[:list.Selected], list.Items[list.Selected+1:]...) list.Prev() } +// Demote moves the selected item down in the checklist func (list *Checklist) Demote() { if list.IsUnselectable() { return @@ -56,14 +62,17 @@ func (list *Checklist) Demote() { list.Selected = j } +// IsSelectable returns true if the checklist has selectable items, false if it does not func (list *Checklist) IsSelectable() bool { return list.Selected >= 0 && list.Selected < len(list.Items) } +// IsUnselectable returns true if the checklist has no selectable items, false if it does func (list *Checklist) IsUnselectable() bool { return !list.IsSelectable() } +// Next selects the next item in the checklist func (list *Checklist) Next() { list.Selected = list.Selected + 1 if list.Selected >= len(list.Items) { @@ -71,6 +80,7 @@ func (list *Checklist) Next() { } } +// LongestLine returns the length of the longest checklist item's text func (list *Checklist) LongestLine() int { maxLen := 0 @@ -83,6 +93,7 @@ func (list *Checklist) LongestLine() int { return maxLen } +// Prev selects the previous item in the checklist func (list *Checklist) Prev() { list.Selected = list.Selected - 1 if list.Selected < 0 { @@ -90,6 +101,7 @@ func (list *Checklist) Prev() { } } +// Promote moves the selected item up in the checklist func (list *Checklist) Promote() { if list.IsUnselectable() { return @@ -104,6 +116,7 @@ func (list *Checklist) Promote() { list.Selected = j } +// SelectedItem returns the currently-selected checklist item or nil if no item is selected func (list *Checklist) SelectedItem() *ChecklistItem { if list.IsUnselectable() { return nil @@ -130,6 +143,7 @@ func (list *Checklist) Toggle() { list.SelectedItem().Toggle() } +// UncheckedItems returns a slice of all the unchecked items func (list *Checklist) UncheckedItems() []*ChecklistItem { items := []*ChecklistItem{} @@ -142,10 +156,12 @@ func (list *Checklist) UncheckedItems() []*ChecklistItem { return items } +// Unselect removes the current select such that no item is selected func (list *Checklist) Unselect() { list.Selected = -1 } +// Update sets the text of the currently-selected item to the provided text func (list *Checklist) Update(text string) { item := list.SelectedItem()