package main // Testing out tea; really, git and ssh import ( "fmt" "io/fs" "log" "os" "os/exec" "strings" "time" tea "github.com/charmbracelet/bubbletea" ) type commandChoice struct { displayString string command *exec.Cmd } type choices []commandChoice type model struct { cursor int choices choices selected map[int]struct{} status string } type statusMsg string var selection map[int]struct{} func getBrowserSessions() []commandChoice { userConfigDir, err := os.UserConfigDir() if err != nil { log.Printf("Error finding user configuration directory: %v", err) return []commandChoice{} } log.Printf("userConfigDir: %+v", userConfigDir) fileSystem := os.DirFS(userConfigDir) log.Printf("fileSystem: %+v", fileSystem) fileList, err := fs.ReadDir(fileSystem, "local/share/qutebrowser/sessions") if err != nil { log.Printf("Error reading browser sessions directory: %v", err) return []commandChoice{} } result := make([]commandChoice, 0) for _, entry := range fileList { if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".yml") { log.Printf("%s", entry.Name()) result = append(result, commandChoice{ displayString: fmt.Sprintf("Qutebrowser session: %s", strings.TrimSuffix(entry.Name(), ".yml")), command: exec.Command("xterm", "-e", "qutebrowser", "--target", "window", "--restore", strings.TrimSuffix(entry.Name(), ".yml")), }) } } return result } func initialModel() model { return model{ cursor: 0, choices: append( getBrowserSessions(), commandChoice{ displayString: "bottom", command: exec.Command("xterm", "-maximized", "-e", "btm", "--battery", "--color", "gruvbox-light"), }, commandChoice{ displayString: "broot", command: exec.Command("xterm", "-maximized", "-e", "broot"), }, commandChoice{ displayString: "joplin", command: exec.Command("xterm", "-maximized", "-e", "joplin"), }, commandChoice{ displayString: "neomutt", command: exec.Command("xterm", "-maximized", "-e", "neomutt"), }, commandChoice{ displayString: "newsboat", command: exec.Command("xterm", "-maximized", "-e", "newsboat"), }, commandChoice{ displayString: "w3m", command: exec.Command("xterm", "-maximized", "-e", "w3m", "https://languagehat.com/"), }, ), selected: make(map[int]struct{}), } } func launch(m model) tea.Cmd { return func() tea.Msg { var result statusMsg for q, item := range m.choices { if _, ok := m.selected[q]; ok { log.Printf("Launching: %v\n", item.displayString) result += statusMsg(fmt.Sprintf("Launching command: %v\n", item.displayString)) err := item.command.Start() if err != nil { result += statusMsg(fmt.Sprintf("%v\n", err)) log.Fatalf("Error launching: %v\n", err) } } } time.Sleep(2000 * time.Millisecond) return result } } func (m model) Init() tea.Cmd { return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case statusMsg: m.status = string(msg) return m, tea.Quit case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "up", "k": if m.cursor > 0 { m.cursor-- } case "down", "j": if m.cursor < len(m.choices)-1 { m.cursor++ } case " ": _, ok := m.selected[m.cursor] if ok { delete(m.selected, m.cursor) } else { m.selected[m.cursor] = struct{}{} } case "enter": return m, launch(m) } } return m, nil } func (m model) View() string { s := "\nLet's get started!\n\n" for i, choice := range m.choices { cursor := " " if m.cursor == i { cursor = ">" } checked := " " if _, ok := m.selected[i]; ok { checked = "x" } s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice.displayString) } s += "\n\n" s += fmt.Sprintf("%s\n", m.status) s += "Press enter to launch.\n" s += "Press q to quit.\n" return s } func main() { logfilePath := os.Getenv("BREAKFAST_LOG") if logfilePath == "" { logfilePath = fmt.Sprintf("/tmp/%s_breakfast.log", time.Now().Local().Format("20060102150405-0700")) } if _, err := tea.LogToFile(logfilePath, "DEBUG"); err != nil { log.Fatal(err) } p := tea.NewProgram(initialModel()) if err := p.Start(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) os.Exit(1) } }