This commit is contained in:
Brian Buller 2019-02-15 11:15:26 -06:00
parent 017ce196b4
commit 9bc8de8700
10 changed files with 1167 additions and 0 deletions

207
app_state.go Normal file
View File

@ -0,0 +1,207 @@
package main
import (
"fmt"
"os"
"strings"
todotxt "github.com/JamesClonk/go-todotxt"
"github.com/br0xen/user-config"
)
type AppState struct {
Name string
Version int
config *userConfig.Config
directory string
fileTodo string
fileDone string
fileReport string
ValidOperations map[string][]string
OpFuncs map[string]func([]string) int
TaskList *todotxt.TaskList
DoneList *todotxt.TaskList
screens []Screen
}
func NewApp() *AppState {
app := &AppState{Name: AppName, Version: AppVersion}
app.initialize()
app.doVersionCheck()
if err := app.LoadTaskList(); err != nil {
if len(os.Args) > 1 && os.Args[1] != "--reinit" {
panic(err)
}
}
return app
}
func (a *AppState) run(parms []string) int {
if len(parms) == 0 || parms[0] == "ui" {
// UI Mode
return uiLoop()
}
if fn, ok := a.OpFuncs[parms[0]]; ok {
return fn(parms[1:])
}
fmt.Println("Unknown Command")
return 1
}
func (a *AppState) filterList(list *todotxt.TaskList, filter string) *todotxt.TaskList {
return list.Filter(a.getFilterPredicate(filter))
}
func (a *AppState) getFilteredList(filter string) *todotxt.TaskList {
return a.TaskList.Filter(a.getFilterPredicate(filter))
}
func (a *AppState) getFilteredDoneList(filter string) *todotxt.TaskList {
return a.DoneList.Filter(a.getFilterPredicate(filter))
}
func (a *AppState) getTodoFile() string {
return a.directory + a.fileTodo
}
func (a *AppState) getDoneFile() string {
return a.directory + a.fileDone
}
func (a *AppState) getReportFile() string {
return a.directory + a.fileReport
}
func (a *AppState) addOperation(name string, desc []string, fn func([]string) int) {
a.ValidOperations[name] = desc
a.OpFuncs[name] = fn
}
func (a *AppState) getTaskString(task todotxt.Task) string {
var completed string
completed = " "
if task.Completed {
completed = "X"
}
return fmt.Sprintf("%d. [%s] %s", task.Id, completed, strings.TrimPrefix(task.String(), "x "))
}
func (a *AppState) getDoneTaskString(task todotxt.Task) string {
var completed string
completed = " "
if task.Completed {
completed = "X"
}
return fmt.Sprintf("0. [%s] %s", completed, strings.TrimPrefix(task.String(), "x "))
}
func (a *AppState) doVersionCheck() {
confVer, _ := a.config.GetInt("version")
for confVer < a.Version {
confVer = a.migrate(confVer, a.Version)
}
a.config.SetInt("version", confVer)
}
func (a *AppState) migrate(from, to int) int {
if from == to {
return to
}
switch from {
case 0:
a.initializeConfig()
return 1
}
// If we get all the way down here, we _must_ be done.
return to
}
func (a *AppState) initialize() {
var err error
a.config, err = userConfig.NewConfig(a.Name)
if err != nil {
panic(err)
}
a.ValidOperations = make(map[string][]string)
a.OpFuncs = make(map[string]func([]string) int)
a.addOperation("ls",
[]string{"ls - List Tasks"},
a.opListTasks,
)
a.addOperation("lsa",
[]string{"lsa - The same as 'ls -a'"},
func(args []string) int {
return a.opListTasks(append([]string{"-a"}, args...))
},
)
a.addOperation("add",
[]string{"add - Add a task"},
a.opAddTask,
)
a.addOperation("new",
[]string{"new - Same as 'add'"},
a.opAddTask,
)
a.addOperation("x",
[]string{"x - Toggle a task's complete flag on/off"},
a.opToggleTaskComplete,
)
a.addOperation("done",
[]string{"done - The same as 'x'"},
a.opToggleTaskComplete,
)
a.addOperation("archive",
[]string{"archive [id1 id2 ...] - Archive completed tasks"},
a.opArchiveTasks,
)
a.addOperation("--reinit",
[]string{"--reinit - Reset all Configuration Settings"},
func(args []string) int {
a.initializeConfig()
return 0
},
)
a.addOperation("-h",
[]string{"-h - Print this message"},
a.opPrintUsage,
)
a.addOperation("help",
[]string{"help - Print this message"},
a.opPrintUsage,
)
a.addOperation("--h",
[]string{"--h - Print this message"},
a.opPrintUsage,
)
a.directory = a.config.Get("directory")
a.fileTodo = a.config.Get("todofile")
a.fileDone = a.config.Get("donefile")
a.fileReport = a.config.Get("reportfile")
}
func (a *AppState) initializeConfig() {
fmt.Println("Initializing " + a.Name)
for {
var add string
if a.directory != "" {
add = " (" + a.directory + ")"
}
fmt.Println("Path to todo.txt" + add + ":")
var resp string
fmt.Scanln(&resp)
if resp == "" && a.directory != "" {
resp = a.directory
}
if resp != "" {
if !strings.HasSuffix(resp, "/") {
resp = resp + "/"
}
fmt.Println("Setting todo.txt directory to: " + resp)
a.config.Set("directory", resp)
break
}
}
a.config.Set("todofile", "todo.txt")
a.config.Set("donefile", "done.txt")
a.config.Set("reportfile", "report.txt")
}

