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