Allow customizing companion commands
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user