28
bundle.go Normal file
View File

@ -0,0 +1,28 @@
package main
type Bundle map[string]interface{}
func (b Bundle) setValue(key string, val interface{}) {
b[key] = val
}
func (b Bundle) getBool(key string, def bool) bool {
if v, ok := b[key].(bool); ok {
return v
}
return def
}
func (b Bundle) getString(key, def string) string {
if v, ok := b[key].(string); ok {
return v
}
return def
}
func (b Bundle) getInt(key string, def int) int {
if v, ok := b[key].(int); ok {
return v
}
return def
}

25
main.go Normal file
View File

@ -0,0 +1,25 @@
package main
import (
"os"
)
const (
AppName = "gask"
AppVersion = 1
)
var app *AppState
func main() {
app = NewApp()
var parms []string
if len(os.Args) > 1 {
parms = os.Args[1:]
} else {
// If no parameters were passed, just do an ls
parms = append(parms, "ui")
}
os.Exit(app.run(parms))
}

117
model.go Normal file
View File

@ -0,0 +1,117 @@
package main
import (
"strings"
"time"
todotxt "github.com/JamesClonk/go-todotxt"
)
func (a *AppState) addTask(taskString string) error {
t, err := todotxt.ParseTask(taskString)
if err != nil {
return err
}
if t.CreatedDate.IsZero() {
t.CreatedDate = time.Now()
}
a.TaskList.AddTask(t)
return nil
}
func (a *AppState) toggleTaskComplete(id int) error {
var task *todotxt.Task
var err error
if task, err = a.TaskList.GetTask(id); err != nil {
return err
}
if task.Completed {
task.Reopen()
} else {
task.Complete()
}
return nil
}
func (a *AppState) archiveTask(id int) error {
var err error
var task *todotxt.Task
if task, err = a.TaskList.GetTask(id); err != nil {
return err
}
a.TaskList.RemoveTask(*task)
task.Completed = true
a.DoneList.AddTask(task)
return nil
}
func (a *AppState) getFilterPredicate(filter string) func(todotxt.Task) bool {
var predicates []func(todotxt.Task) bool
// If none of the 'filter' is in upper-case, do a case-insensitive filter
checkCase := true
if strings.ToLower(filter) == filter {
checkCase = false
}
filterParts := strings.Split(filter, " ")
for _, part := range filterParts {
if strings.HasPrefix(part, "@") {
predicates = append(predicates, func(t todotxt.Task) bool {
for _, v := range t.Contexts {
if "@"+v == part {
return true
}
}
return false
})
} else if strings.HasPrefix(part, "+") {
predicates = append(predicates, func(t todotxt.Task) bool {
for _, v := range t.Projects {
if "+"+v == part {
return true
}
}
return false
})
} else {
predicates = append(predicates, func(t todotxt.Task) bool {
val := t.Original
if !checkCase {
val = strings.ToLower(t.Original)
}
return strings.Contains(val, part)
})
}
}
return func(t todotxt.Task) bool {
for _, v := range predicates {
if v(t) {
return true
}
}
return false
}
}
func (a *AppState) LoadTaskList() error {
var err error
var tl todotxt.TaskList
tl, err = todotxt.LoadFromFilename(a.getTodoFile())
a.TaskList = &tl
return err
}
func (a *AppState) LoadDoneList() error {
var err error
var tl todotxt.TaskList
tl, err = todotxt.LoadFromFilename(a.getDoneFile())
a.DoneList = &tl
return err
}
func (a *AppState) WriteList() error {
return a.TaskList.WriteToFilename(a.getTodoFile())
}
func (a *AppState) WriteDoneList() error {
return a.DoneList.WriteToFilename(a.getDoneFile())
}

