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}