97 lines
2.2 KiB
Go
97 lines
2.2 KiB
Go
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
|
|
}
|