v1
This commit is contained in:
parent
017ce196b4
commit
9bc8de8700
207
app_state.go
Normal file
207
app_state.go
Normal 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
28
bundle.go
Normal 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
25
main.go
Normal 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
117
model.go
Normal 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
62
screen.go
Normal 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
30
screen_about.go
Normal 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
380
screen_main.go
Normal 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
122
screen_task.go
Normal 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
140
task_ops.go
Normal 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
56
ui_loop.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user