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.Filter(todo.FilterNotCompleted)
53 tasks := tasklist
54 _ = tasks.Sort(todo.SortPriorityAsc, todo.SortProjectAsc)
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) {
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.Clean):
123 m.tasks = m.tasks.Filter(todo.FilterNotCompleted)
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 // Interface
132 case key.Matches(msg, m.keys.Add):
133 m.interfaceState = Add
134
135 case key.Matches(msg, m.keys.Quit):
136 m.tasks.WriteToPath("todo.txt")
137 return m, tea.Quit
138
139 case key.Matches(msg, m.keys.Help):
140 m.help.ShowAll = !m.help.ShowAll
141
142 }
143
144 return m, nil
145
146 case Add:
147 switch msg.Type {
148 case tea.KeyCtrlC, tea.KeyEsc:
149 return m, tea.Quit
150 case tea.KeyEnter:
151 inputValue := m.textInput.Value()
152 if inputValue != "" {
153 newTask, _ := todo.ParseTask(inputValue)
154 m.tasks.AddTask(newTask)
155 }
156 m.textInput.Reset()
157 m.interfaceState = List
158 }
159
160 m.textInput, cmd = m.textInput.Update(msg)
161
162 return m, cmd
163
164 }
165 }
166
167 // Return the updated model to the Bubble Tea runtime for processing.
168 // Note that we're not returning a command.
169 return m, nil
170}
171
172func (m model) View() string {
173 output := "\n"
174
175 switch m.interfaceState {
176
177 case List:
178
179 // Iterate over our tasks
180 for i, task := range m.tasks {
181
182 cursor := " "
183 if m.cursor == i {
184 cursor = ">"
185 }
186
187 // Is this choice selected?
188 checked := " " // not selected
189 if _, ok := m.selected[i]; ok {
190 checked = "x" // selected!
191 }
192
193 // Render the row
194 styles := NewTextStyle()
195 priorityStyle := getPriorityStyle(styles, task.Priority)
196 output += fmt.Sprintf("%s [%s] %s %s\n", cursor, checked, priorityStyle.Render(task.Priority), task.Todo)
197 }
198
199 output += "\n"
200 output += m.help.View(m.keys)
201 case Add:
202 output += fmt.Sprintf("Nouvelle tâche:\n\n%s", m.textInput.View())
203 output += "\n\n Press ESC to quit.\n"
204 }
205
206 return output
207
208}
209
210func main() {
211 p := tea.NewProgram(initialModel())
212 if _, err := p.Run(); err != nil {
213 fmt.Printf("Alas, there's been an error: %v", err)
214 os.Exit(1)
215 }
216}