62
screen.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
termbox "github.com/nsf/termbox-go"
)
type Screen interface {
handleKeyEvent(termbox.Event) int
initialize(Bundle) error
drawScreen()
}
const (
ScreenMain = iota
ScreenTask
ScreenAbout
ScreenExit
DefaultBg = termbox.ColorBlack
DefaultFg = termbox.ColorWhite
TitleFg = termbox.ColorWhite
TitleBg = termbox.ColorBlue
CursorFg = termbox.ColorBlack
CursorBg = termbox.ColorGreen
)
func (a *AppState) BuildScreens() {
mainScreen := MainScreen{
viewPort: &ViewPort{},
}
aboutScreen := AboutScreen{}
taskScreen := TaskScreen{
viewPort: &ViewPort{},
}
a.screens = append(a.screens, &mainScreen)
a.screens = append(a.screens, &taskScreen)
a.screens = append(a.screens, &aboutScreen)
}
func (a *AppState) drawBackground(bg termbox.Attribute) {
termbox.Clear(0, bg)
}
func (a *AppState) layoutAndDrawScreen(s Screen) {
a.drawBackground(DefaultBg)
s.drawScreen()
termbox.Flush()
}
// ViewPort helps keep track of what's being displayed on the screen
type ViewPort struct {
bytesPerRow int
numberOfRows int
firstRow int
cursor int
}
func readUserInput(e chan termbox.Event) {
for {
e <- termbox.PollEvent()
}
}

30
screen_about.go Normal file
View File

