|
|
|
@ -17,35 +17,31 @@ const ( |
|
|
|
|
Commands |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type sessionOrCommand struct { |
|
|
|
|
type commandChoice struct { |
|
|
|
|
displayString string |
|
|
|
|
command *exec.Cmd |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Choices [][]sessionOrCommand |
|
|
|
|
|
|
|
|
|
type SelectionSet map[int]struct{} |
|
|
|
|
type Choices []commandChoice |
|
|
|
|
|
|
|
|
|
type model struct { |
|
|
|
|
activeSection int |
|
|
|
|
cursor []int |
|
|
|
|
choices Choices |
|
|
|
|
selected []SelectionSet |
|
|
|
|
status string |
|
|
|
|
cursor int |
|
|
|
|
choices Choices |
|
|
|
|
selected map[int]struct{} |
|
|
|
|
status string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type statusMsg string |
|
|
|
|
|
|
|
|
|
var browserSelection SelectionSet |
|
|
|
|
var commandSelection SelectionSet |
|
|
|
|
var selection map[int]struct{} |
|
|
|
|
|
|
|
|
|
func getBrowserSessions() []sessionOrCommand { |
|
|
|
|
func getBrowserSessions() []commandChoice { |
|
|
|
|
// 1. List files in $XDG_DATA_HOME/qutebrowser/sessions/ (N.B.:
|
|
|
|
|
// UserConfigDir() in os)
|
|
|
|
|
userConfigDir, err := os.UserConfigDir() |
|
|
|
|
if err != nil { |
|
|
|
|
log.Printf("Error finding user configuration directory: %v", err) |
|
|
|
|
return []sessionOrCommand{} |
|
|
|
|
return []commandChoice{} |
|
|
|
|
} |
|
|
|
|
log.Printf("INFO userConfigDir: %+v", userConfigDir) |
|
|
|
|
fileSystem := os.DirFS(userConfigDir) |
|
|
|
@ -53,17 +49,17 @@ func getBrowserSessions() []sessionOrCommand { |
|
|
|
|
fileList, err := fs.ReadDir(fileSystem, "local/share/qutebrowser/sessions") |
|
|
|
|
if err != nil { |
|
|
|
|
log.Printf("Error reading browser sessions directory: %v", err) |
|
|
|
|
return []sessionOrCommand{} |
|
|
|
|
return []commandChoice{} |
|
|
|
|
} |
|
|
|
|
// 2. Exclude non-YAML files
|
|
|
|
|
// 3. Wrangle them into this struct array
|
|
|
|
|
result := make([]sessionOrCommand, 0) |
|
|
|
|
result := make([]commandChoice, 0) |
|
|
|
|
for _, entry := range fileList { |
|
|
|
|
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".yml") { |
|
|
|
|
log.Printf("INFO %s", entry.Name()) |
|
|
|
|
result = append(result, sessionOrCommand{ |
|
|
|
|
displayString: strings.TrimSuffix(entry.Name(), ".yml"), |
|
|
|
|
command: exec.Command("qutebrowser", "--restore", strings.TrimSuffix(entry.Name(), ".yml")), |
|
|
|
|
result = append(result, commandChoice{ |
|
|
|
|
displayString: fmt.Sprintf("Qutebrowser session: %s", strings.TrimSuffix(entry.Name(), ".yml")), |
|
|
|
|
command: exec.Command("qutebrowser", "--target", "window", "--restore", strings.TrimSuffix(entry.Name(), ".yml")), |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -72,51 +68,54 @@ func getBrowserSessions() []sessionOrCommand { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func initialModel() model { |
|
|
|
|
browserSelection = make(SelectionSet) |
|
|
|
|
commandSelection = make(SelectionSet) |
|
|
|
|
return model{ |
|
|
|
|
activeSection: BrowserSessions, |
|
|
|
|
cursor: []int{0, 0}, |
|
|
|
|
choices: Choices{ |
|
|
|
|
cursor: 0, |
|
|
|
|
choices: append( |
|
|
|
|
getBrowserSessions(), |
|
|
|
|
[]sessionOrCommand{{ |
|
|
|
|
commandChoice{ |
|
|
|
|
displayString: "bottom", |
|
|
|
|
command: exec.Command("xterm", "-maximized", "-e", "btm", "--group", "--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"), |
|
|
|
|
}}, |
|
|
|
|
}, |
|
|
|
|
// An array of maps which indicates which choices are selected.
|
|
|
|
|
// We're using the map like a mathematical set. The keys refer to
|
|
|
|
|
// the indexes of the `choices` slice, above.
|
|
|
|
|
selected: []SelectionSet{browserSelection, commandSelection}, |
|
|
|
|
}, |
|
|
|
|
), |
|
|
|
|
selected: make(map[int]struct{}), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func launch(m model) tea.Cmd { |
|
|
|
|
return func() tea.Msg { |
|
|
|
|
var result statusMsg |
|
|
|
|
for p := 0; p < 2; p++ { |
|
|
|
|
for q, item := range m.choices[p] { |
|
|
|
|
if _, ok := m.selected[p][q]; ok { |
|
|
|
|
log.Printf("INFO launching: %v\n", item.displayString) |
|
|
|
|
result += statusMsg(fmt.Sprintf("Launching command: %v\n", item.displayString)) |
|
|
|
|
err := item.command.Start() |
|
|
|
|
for q, item := range m.choices { |
|
|
|
|
if _, ok := m.selected[q]; ok { |
|
|
|
|
log.Printf("INFO 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) |
|
|
|
|
} |
|
|
|
|
/* |
|
|
|
|
err = item.command.Wait() |
|
|
|
|
if err != nil { |
|
|
|
|
result += statusMsg(fmt.Sprintf("%v\n", err)) |
|
|
|
|
log.Fatalf("Error launching: %v\n", err) |
|
|
|
|
log.Fatalf("Error during wait: %v\n", err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -137,26 +136,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
|
|
|
|
switch msg.String() { |
|
|
|
|
case "ctrl+c", "q": |
|
|
|
|
return m, tea.Quit |
|
|
|
|
case "tab": |
|
|
|
|
if m.activeSection == BrowserSessions { |
|
|
|
|
m.activeSection = Commands |
|
|
|
|
} else { |
|
|
|
|
m.activeSection = BrowserSessions |
|
|
|
|
} |
|
|
|
|
case "up", "k": |
|
|
|
|
if m.cursor[m.activeSection] > 0 { |
|
|
|
|
m.cursor[m.activeSection]-- |
|
|
|
|
if m.cursor > 0 { |
|
|
|
|
m.cursor-- |
|
|
|
|
} |
|
|
|
|
case "down", "j": |
|
|
|
|
if m.cursor[m.activeSection] < len(m.choices[m.activeSection])-1 { |
|
|
|
|
m.cursor[m.activeSection]++ |
|
|
|
|
if m.cursor < len(m.choices)-1 { |
|
|
|
|
m.cursor++ |
|
|
|
|
} |
|
|
|
|
case " ": |
|
|
|
|
_, ok := m.selected[m.activeSection][m.cursor[m.activeSection]] |
|
|
|
|
_, ok := m.selected[m.cursor] |
|
|
|
|
if ok { |
|
|
|
|
delete(m.selected[m.activeSection], m.cursor[m.activeSection]) |
|
|
|
|
delete(m.selected, m.cursor) |
|
|
|
|
} else { |
|
|
|
|
m.selected[m.activeSection][m.cursor[m.activeSection]] = struct{}{} |
|
|
|
|
m.selected[m.cursor] = struct{}{} |
|
|
|
|
} |
|
|
|
|
case "enter": |
|
|
|
|
return m, launch(m) |
|
|
|
@ -167,24 +160,22 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m model) View() string { |
|
|
|
|
s := "Let's get started!\n\n" |
|
|
|
|
s := "\nLet's get started!\n\n" |
|
|
|
|
|
|
|
|
|
for j := 0; j < 2; j++ { |
|
|
|
|
for i, choice := range m.choices[j] { |
|
|
|
|
cursor := " " |
|
|
|
|
if m.cursor[j] == i { |
|
|
|
|
cursor = ">" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
checked := " " |
|
|
|
|
if _, ok := m.selected[j][i]; ok { |
|
|
|
|
checked = "x" |
|
|
|
|
} |
|
|
|
|
for i, choice := range m.choices { |
|
|
|
|
cursor := " " |
|
|
|
|
if m.cursor == i { |
|
|
|
|
cursor = ">" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice.displayString) |
|
|
|
|
checked := " " |
|
|
|
|
if _, ok := m.selected[i]; ok { |
|
|
|
|
checked = "x" |
|
|
|
|
} |
|
|
|
|
s += "\n\n" |
|
|
|
|
|
|
|
|
|
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice.displayString) |
|
|
|
|
} |
|
|
|
|
s += "\n\n" |
|
|
|
|
|
|
|
|
|
// s += fmt.Sprintf("\n%+v", m.selected) // debug
|
|
|
|
|
s += fmt.Sprintf("%s\n", m.status) |
|
|
|
|