package main import ( "errors" "fmt" "os/exec" "runtime" "strings" ) // commandDefaults holds the per-OS open-command templates used when the user // hasn't overridden them in the config. type commandDefaults struct { OpenFile string OpenFolder string } func defaultCommands() commandDefaults { if runtime.GOOS == "windows" { return commandDefaults{ OpenFile: `cmd /c start "" "{path}"`, OpenFolder: `explorer.exe "{path}"`, } } return commandDefaults{ OpenFile: `xdg-open "{path}"`, OpenFolder: `xdg-open "{path}"`, } } // resolveOpenCommand returns the user-configured command if non-blank, else // the platform default. func resolveOpenCommand(configured, fallback string) string { if strings.TrimSpace(configured) != "" { return configured } return fallback } // runOpenCommand tokenizes template, substitutes {path} with the resolved // path (appending it if the placeholder is missing), and starts the command. func runOpenCommand(template, path string) error { tokens, err := tokenizeCommand(template) if err != nil { return fmt.Errorf("parse command: %w", err) } if len(tokens) == 0 { return errors.New("command is empty") } sawPath := false for i, t := range tokens { if strings.Contains(t, "{path}") { tokens[i] = strings.ReplaceAll(t, "{path}", path) sawPath = true } } if !sawPath { tokens = append(tokens, path) } return exec.Command(tokens[0], tokens[1:]...).Start() } // tokenizeCommand splits a command-line string into argv tokens, honouring // double-quoted segments. An empty pair "" yields an empty argument — needed // for Windows `cmd /c start "" file`, where the empty quotes are the title. func tokenizeCommand(s string) ([]string, error) { var tokens []string var cur strings.Builder inQuote := false inToken := false for i := 0; i < len(s); i++ { c := s[i] if c == '"' { inQuote = !inQuote inToken = true continue } if !inQuote && (c == ' ' || c == '\t') { if inToken { tokens = append(tokens, cur.String()) cur.Reset() inToken = false } continue } cur.WriteByte(c) inToken = true } if inQuote { return nil, errors.New("unclosed quote") } if inToken { tokens = append(tokens, cur.String()) } return tokens, nil }