@ -0,0 +1,30 @@
package main
import (
"time"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
// AboutScreen holds all that's going on
type AboutScreen struct {
viewPort *ViewPort
message string
messageTimeout time.Duration
messageTime time.Time
}
func (screen *AboutScreen) initialize(bundle Bundle) error {
return nil
}
func (screen *AboutScreen) handleKeyEvent(event termbox.Event) int {
return ScreenMain
}
func (screen *AboutScreen) drawScreen() {
width, height := termbox.Size()
exitTxt := "Press any key to return to tasks"
termboxUtil.DrawStringAtPoint(exitTxt, (width-len(exitTxt))/2, height-1, TitleFg, TitleBg)
}

380
screen_main.go Normal file
View File

@ -0,0 +1,380 @@
package main
import (
"fmt"
"strings"
"time"
todotxt "github.com/JamesClonk/go-todotxt"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
// MainScreen holds all that's going on
type MainScreen struct {
viewPort *ViewPort
message string
messageTimeout time.Duration
messageTime time.Time
mode int
inputField *termboxUtil.InputField
currentList string
currentFilter string
activeList todotxt.TaskList
displayList []todotxt.Task
undoQueue []string
redoQueue []string
backspaceDoes int
}
const (
MainBundleListKey = "mainscreen.list"
MainBundleFilterKey = "mainscreen.filter"
MainBundleListTodo = "mainscreen.list.todo"
MainBundleListDone = "mainscreen.list.done"
MainModalIdFilter = "mainscreen.filter"
MainModalIdAddTask = "mainscreen.addtask"
MainBackspaceNothing = iota
MainBackspaceMain
MainBackspaceFilter
)
func (screen *MainScreen) initialize(bundle Bundle) error {
if err := screen.reloadList(bundle); err != nil {
return err
}
width, height := termbox.Size()
screen.inputField = termboxUtil.CreateInputField(0, (height - 3), width, 1, DefaultFg, DefaultBg)
screen.inputField.SetID("")
screen.inputField.SetBordered(false)
return nil
}
func (screen *MainScreen) reloadList(bundle Bundle) error {
screen.displayList = []todotxt.Task{}
screen.currentList = bundle.getString(MainBundleListKey, MainBundleListTodo)
switch screen.currentList {
case MainBundleListTodo:
screen.setActiveList(*app.TaskList)
if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" {
filteredList := app.filterList(&screen.activeList, screen.currentFilter)
for _, av := range screen.activeList {
for _, fv := range *filteredList {
if av.String() == fv.String() {
screen.displayList = append(screen.displayList, av)
break
}
}
}
} else {
for _, av := range screen.activeList {
screen.displayList = append(screen.displayList, av)
}
}
case MainBundleListDone:
if err := app.LoadDoneList(); err != nil {
return err
}
screen.setActiveList(*app.DoneList)
if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" {
filteredList := app.filterList(&screen.activeList, screen.currentFilter)
for _, av := range screen.activeList {
for _, fv := range *filteredList {
if av.String() == fv.String() {
screen.displayList = append(screen.displayList, av)
break
}
}
}
} else {
for _, av := range screen.activeList {
screen.displayList = append(screen.displayList, av)
}
}
}
return nil
}
func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
if screen.inputField.GetID() != "" {
return screen.handleInputKeyEvent(event)
}
if event.Ch == '?' {
// Go to About Screen
return ScreenAbout
} else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
if screen.backspaceDoes == MainBackspaceNothing {
if screen.currentList == MainBundleListDone {
screen.reloadList(screen.buildBundle(MainBundleListTodo, screen.currentFilter))
} else if screen.currentFilter != "" {
screen.reloadList(screen.buildBundle(screen.currentList, ""))
}
} else if screen.backspaceDoes == MainBackspaceMain {
screen.reloadList(screen.buildBundle(MainBundleListTodo, screen.currentFilter))
} else if screen.backspaceDoes == MainBackspaceFilter {
screen.reloadList(screen.buildBundle(screen.currentList, ""))
}
return ScreenMain
} else if event.Key == termbox.KeySpace {
return screen.toggleTaskComplete()
} else if event.Ch == 'L' {
return screen.toggleViewList()
} else if event.Ch == 'a' {
return screen.startAddNewTask()
} else if event.Ch == 'e' || event.Ch == 'l' || event.Key == termbox.KeyEnter {
return screen.startEditTaskScreen()
} else if event.Ch == 'j' || event.Key == termbox.KeyArrowDown {
screen.moveCursorDown()
} else if event.Ch == 'k' || event.Key == termbox.KeyArrowUp {
screen.moveCursorUp()
} else if event.Ch == '/' {
screen.startFilter()
} else if event.Ch == 'D' {
screen.archiveCurrentItem()
} else if event.Ch == 'q' {
return ScreenExit
}
return ScreenMain
}
func (screen *MainScreen) handleConfirmKeyEvent(event termbox.Event) int {
return ScreenMain
}
func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
screen.inputField.SetID("add task")
id := screen.inputField.GetID()
if id == "filter" {
if event.Key == termbox.KeyEnter {
// Apply the filter
screen.backspaceDoes = MainBackspaceFilter
screen.reloadList(screen.buildBundle(screen.currentList, screen.inputField.GetValue()))
screen.inputField.SetID("")
screen.inputField.SetValue("")
return ScreenMain
}
} else if id == "add task" {
if event.Key == termbox.KeyEnter {
// Create the new item
err := app.addTask(screen.inputField.GetValue())
if err != nil {
screen.setMessage(err.Error())
}
if err = app.WriteList(); err != nil {
screen.setMessage(err.Error())
return ScreenMain
}
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter))
screen.inputField.SetID("")
screen.inputField.SetValue("")
return ScreenMain
}
}
if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
if screen.inputField.GetValue() == "" {
screen.reloadList(screen.buildBundle(screen.currentList, screen.inputField.GetValue()))
screen.inputField.SetID("")
screen.inputField.SetValue("")
return ScreenMain
}
}
screen.inputField.HandleEvent(event)
return ScreenMain
}
func (screen *MainScreen) setActiveList(list todotxt.TaskList) {
screen.activeList = todotxt.NewTaskList()
for _, v := range list {
screen.activeList.AddTask(&v)
}
}
func (screen *MainScreen) drawScreen() {
if screen.message == "" {
screen.setMessageWithTimeout("Press '?' for help", -1)
}
screen.drawHeader()
for k, v := range screen.displayList {
useFg, useBg := DefaultFg, DefaultBg
if k == screen.viewPort.cursor {
useFg, useBg = CursorFg, CursorBg
}
termboxUtil.DrawStringAtPoint(app.getTaskString(v), 0, k+1, useFg, useBg)
}
screen.drawFooter()
}
func (screen *MainScreen) drawHeader() {
width, _ := termbox.Size()
headerString := screen.currentFilter
if headerString == "" {
if screen.currentList == MainBundleListTodo {
headerString = "Todo List"
} else if screen.currentList == MainBundleListDone {
headerString = "Done List"
}
}
spaces := strings.Repeat(" ", ((width-len(headerString))/2)+1)
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, TitleFg, TitleBg)
}
func (screen *MainScreen) drawFooter() {
if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout {
screen.clearMessage()
}
_, height := termbox.Size()
if screen.inputField.GetID() != "" {
termboxUtil.DrawStringAtPoint(screen.inputField.GetID()+": ", 0, height-2, DefaultFg, DefaultBg)
screen.inputField.Draw()
}
// And the 'message'
termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, DefaultFg, DefaultBg)
}
func (screen *MainScreen) archiveCurrentItem() int {
/*
// Find the task under the cursor
if len(screen.displayList) > screen.viewPort.cursor {
t := screen.displayList[screen.viewPort.cursor]
// Load the task screen with this task
if err := a.archiveTask(t.Id); err != nil {
screen.setMessage(error.Error())
}
b := Bundle{}
b.setValue(TaskBundleTaskIdKey, t.Id)
if err := app.screens[ScreenTask].initialize(b); err != nil {
screen.setMessage(err.Error())
}
return ScreenTask
}
*/
return ScreenMain
}
func (screen *MainScreen) startEditTaskScreen() int {
// Find the task under the cursor
if len(screen.displayList) > screen.viewPort.cursor {
t := screen.displayList[screen.viewPort.cursor]
// Load the task screen with this task
b := Bundle{}
b.setValue(TaskBundleTaskIdKey, t.Id)
if err := app.screens[ScreenTask].initialize(b); err != nil {
screen.setMessage(err.Error())
}
return ScreenTask
}
return ScreenMain
}
func (screen *MainScreen) reloadCurrentView() {
bundle := Bundle{}
bundle.setValue(MainBundleListKey, screen.currentList)
bundle.setValue(MainBundleFilterKey, screen.currentFilter)
screen.initialize(bundle)
}
func (screen *MainScreen) toggleViewList() int {
bundle := Bundle{}
if screen.currentList == MainBundleListTodo {
bundle.setValue(MainBundleListKey, MainBundleListDone)
screen.backspaceDoes = MainBackspaceMain
} else {
bundle.setValue(MainBundleListKey, MainBundleListTodo)
}
bundle.setValue(MainBundleFilterKey, screen.currentFilter)
screen.initialize(bundle)
return ScreenMain
}
func (screen *MainScreen) startAddNewTask() int {
screen.inputField.SetID("add task")
screen.inputField.SetX(len(screen.inputField.GetID()) + 2)
return ScreenMain
}
func (screen *MainScreen) toggleTaskComplete() int {
// Find the task under the cursor
if len(screen.displayList) > screen.viewPort.cursor {
t := screen.displayList[screen.viewPort.cursor]
err := app.toggleTaskComplete(t.Id)
if err != nil {
screen.setMessage(err.Error())
return ScreenMain
}
if err = app.WriteList(); err != nil {
screen.setMessage(err.Error())
}
}
screen.reloadCurrentView()
return ScreenMain
}
func (screen *MainScreen) moveCursorDown() bool {
screen.viewPort.cursor++
if screen.viewPort.cursor >= len(screen.displayList) {
screen.viewPort.cursor = len(screen.displayList) - 1
return false
}
return true
}
func (screen *MainScreen) moveCursorUp() bool {
screen.viewPort.cursor--
if screen.viewPort.cursor < 0 {
screen.viewPort.cursor = 0
return false
}
return true
}
func (screen *MainScreen) startFilter() {
screen.inputField.SetID("filter")
screen.inputField.SetX(len(screen.inputField.GetID()) + 2)
}
func (screen *MainScreen) setMessage(msg string) {
screen.message = msg
screen.messageTime = time.Now()
screen.messageTimeout = time.Second * 2
}
/* setMessageWithTimeout lets you specify the timeout for the message
* setting it to -1 means it won't timeout
*/
func (screen *MainScreen) setMessageWithTimeout(msg string, timeout time.Duration) {
screen.message = msg
screen.messageTime = time.Now()
screen.messageTimeout = timeout
}
func (screen *MainScreen) clearMessage() {
screen.message = ""
screen.messageTimeout = -1
}
func (screen *MainScreen) buildBundle(list, filter string) Bundle {
bundle := Bundle{}
bundle.setValue(MainBundleListKey, list)
bundle.setValue(MainBundleFilterKey, filter)
return bundle
}

