159 lines
3.9 KiB
Go
159 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
func requireRofi() error {
|
|
if _, err := exec.LookPath("rofi"); err != nil {
|
|
return fmt.Errorf("rofi is required for interactive search")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rofiPrompt(prompt string) (string, bool, error) {
|
|
cmd := exec.Command("rofi", "-dmenu", "-p", prompt)
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
|
return "", false, nil
|
|
}
|
|
return "", false, fmt.Errorf("rofi prompt failed: %w", err)
|
|
}
|
|
selection := strings.TrimSpace(out.String())
|
|
if selection == "" {
|
|
return "", false, nil
|
|
}
|
|
return selection, true, nil
|
|
}
|
|
|
|
func rofiSelect(prompt string, options []string) (string, bool, error) {
|
|
cmd := exec.Command("rofi", "-dmenu", "-p", prompt)
|
|
cmd.Stdin = strings.NewReader(strings.Join(options, "\n"))
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
|
return "", false, nil
|
|
}
|
|
return "", false, fmt.Errorf("rofi selection failed: %w", err)
|
|
}
|
|
selection := strings.TrimSpace(out.String())
|
|
if selection == "" {
|
|
return "", false, nil
|
|
}
|
|
return selection, true, nil
|
|
}
|
|
|
|
func interactiveSearch(ctx context.Context, cache *Cache, userAgent string) (Location, error) {
|
|
recent, err := cache.LoadRecentLocations()
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
|
|
selection, ok, err := rofiPrompt("Search")
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
if !ok {
|
|
return Location{}, fmt.Errorf("interactive search cancelled")
|
|
}
|
|
|
|
query := selection
|
|
matches := matchRecentLocations(recent, query)
|
|
if len(matches) > 0 {
|
|
choiceOptions := make([]string, 0, len(matches)+1)
|
|
for _, loc := range matches {
|
|
choiceOptions = append(choiceOptions, fmt.Sprintf("Use saved %s", loc.LabelFull()))
|
|
}
|
|
searchOption := fmt.Sprintf("Search for %s", query)
|
|
choiceOptions = append(choiceOptions, searchOption)
|
|
|
|
choice, ok, err := rofiSelect("Use saved or search", choiceOptions)
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
if !ok {
|
|
return Location{}, fmt.Errorf("interactive search cancelled")
|
|
}
|
|
if choice != searchOption {
|
|
for _, loc := range matches {
|
|
if choice == fmt.Sprintf("Use saved %s", loc.LabelFull()) {
|
|
if err := cache.SaveLocation(loc.LabelFull(), loc); err != nil {
|
|
return Location{}, err
|
|
}
|
|
return loc, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println("Searching for location...")
|
|
results, err := SearchLocations(ctx, query, userAgent)
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
loc, err := SelectLocationRofi(results)
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
if err := cache.SaveLocation(query, loc); err != nil {
|
|
return Location{}, err
|
|
}
|
|
return loc, nil
|
|
}
|
|
|
|
func SelectLocationRofi(results []Location) (Location, error) {
|
|
switch len(results) {
|
|
case 0:
|
|
return Location{}, fmt.Errorf("no locations found")
|
|
case 1:
|
|
return results[0], nil
|
|
}
|
|
|
|
options := make([]string, 0, len(results))
|
|
for _, loc := range results {
|
|
label := loc.LabelFull()
|
|
if loc.Country != "" && !strings.Contains(label, loc.Country) {
|
|
label = fmt.Sprintf("%s, %s", label, loc.Country)
|
|
}
|
|
options = append(options, label)
|
|
}
|
|
|
|
selection, ok, err := rofiSelect("Select", options)
|
|
if err != nil {
|
|
return Location{}, err
|
|
}
|
|
if !ok {
|
|
return Location{}, fmt.Errorf("interactive selection cancelled")
|
|
}
|
|
|
|
for i, option := range options {
|
|
if option == selection {
|
|
return results[i], nil
|
|
}
|
|
}
|
|
|
|
return Location{}, fmt.Errorf("invalid selection")
|
|
}
|
|
|
|
func matchRecentLocations(recent []Location, query string) []Location {
|
|
query = strings.TrimSpace(strings.ToLower(query))
|
|
if query == "" {
|
|
return nil
|
|
}
|
|
matches := make([]Location, 0, len(recent))
|
|
for _, loc := range recent {
|
|
label := strings.ToLower(loc.LabelFull())
|
|
if strings.Contains(label, query) {
|
|
matches = append(matches, loc)
|
|
}
|
|
}
|
|
return matches
|
|
}
|