package main import ( "fmt" "regexp" "time" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) func (m model) Init() tea.Cmd { // Just return `nil`, which means "no I/O right now, please." return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd if m.addTask { m.textinput, cmd = m.textinput.Update(msg) } switch msg := msg.(type) { // Is it a key press? case tea.KeyMsg: // Cool, what was the actual key pressed? switch msg.String() { // These keys should exit the program. case "ctrl+c", "q": return m, tea.Quit // The "up" and "k" keys move the cursor up case "up", "k": if m.cursor > 0 && !m.addTask { m.cursor-- } // The "down" and "j" keys move the cursor down case "down", "j": if m.cursor < len(m.todos)-1 && !m.addTask { m.cursor++ } case "left", "h": if m.tab > 0 && !m.addTask { m.tab-- } case "right", "l": if m.tab < 4 && !m.addTask { m.tab++ } // new task case "n": if !m.addTask { m.addTask = true m.textinput.Focus() m.textinput.Reset() } case "esc": if m.addTask { m.addTask = false m.textinput.Reset() } case "enter": if m.addTask { AddNewTask(&m) m.addTask = false m.textinput.Reset() } // The "enter" key and the spacebar (a literal space) toggle // the selected state for the item that the cursor is pointing at. case " ": if !m.addTask { if m.todos[m.cursor].done { m.todos[m.cursor].done = false } else { m.todos[m.cursor].done = true } } } } // Return the updated model to the Bubble Tea runtime for processing. // Note that we're not returning a command. return m, cmd } func AddNewTask(m *model) { taskTitle := m.textinput.Value() // parse date in title var doDate int64 = -1 var deadline int64 = -1 reDate := regexp.MustCompile("\\d{1,2}/\\d{1,2}") date := reDate.FindStringSubmatch(taskTitle) if len(date) > 0 { // what the fuck Go date, _ := time.Parse("02/01/2006", "10/03/2025") doDate = date.Unix() } t := todo{ name: taskTitle, done: false, startdate: doDate, deadline: deadline, priority: 1, isInbox: true, } m.todos = append(m.todos, t) } func (m model) View() string { // The header s := "" currentList := []todo{} s += "GOTD\n\n" switch m.tab { case 0: // s += "Inbox" taskFilter := func(t todo) bool { return t.isInbox } currentList = filter(m.todos, taskFilter) case 1: // s += "Today" taskFilter := func(t todo) bool { return t.startdate == midnightToUnix() || t.deadline == midnightToUnix() } currentList = filter(m.todos, taskFilter) case 2: // s += "Tomorrow" // 86400 seconds in 24h, add it on to today's midnight for tomorrow's midnight taskFilter := func(t todo) bool { return t.startdate == midnightToUnix() + 86400 || t.deadline == midnightToUnix() + 86400} currentList = filter(m.todos, taskFilter) case 3: // s += "Scheduled" taskFilter := func(t todo) bool { return t.startdate != -1 } currentList = filter(m.todos, taskFilter) case 4: // s += "Anytime" taskFilter := func(t todo) bool { return t.startdate == -1 } currentList = filter(m.todos, taskFilter) } // Iterate over our choices for i, value := range currentList { // Is the cursor pointing at this choice? cursor := " " // no cursor if m.cursor == i { cursor = ">" // cursor! } // Is this choice selected? checked := " " // not selected // if _, ok := m.selected[i]; ok { // checked = "x" // selected! // } if value.done { checked = "x" } renderedTime := time.Unix(value.startdate, 0) // Render the row s += fmt.Sprintf("%s [%s] %s – %s\n", cursor, checked, value.name, renderedTime) } s += "\n\n" // render tab bar for i, v := range []string{"Inbox", "Today", "Tomorrow", "Scheduled", "Anytime"} { if i == m.tab { s += lipgloss.NewStyle(). Bold(true). Underline(true). PaddingRight(3). Render(v) } else { s += lipgloss.NewStyle(). PaddingRight(3). Render(v) } } // The footer s += "\nn: new" if m.addTask { s += ": " s += m.textinput.View() } s += "\nq: quit.\n" // Send the UI for rendering return s }