122
screen_task.go Normal file
View File

@ -0,0 +1,122 @@
package main
import (
"errors"
"fmt"
"strings"
"time"
todotxt "github.com/JamesClonk/go-todotxt"
"github.com/br0xen/termbox-util"
termbox "github.com/nsf/termbox-go"
)
// TaskScreen holds all that's going on
type TaskScreen struct {
viewPort *ViewPort
message string
messageTimeout time.Duration
messageTime time.Time
inputModal *termboxUtil.InputModal
confirmModal *termboxUtil.ConfirmModal
currentTaskId int
displayTask *todotxt.Task
}
const (
TaskBundleTaskIdKey = "taskscreen.taskid"
)
func (screen *TaskScreen) initialize(bundle Bundle) error {
var err error
screen.currentTaskId = bundle.getInt(TaskBundleTaskIdKey, -1)
if screen.currentTaskId == -1 {
return errors.New("Task Screen Initialization Failed")
}
if screen.displayTask, err = app.TaskList.GetTask(screen.currentTaskId); err != nil {
return err
}
return nil
}
func (screen *TaskScreen) handleKeyEvent(event termbox.Event) int {
if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 || event.Ch == 'h' || event.Key == termbox.KeyArrowLeft {
return ScreenMain
} else if event.Ch == 'j' || event.Key == termbox.KeyArrowDown {
screen.moveCursorDown()
} else if event.Ch == 'k' || event.Key == termbox.KeyArrowUp {
screen.moveCursorUp()
}
return ScreenTask
}
func (screen *TaskScreen) drawScreen() {
screen.drawHeader()
yPos := 1
termboxUtil.DrawStringAtPoint(screen.displayTask.Todo, 0, yPos, DefaultFg, DefaultBg)
yPos++
termboxUtil.DrawStringAtPoint(fmt.Sprintf("Priority: %s", screen.displayTask.Priority), 0, yPos, DefaultFg, DefaultBg)
yPos++
termboxUtil.DrawStringAtPoint(fmt.Sprintf("Projects: %s", screen.displayTask.Projects), 0, yPos, DefaultFg, DefaultBg)
yPos++
termboxUtil.DrawStringAtPoint(fmt.Sprintf("Contexts: %s", screen.displayTask.Contexts), 0, yPos, DefaultFg, DefaultBg)
yPos++
termboxUtil.DrawStringAtPoint("Additional Tags:", 0, yPos, DefaultFg, DefaultBg)
yPos++
for k, v := range screen.displayTask.AdditionalTags {
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s: %s", k, v), 0, yPos, DefaultFg, DefaultBg)
yPos++
}
screen.drawFooter()
}
func (screen *TaskScreen) drawHeader() {
width, _ := termbox.Size()
headerString := screen.displayTask.Todo
spaces := strings.Repeat(" ", ((width-len(headerString))/2)+1)
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, TitleFg, TitleBg)
}
func (screen *TaskScreen) drawFooter() {
if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout {
screen.clearMessage()
}
_, height := termbox.Size()
termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, DefaultFg, DefaultBg)
}
func (screen *TaskScreen) moveCursorDown() bool {
screen.viewPort.cursor++
return true
}
func (screen *TaskScreen) moveCursorUp() bool {
screen.viewPort.cursor--
return true
}
func (screen *TaskScreen) setMessage(msg string) {
screen.message = msg
screen.messageTime = time.Now()
screen.messageTimeout = time.Second * 2
}
/* setMessageWithTimeout lets you specify the timeout for the message
* setting it to -1 means it won't timeout
*/
func (screen *TaskScreen) setMessageWithTimeout(msg string, timeout time.Duration) {
screen.message = msg
screen.messageTime = time.Now()
screen.messageTimeout = timeout
}
func (screen *TaskScreen) clearMessage() {
screen.message = ""
screen.messageTimeout = -1
}

