diff --git a/kitty/cli.py b/kitty/cli.py index cd1a4e969..5c642b72c 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 # License: GPL v3 Copyright: 2017, Kovid Goyal +import os import re import shlex +import socket import sys from collections import deque from typing import ( @@ -13,7 +15,8 @@ from typing import ( from .cli_stub import CLIOptions from .conf.utils import resolve_config from .constants import ( - appname, clear_handled_signals, defconf, is_macos, str_version, website_url + appname, clear_handled_signals, config_dir, defconf, is_macos, str_version, + website_url ) from .fast_data_types import wcswidth from .options.types import Options as KittyOpts @@ -161,8 +164,17 @@ def hyperlink_for_url(url: str, text: str) -> str: return text +def hyperlink_for_path(path: str, text: str) -> str: + path = os.path.abspath(path).replace(os.sep, "/") + if os.path.isdir(path): + path += path.rstrip("/") + "/" + return hyperlink_for_url(f'file://{socket.gethostname()}{path}', text) + + @role def file(x: str) -> str: + if x == 'kitty.conf': + x = hyperlink_for_path(os.path.join(config_dir, x), x) return italic(x) diff --git a/tools/cli/infrastructure.go b/tools/cli/infrastructure.go index a8834f5eb..41fe9f7c3 100644 --- a/tools/cli/infrastructure.go +++ b/tools/cli/infrastructure.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "os" + "path/filepath" "regexp" "strings" "syscall" @@ -15,6 +16,7 @@ import ( "github.com/spf13/pflag" "kitty" + "kitty/tools/utils" ) var RootCmd *cobra.Command @@ -180,11 +182,37 @@ func website_url(doc string) string { var prettify_pat = regexp.MustCompile(":([a-z]+):`([^`]+)`") var ref_pat = regexp.MustCompile(`\s*<\S+?>`) +func is_atty() bool { + return italic_fmt("") != "" +} + +func hyperlink_for_path(path string, text string) string { + if !is_atty() { + return text + } + path = strings.ReplaceAll(utils.Abspath(path), string(os.PathSeparator), "/") + fi, err := os.Stat(path) + if err == nil && fi.IsDir() { + path = strings.TrimSuffix(path, "/") + "/" + } + host, err := os.Hostname() + if err != nil { + host = "" + } + return "\x1b]8;;file://" + host + path + "\x1b\\" + text + "\x1b]8;;\x1b\\" +} + func prettify(text string) string { return ReplaceAllStringSubmatchFunc(prettify_pat, text, func(groups []string) string { val := groups[2] switch groups[1] { - case "file", "env", "envvar": + case "file": + if val == "kitty.conf" && is_atty() { + path := filepath.Join(utils.ConfigDir(), val) + val = hyperlink_for_path(path, val) + } + return italic_fmt(val) + case "env", "envvar": return italic_fmt(val) case "doc": return website_url(val) @@ -233,7 +261,7 @@ func show_usage(cmd *cobra.Command) error { use := cmd.Use idx := strings.Index(use, " ") if idx > -1 { - use = use[idx + 1:] + use = use[idx+1:] } var parent_names []string cmd.VisitParents(func(p *cobra.Command) { @@ -274,7 +302,7 @@ func show_usage(cmd *cobra.Command) error { defval := "" switch flag.Value.Type() { default: - if (flag.DefValue != "") { + if flag.DefValue != "" { defval = fmt.Sprintf("[=%s]", italic_fmt(flag.DefValue)) } case "bool": diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index 1a9242903..ae7027ad5 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -1,7 +1,6 @@ package at import ( - "github.com/spf13/cobra" "kitty/tools/cli" @@ -14,7 +13,7 @@ func EntryPoint(tool_root *cobra.Command) *cobra.Command { var root = cli.CreateCommand(&cobra.Command{ Use: "@ [global options] command [command options] [command args]", Short: "Control kitty remotely", - Long: "Control kitty by sending it commands. Set the allow_remote_control option in kitty.conf or use a password, for this to work.", + Long: "Control kitty by sending it commands. Set the allow_remote_control option in :file:`kitty.conf` or use a password, for this to work.", }) root.Annotations["options_title"] = "Global options" diff --git a/tools/utils/paths.go b/tools/utils/paths.go new file mode 100644 index 000000000..240756ddd --- /dev/null +++ b/tools/utils/paths.go @@ -0,0 +1,87 @@ +package utils + +import ( + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +func Expanduser(path string) string { + if !strings.HasPrefix(path, "~") { + return path + } + home, err := os.UserHomeDir() + if err != nil { + usr, err := user.Current() + if err == nil { + home = usr.HomeDir + } + } + if err != nil || home == "" { + return path + } + if path == "~" { + return home + } + path = strings.ReplaceAll(path, string(os.PathSeparator), "/") + parts := strings.Split(path, "/") + if parts[0] == "~" { + parts[0] = home + } else { + uname := parts[0][1:] + if uname != "" { + u, err := user.Lookup(uname) + if err == nil && u.HomeDir != "" { + parts[0] = u.HomeDir + } + } + } + return strings.Join(parts, string(os.PathSeparator)) +} + +func Abspath(path string) string { + q, err := filepath.Abs(path) + if err == nil { + return q + } + return path +} + +var config_dir string + +func ConfigDir() string { + if config_dir != "" { + return config_dir + } + if os.Getenv("KITTY_CONFIG_DIRECTORY") != "" { + config_dir = Abspath(Expanduser(os.Getenv("KITTY_CONFIG_DIRECTORY"))) + } else { + var locations []string + if os.Getenv("XDG_CONFIG_HOME") != "" { + locations = append(locations, os.Getenv("XDG_CACHE_HOME")) + } + locations = append(locations, Expanduser("~/.config")) + if runtime.GOOS == "darwin" { + locations = append(locations, Expanduser("~/Library/Preferences")) + } + for _, loc := range locations { + if loc != "" { + q := filepath.Join(loc, "kitty") + if _, err := os.Stat(filepath.Join(q, "kitty.conf")); err == nil { + config_dir = q + break + } + } + } + for _, loc := range locations { + if loc != "" { + config_dir = filepath.Join(loc, "kitty") + break + } + } + } + + return config_dir +}