From 4ad25edc0e233a9417268340ee1642cae92a714a Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 12 Jul 2018 11:14:52 -0700 Subject: [PATCH] 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] +}