140
task_ops.go Normal file
View File

@ -0,0 +1,140 @@
package main
import (
"fmt"
"strconv"
"strings"
todotxt "github.com/JamesClonk/go-todotxt"
)
func (a *AppState) opListTasks(args []string) int {
var lastIdx int
var filterString string
showAll := len(args) > 0 && args[0] == "-1"
if showAll {
args = args[1:]
}
if len(args) > 0 {
filterString = strings.Join(args, " ")
a.TaskList = a.getFilteredList(filterString)
}
for _, v := range *a.TaskList {
fmt.Println(a.getTaskString(v))
lastIdx++
}
if showAll {
if err := app.LoadDoneList(); err != nil {
fmt.Println("Error loading 'Done' list")
fmt.Println(err.Error())
return 1
}
if filterString != "" {
a.DoneList = a.getFilteredList(filterString)
}
for _, v := range *a.DoneList {
fmt.Println(a.getDoneTaskString(v))
}
}
return 0
}
func (a *AppState) opAddTask(args []string) int {
taskString := strings.Join(args, " ")
if err := a.addTask(taskString); err != nil {
fmt.Println("Error adding task")
fmt.Println(err.Error())
return 1
}
if err := a.WriteList(); err != nil {
fmt.Println("Error saving task list")
fmt.Println(err.Error())
return 1
}
return 0
}
func (a *AppState) opToggleTaskComplete(args []string) int {
if len(args) == 0 {
fmt.Println("No id given")
return 1
}
if len(args) == 0 {
fmt.Println("No id given")
return 1
}
for _, v := range args {
var id int
var err error
if id, err = strconv.Atoi(v); err != nil {
fmt.Printf("Invalid id given: %s\n", v)
return 1
}
if err := a.toggleTaskComplete(id); err != nil {
fmt.Println(err.Error())
return 1
}
}
if a.WriteList() != nil {
fmt.Println("Error saving list")
return 1
}
return 0
}
func (a *AppState) opArchiveTasks(args []string) int {
if err := app.LoadDoneList(); err != nil {
fmt.Println("Error loading 'Done' list")
fmt.Println(err.Error())
return 1
}
if len(args) > 0 {
for _, v := range args {
var id int
var task *todotxt.Task
var err error
if id, err = strconv.Atoi(v); err != nil {
fmt.Printf("Invalid id given: %s\n", v)
return 1
}
if task, err = a.TaskList.GetTask(id); err != nil {
fmt.Printf("Error getting task %d\n", id)
return 1
}
if err = a.archiveTask(id); err != nil {
fmt.Printf("Error archiving task %d\n", id)
return 1
}
fmt.Println(a.getDoneTaskString(*task))
}
} else {
for _, v := range *a.TaskList {
if v.Completed {
if err := a.archiveTask(v.Id); err != nil {
fmt.Printf("Error archiving task %d\n", v.Id)
return 1
}
fmt.Println(a.getDoneTaskString(v))
}
}
}
if a.WriteDoneList() != nil {
fmt.Println("Error saving archive list")
return 1
}
if a.WriteList() != nil {
fmt.Println("Error saving task list")
return 1
}
return 0
}
func (a *AppState) opPrintUsage(args []string) int {
for _, v := range a.ValidOperations {
for _, vv := range v {
fmt.Println(" " + vv)
fmt.Println("")
}
}
return 0
}

