From 4ad25edc0e233a9417268340ee1642cae92a714a Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 12 Jul 2018 11:14:52 -0700 Subject: [PATCH 1/3] First pass at creating a generic checklist component The idea is that checklist-like modules would all share an underlying checklist implementation (ie: Todo and Todoist) to avoid duplication. --- todo/display.go | 28 +++---- todo/item.go | 22 ------ todo/list.go | 165 -------------------------------------- todo/widget.go | 12 +-- todoist/display.go | 20 ++++- todoist/list.go | 12 +++ wtf/checklist_item.go | 18 +++++ wtf/checklist_widget.go | 171 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 237 insertions(+), 211 deletions(-) delete mode 100644 todo/item.go delete mode 100644 todo/list.go create mode 100644 wtf/checklist_item.go create mode 100644 wtf/checklist_widget.go diff --git a/todo/display.go b/todo/display.go index 8757bad4..91a8193b 100644 --- a/todo/display.go +++ b/todo/display.go @@ -3,6 +3,7 @@ package todo import ( "fmt" + "github.com/rivo/tview" "github.com/senorprogrammer/wtf/wtf" ) @@ -10,38 +11,35 @@ const checkWidth = 4 func (widget *Widget) display() { str := "" - newList := List{selected: -1} - - selectedItem := widget.list.Selected() - maxLineLen := widget.list.LongestLine() + newList := wtf.NewChecklist() for _, item := range widget.list.UncheckedItems() { - str = str + widget.formattedItemLine(item, selectedItem, maxLineLen) + str = str + widget.formattedItemLine(item, widget.list.SelectedItem(), widget.list.LongestLine()) newList.Items = append(newList.Items, item) } for _, item := range widget.list.CheckedItems() { - str = str + widget.formattedItemLine(item, selectedItem, maxLineLen) + str = str + widget.formattedItemLine(item, widget.list.SelectedItem(), widget.list.LongestLine()) newList.Items = append(newList.Items, item) } - newList.SetSelectedByItem(widget.list.Selected()) - widget.SetList(&newList) + newList.SetSelectedByItem(widget.list.SelectedItem()) + widget.SetList(newList) widget.View.Clear() widget.View.SetText(str) } -func (widget *Widget) formattedItemLine(item *Item, selectedItem *Item, maxLen int) string { +func (widget *Widget) formattedItemLine(item *wtf.ChecklistItem, selectedItem *wtf.ChecklistItem, maxLen int) string { foreColor, backColor := "white", wtf.Config.UString("wtf.colors.background", "black") if item.Checked { - foreColor = wtf.Config.UString("wtf.mods.todo.colors.checked", "white") + foreColor = wtf.Config.UString("wtf.colors.checked", "white") } if widget.View.HasFocus() && (item == selectedItem) { - foreColor = wtf.Config.UString("wtf.mods.todo.colors.highlight.fore", "black") - backColor = wtf.Config.UString("wtf.mods.todo.colors.highlight.back", "white") + foreColor = wtf.Config.UString("wtf.colors.highlight.fore", "black") + backColor = wtf.Config.UString("wtf.colors.highlight.back", "orange") } str := fmt.Sprintf( @@ -49,10 +47,8 @@ func (widget *Widget) formattedItemLine(item *Item, selectedItem *Item, maxLen i foreColor, backColor, item.CheckMark(), - item.Text, + tview.Escape(item.Text), ) - str = str + wtf.PadRow((checkWidth+len(item.Text)), (checkWidth+maxLen)) + "\n" - - return str + return str + wtf.PadRow((checkWidth+len(item.Text)), (checkWidth+maxLen+1)) + "\n" } diff --git a/todo/item.go b/todo/item.go deleted file mode 100644 index f546b124..00000000 --- a/todo/item.go +++ /dev/null @@ -1,22 +0,0 @@ -package todo - -import ( - "github.com/senorprogrammer/wtf/wtf" -) - -type Item struct { - Checked bool - Text string -} - -func (item *Item) CheckMark() string { - if item.Checked { - return wtf.Config.UString("wtf.mods.todo.checkedIcon", "x") - } else { - return " " - } -} - -func (item *Item) Toggle() { - item.Checked = !item.Checked -} diff --git a/todo/list.go b/todo/list.go deleted file mode 100644 index 0e852f6c..00000000 --- a/todo/list.go +++ /dev/null @@ -1,165 +0,0 @@ -package todo - -type List struct { - Items []*Item - - selected int -} - -/* -------------------- Exported Functions -------------------- */ - -func (list *List) Add(text string) { - item := Item{ - Checked: false, - Text: text, - } - - list.Items = append([]*Item{&item}, list.Items...) -} - -func (list *List) CheckedItems() []*Item { - items := []*Item{} - - for _, item := range list.Items { - if item.Checked { - items = append(items, item) - } - } - - return items -} - -func (list *List) Delete() { - list.Items = append(list.Items[:list.selected], list.Items[list.selected+1:]...) - list.Prev() -} - -func (list *List) Demote() { - if list.isUnselectable() { - return - } - - j := list.selected + 1 - if j >= len(list.Items) { - j = 0 - } - - list.Swap(list.selected, j) - list.selected = j -} - -func (list *List) Next() { - list.selected = list.selected + 1 - if list.selected >= len(list.Items) { - list.selected = 0 - } -} - -func (list *List) LongestLine() int { - maxLen := 0 - - for _, item := range list.Items { - if len(item.Text) > maxLen { - maxLen = len(item.Text) - } - } - - return maxLen -} - -func (list *List) Prev() { - list.selected = list.selected - 1 - if list.selected < 0 { - list.selected = len(list.Items) - 1 - } -} - -func (list *List) Promote() { - if list.isUnselectable() { - return - } - - j := list.selected - 1 - if j < 0 { - j = len(list.Items) - 1 - } - - list.Swap(list.selected, j) - list.selected = j -} - -func (list *List) Selected() *Item { - if list.isUnselectable() { - return nil - } - - return list.Items[list.selected] -} - -func (list *List) SetSelectedByItem(selectableItem *Item) { - for idx, item := range list.Items { - if item == selectableItem { - list.selected = idx - break - } - } -} - -// Toggle switches the checked state of the currently-selected item -func (list *List) Toggle() { - if list.isUnselectable() { - return - } - - list.Selected().Toggle() -} - -func (list *List) UncheckedItems() []*Item { - items := []*Item{} - - for _, item := range list.Items { - if !item.Checked { - items = append(items, item) - } - } - - return items -} - -func (list *List) Unselect() { - list.selected = -1 -} - -func (list *List) Update(text string) { - item := list.Selected() - - if item == nil { - return - } - - item.Text = text -} - -/* -------------------- Sort Interface -------------------- */ - -func (list *List) Len() int { - return len(list.Items) -} - -func (list *List) Less(i, j int) bool { - return list.Items[i].Text < list.Items[j].Text -} - -func (list *List) Swap(i, j int) { - list.Items[i], list.Items[j] = list.Items[j], list.Items[i] -} - -/* -------------------- Unexported Functions -------------------- */ - -func (list *List) isSelectable() bool { - return list.selected >= 0 && list.selected < len(list.Items) -} - -func (list *List) isUnselectable() bool { - return !list.isSelectable() -} diff --git a/todo/widget.go b/todo/widget.go index 6bebc605..c0d9bef1 100644 --- a/todo/widget.go +++ b/todo/widget.go @@ -39,7 +39,7 @@ type Widget struct { app *tview.Application filePath string - list *List + list wtf.Checklist pages *tview.Pages } @@ -49,7 +49,7 @@ func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { app: app, filePath: wtf.Config.UString("wtf.mods.todo.filename"), - list: &List{selected: -1}, + list: wtf.NewChecklist(), pages: pages, } @@ -67,7 +67,7 @@ func (widget *Widget) Refresh() { widget.display() } -func (widget *Widget) SetList(newList *List) { +func (widget *Widget) SetList(newList wtf.Checklist) { widget.list = newList } @@ -75,11 +75,11 @@ func (widget *Widget) SetList(newList *List) { // edit opens a modal dialog that permits editing the text of the currently-selected item func (widget *Widget) editItem() { - if widget.list.Selected() == nil { + if widget.list.SelectedItem() == nil { return } - form := widget.modalForm("Edit:", widget.list.Selected().Text) + form := widget.modalForm("Edit:", widget.list.SelectedItem().Text) saveFctn := func() { text := form.GetFormItem(0).(*tview.InputField).GetText() @@ -191,7 +191,7 @@ func (widget *Widget) newItem() { saveFctn := func() { text := form.GetFormItem(0).(*tview.InputField).GetText() - widget.list.Add(text) + widget.list.Add(false, text) widget.persist() widget.pages.RemovePage("modal") widget.app.SetFocus(widget.View) diff --git a/todoist/display.go b/todoist/display.go index 8cbfa858..90e500b9 100644 --- a/todoist/display.go +++ b/todoist/display.go @@ -8,6 +8,8 @@ import ( "github.com/senorprogrammer/wtf/wtf" ) +const checkWidth = 4 + func (w *Widget) display() { if len(w.list) == 0 { return @@ -17,11 +19,25 @@ func (w *Widget) display() { 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" + maxLen := w.list[w.idx].LongestLine() + for index, item := range list.items { + foreColor, backColor := "white", wtf.Config.UString("wtf.colors.background", "black") + if index == list.index { - str = str + fmt.Sprintf("[%s]", wtf.Config.UString("wtf.colors.border.focused", "grey")) + foreColor = wtf.Config.UString("wtf.colors.highlight.fore", "black") + backColor = wtf.Config.UString("wtf.colors.highlight.back", "orange") } - str = str + fmt.Sprintf("| | %s[white]\n", tview.Escape(item.Content)) + + row := fmt.Sprintf( + "[%s:%s]| | %s[white]", + foreColor, + backColor, + tview.Escape(item.Content), + ) + + row = row + wtf.PadRow((checkWidth+len(item.Content)), (checkWidth+maxLen+1)) + "\n" + str = str + row } w.View.Clear() diff --git a/todoist/list.go b/todoist/list.go index 81b9d55e..6f98b5c2 100644 --- a/todoist/list.go +++ b/todoist/list.go @@ -62,6 +62,18 @@ func (l *List) loadItems() { 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) diff --git a/wtf/checklist_item.go b/wtf/checklist_item.go new file mode 100644 index 00000000..7903fb65 --- /dev/null +++ b/wtf/checklist_item.go @@ -0,0 +1,18 @@ +package wtf + +type ChecklistItem struct { + Checked bool + Text string +} + +func (item *ChecklistItem) CheckMark() string { + if item.Checked { + return Config.UString("wtf.mods.todo.checkedIcon", "x") + } else { + return " " + } +} + +func (item *ChecklistItem) Toggle() { + item.Checked = !item.Checked +} diff --git a/wtf/checklist_widget.go b/wtf/checklist_widget.go new file mode 100644 index 00000000..34b1cd38 --- /dev/null +++ b/wtf/checklist_widget.go @@ -0,0 +1,171 @@ +package wtf + +type Checklist struct { + Selected int + + Items []*ChecklistItem +} + +func NewChecklist() Checklist { + list := Checklist{ + Selected: -1, + } + + return list +} + +/* -------------------- Exported Functions -------------------- */ + +func (list *Checklist) Add(checked bool, text string) { + item := ChecklistItem{ + Checked: checked, + Text: text, + } + + list.Items = append([]*ChecklistItem{&item}, list.Items...) +} + +func (list *Checklist) CheckedItems() []*ChecklistItem { + items := []*ChecklistItem{} + + for _, item := range list.Items { + if item.Checked { + items = append(items, item) + } + } + + return items +} + +func (list *Checklist) Delete() { + list.Items = append(list.Items[:list.Selected], list.Items[list.Selected+1:]...) + list.Prev() +} + +func (list *Checklist) Demote() { + if list.IsUnselectable() { + return + } + + j := list.Selected + 1 + if j >= len(list.Items) { + j = 0 + } + + list.Swap(list.Selected, j) + list.Selected = j +} + +func (list *Checklist) IsSelectable() bool { + return list.Selected >= 0 && list.Selected < len(list.Items) +} + +func (list *Checklist) IsUnselectable() bool { + return !list.IsSelectable() +} + +func (list *Checklist) Next() { + list.Selected = list.Selected + 1 + if list.Selected >= len(list.Items) { + list.Selected = 0 + } +} + +func (list *Checklist) LongestLine() int { + maxLen := 0 + + for _, item := range list.Items { + if len(item.Text) > maxLen { + maxLen = len(item.Text) + } + } + + return maxLen +} + +func (list *Checklist) Prev() { + list.Selected = list.Selected - 1 + if list.Selected < 0 { + list.Selected = len(list.Items) - 1 + } +} + +func (list *Checklist) Promote() { + if list.IsUnselectable() { + return + } + + j := list.Selected - 1 + if j < 0 { + j = len(list.Items) - 1 + } + + list.Swap(list.Selected, j) + list.Selected = j +} + +func (list *Checklist) SelectedItem() *ChecklistItem { + if list.IsUnselectable() { + return nil + } + + return list.Items[list.Selected] +} + +func (list *Checklist) SetSelectedByItem(selectableItem *ChecklistItem) { + for idx, item := range list.Items { + if item == selectableItem { + list.Selected = idx + break + } + } +} + +// Toggle switches the checked state of the currently-selected item +func (list *Checklist) Toggle() { + if list.IsUnselectable() { + return + } + + list.SelectedItem().Toggle() +} + +func (list *Checklist) UncheckedItems() []*ChecklistItem { + items := []*ChecklistItem{} + + for _, item := range list.Items { + if !item.Checked { + items = append(items, item) + } + } + + return items +} + +func (list *Checklist) Unselect() { + list.Selected = -1 +} + +func (list *Checklist) Update(text string) { + item := list.SelectedItem() + + if item == nil { + return + } + + item.Text = text +} + +/* -------------------- Sort Interface -------------------- */ + +func (list *Checklist) Len() int { + return len(list.Items) +} + +func (list *Checklist) Less(i, j int) bool { + return list.Items[i].Text < list.Items[j].Text +} + +func (list *Checklist) Swap(i, j int) { + list.Items[i], list.Items[j] = list.Items[j], list.Items[i] +} From 5ebab79e2cc5d22402da21f21062a5826060607d Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 12 Jul 2018 17:09:36 -0700 Subject: [PATCH 2/3] Clean up the Todoist module code a bit - match names to things --- todoist/display.go | 54 ++------- todoist/list.go | 91 -------------- todoist/project.go | 109 +++++++++++++++++ todoist/widget.go | 141 ++++++++++++++++------ wtf/{checklist_widget.go => checklist.go} | 16 +++ 5 files changed, 236 insertions(+), 175 deletions(-) delete mode 100644 todoist/list.go create mode 100644 todoist/project.go rename wtf/{checklist_widget.go => checklist.go} (74%) 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() From 857702e9710d753ca9771cc8c42c1436c4a6abd5 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 12 Jul 2018 17:51:23 -0700 Subject: [PATCH 3/3] Add specs for wtf.ChecklistItem --- wtf/checklist_item.go | 5 ++++ wtftests/checklist_item_test.go | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 wtftests/checklist_item_test.go diff --git a/wtf/checklist_item.go b/wtf/checklist_item.go index 7903fb65..514ef714 100644 --- a/wtf/checklist_item.go +++ b/wtf/checklist_item.go @@ -1,10 +1,13 @@ package wtf +// ChecklistItem is a module for creating generic checklist implementations +// See 'Todo' for an implementation example type ChecklistItem struct { Checked bool Text string } +// CheckMark returns the string used to indicate a ChecklistItem is checked or unchecked func (item *ChecklistItem) CheckMark() string { if item.Checked { return Config.UString("wtf.mods.todo.checkedIcon", "x") @@ -13,6 +16,8 @@ func (item *ChecklistItem) CheckMark() string { } } +// Toggle changes the checked state of the ChecklistItem +// If checked, it is unchecked. If unchecked, it is checked func (item *ChecklistItem) Toggle() { item.Checked = !item.Checked } diff --git a/wtftests/checklist_item_test.go b/wtftests/checklist_item_test.go new file mode 100644 index 00000000..799f4f55 --- /dev/null +++ b/wtftests/checklist_item_test.go @@ -0,0 +1,42 @@ +package wtftests + +import ( + "testing" + + "github.com/olebedev/config" + . "github.com/senorprogrammer/wtf/wtf" + . "github.com/stretchr/testify/assert" +) + +/* -------------------- CheckMark -------------------- */ + +func TestCheckMark(t *testing.T) { + loadConfig() + + item := ChecklistItem{} + Equal(t, " ", item.CheckMark()) + + item = ChecklistItem{Checked: true} + Equal(t, "x", item.CheckMark()) +} + +/* -------------------- Toggle -------------------- */ + +func TestToggle(t *testing.T) { + loadConfig() + + item := ChecklistItem{} + Equal(t, false, item.Checked) + + item.Toggle() + Equal(t, true, item.Checked) + + item.Toggle() + Equal(t, false, item.Checked) +} + +/* -------------------- helpers -------------------- */ + +func loadConfig() { + Config, _ = config.ParseYamlFile("../_sample_configs/simple_config.yml") +}