all repos — todo.txt-go @ b258016622cf9ea6bcb18f4850b601b16dd54185

CLI tool for todo.txt files written in Go

main.go (view raw)

  1package main
  2
  3import (
  4	"fmt"
  5	"log"
  6	"os"
  7
  8	todo "github.com/1set/todotxt"
  9	"github.com/charmbracelet/bubbles/textinput"
 10	tea "github.com/charmbracelet/bubbletea"
 11)
 12
 13type InterfaceState int
 14
 15const (
 16	List InterfaceState = iota
 17	Add
 18)
 19
 20type model struct {
 21	interfaceState InterfaceState
 22
 23	tasks    todo.TaskList
 24	cursor   int              // which to-do list item our cursor is pointing at
 25	selected map[int]struct{} // which to-do items are selected
 26
 27	textInput textinput.Model
 28}
 29
 30func initialModel() model {
 31
 32	ti := textinput.New()
 33	ti.Focus()
 34	ti.CharLimit = 200
 35	ti.Width = 200
 36
 37	if tasklist, err := todo.LoadFromPath("todo.txt"); err != nil {
 38		log.Fatal(err)
 39		// TODO - Handle error, create file if not exists
 40		return model{
 41			textInput: ti,
 42		}
 43	} else {
 44		// tasks := tasklist.Filter(todo.FilterNotCompleted)
 45		tasks := tasklist
 46		_ = tasks.Sort(todo.SortPriorityAsc, todo.SortProjectAsc)
 47		selected := make(map[int]struct{})
 48
 49		for i, t := range tasks {
 50			if t.Completed {
 51				selected[i] = struct{}{}
 52			}
 53		}
 54
 55		return model{
 56			tasks:     tasks,
 57			selected:  selected,
 58			textInput: ti,
 59		}
 60
 61	}
 62
 63}
 64
 65func (m model) Init() tea.Cmd {
 66	return textinput.Blink
 67}
 68
 69func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 70	var cmd tea.Cmd
 71
 72	switch msg := msg.(type) {
 73
 74	// Is it a key press?
 75	case tea.KeyMsg:
 76
 77		switch m.interfaceState {
 78
 79		case List:
 80
 81			switch msg.String() {
 82
 83			// These keys should exit the program.
 84			case "ctrl+c", "q":
 85				m.tasks.WriteToPath("todo.txt")
 86				return m, tea.Quit
 87
 88			// Set focused task priority
 89			case "A":
 90				m.tasks[m.cursor].Priority = "A"
 91			case "B":
 92				m.tasks[m.cursor].Priority = "B"
 93			case "C":
 94				m.tasks[m.cursor].Priority = "C"
 95
 96			// Remove completed tasks
 97			case "u":
 98				m.tasks = m.tasks.Filter(todo.FilterNotCompleted)
 99				m.selected = make(map[int]struct{})
100				for i, t := range m.tasks {
101					if t.Completed {
102						m.selected[i] = struct{}{}
103					}
104				}
105
106			// The "up" and "k" keys move the cursor up
107			case "up", "k":
108				if m.cursor > 0 {
109					m.cursor--
110				}
111
112			// The "down" and "j" keys move the cursor down
113			case "down", "j":
114				if m.cursor < len(m.tasks) {
115					m.cursor++
116				}
117
118			// The "enter" key and the spacebar (a literal space) toggle
119			// the selected state for the item that the cursor is pointing at.
120			case "enter", " ":
121				_, ok := m.selected[m.cursor]
122				if ok {
123					delete(m.selected, m.cursor)
124					m.tasks[m.cursor].Reopen()
125				} else {
126					m.selected[m.cursor] = struct{}{}
127					m.tasks[m.cursor].Complete()
128				}
129
130				// Switch interface to add task interface
131			case "a":
132				m.interfaceState = Add
133
134			}
135
136			return m, nil
137
138		case Add:
139			switch msg.Type {
140			case tea.KeyCtrlC, tea.KeyEsc:
141				return m, tea.Quit
142			case tea.KeyEnter:
143				newTask, _ := todo.ParseTask(m.textInput.Value())
144				m.tasks.AddTask(newTask)
145				m.textInput.Reset()
146				m.interfaceState = List
147			}
148
149			m.textInput, cmd = m.textInput.Update(msg)
150
151			return m, cmd
152
153		}
154	}
155
156	// Return the updated model to the Bubble Tea runtime for processing.
157	// Note that we're not returning a command.
158	return m, nil
159}
160
161func (m model) View() string {
162	output := "My tasks list\n\n"
163
164	switch m.interfaceState {
165
166	case List:
167
168		// Iterate over our tasks
169		for i, task := range m.tasks {
170
171			cursor := " "
172			if m.cursor == i {
173				cursor = ">"
174			}
175
176			// Is this choice selected?
177			checked := " " // not selected
178			if _, ok := m.selected[i]; ok {
179				checked = "x" // selected!
180			}
181
182			// Render the row
183			styles := NewTextStyle()
184			priorityStyle := getPriorityStyle(styles, task.Priority)
185			output += fmt.Sprintf("%s [%s] %s %s\n", cursor, checked, priorityStyle.Render(task.Priority), task.Todo)
186
187		}
188
189		output += "\nPress q to quit.\n"
190	case Add:
191		output += fmt.Sprintf("Nouvelle tâche:\n\n%s", m.textInput.View())
192		output += "\n\n Press ESC to quit.\n"
193	}
194
195	return output
196
197}
198
199func main() {
200	p := tea.NewProgram(initialModel())
201	if _, err := p.Run(); err != nil {
202		fmt.Printf("Alas, there's been an error: %v", err)
203		os.Exit(1)
204	}
205}