56
ui_loop.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"os"
"runtime"
"syscall"
termbox "github.com/nsf/termbox-go"
)
func uiLoop() int {
err := termbox.Init()
if err != nil {
fmt.Println(err.Error())
return 1
}
termbox.SetOutputMode(termbox.Output256)
app.BuildScreens()
displayScreen := app.screens[ScreenMain]
bundle := Bundle{}
bundle.setValue(MainBundleListKey, MainBundleListTodo)
displayScreen.initialize(bundle)
app.layoutAndDrawScreen(displayScreen)
eventChan := make(chan termbox.Event)
go readUserInput(eventChan)
for {
event := <-eventChan
if event.Type == termbox.EventKey {
if event.Key == termbox.KeyCtrlC {
break
} else if event.Key == termbox.KeyCtrlZ {
if runtime.GOOS != "windows" {
process, _ := os.FindProcess(os.Getpid())
termbox.Close()
process.Signal(syscall.SIGSTOP)
termbox.Init()
}
}
newScreenIndex := displayScreen.handleKeyEvent(event)
if newScreenIndex < len(app.screens) {
displayScreen = app.screens[newScreenIndex]
app.layoutAndDrawScreen(displayScreen)
} else {
break
}
}
if event.Type == termbox.EventResize {
app.layoutAndDrawScreen(displayScreen)
}
}
termbox.Close()
// Any wrap up should be done here...
return 0
}