diff --git a/kitty/constants.py b/kitty/constants.py index e9f6da226..2edcc3a0e 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -29,6 +29,7 @@ is_macos: bool = 'darwin' in _plat is_freebsd: bool = 'freebsd' in _plat is_running_from_develop: bool = False RC_ENCRYPTION_PROTOCOL_VERSION = '1' +website_base_url = 'https://sw.kovidgoyal.net/kitty/' if getattr(sys, 'frozen', False): extensions_dir: str = getattr(sys, 'kitty_run_data')['extensions_dir'] @@ -243,7 +244,7 @@ def read_kitty_resource(name: str, package_name: str = 'kitty') -> bytes: return (files(package_name) / name).read_bytes() -def website_url(doc_name: str = '', website: str = 'https://sw.kovidgoyal.net/kitty/') -> str: +def website_url(doc_name: str = '', website: str = website_base_url) -> str: if doc_name: base, _, frag = doc_name.partition('#') base = base.rstrip('/') diff --git a/tools/cli/infrastructure.go b/tools/cli/infrastructure.go index 17dcd3089..7396da990 100644 --- a/tools/cli/infrastructure.go +++ b/tools/cli/infrastructure.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "os" + "regexp" "strings" "syscall" "unicode" @@ -16,6 +17,8 @@ import ( "kitty" ) +var RootCmd *cobra.Command + type Winsize struct { Rows uint16 Cols uint16 @@ -78,6 +81,12 @@ var title_fmt = color.New(color.FgBlue, color.Bold).SprintFunc() var exe_fmt = color.New(color.FgYellow, color.Bold).SprintFunc() var opt_fmt = color.New(color.FgGreen).SprintFunc() var italic_fmt = color.New(color.Italic).SprintFunc() +var bold_fmt = color.New(color.Bold).SprintFunc() +var code_fmt = color.New(color.FgCyan).SprintFunc() +var cyan_fmt = color.New(color.FgCyan).SprintFunc() +var yellow_fmt = color.New(color.FgYellow).SprintFunc() +var blue_fmt = color.New(color.FgBlue).SprintFunc() +var green_fmt = color.New(color.FgGreen).SprintFunc() func cmd_name(cmd *cobra.Command) string { if cmd.Annotations != nil { @@ -91,22 +100,27 @@ func print_created_by(root *cobra.Command) { fmt.Println(italic_fmt(root.Annotations["exe"]), opt_fmt(root.Version), "created by", title_fmt("Kovid Goyal")) } -func print_with_indent(text string, indent string, screen_width int) { +func print_line_with_indent(text string, indent string, screen_width int) { x := len(indent) fmt.Print(indent) in_sgr := false - current_word := "" + var current_word strings.Builder print_word := func(r rune) { - w := runewidth.StringWidth(current_word) + w := runewidth.StringWidth(current_word.String()) if x+w > screen_width { fmt.Println() fmt.Print(indent) x = len(indent) - current_word = strings.TrimSpace(current_word) + s := strings.TrimSpace(current_word.String()) + current_word.Reset() + current_word.WriteString(s) + } + fmt.Print(current_word.String()) + current_word.Reset() + if r > 0 { + current_word.WriteRune(r) } - fmt.Print(current_word) - current_word = string(r) x += w } @@ -115,27 +129,109 @@ func print_with_indent(text string, indent string, screen_width int) { if r == 'm' { in_sgr = false } + fmt.Print(string(r)) continue } if r == 0x1b { in_sgr = true + if current_word.Len() != 0 { + print_word(0) + } + fmt.Print(string(r)) continue } - if current_word != "" && unicode.IsSpace(r) && r != 0xa0 { + if current_word.Len() != 0 && r != 0xa0 && unicode.IsSpace(r) { print_word(r) } else { - current_word += string(r) + current_word.WriteRune(r) } } - if current_word != "" { - print_word(' ') - fmt.Print(current_word) + if current_word.Len() != 0 { + print_word(0) } if len(text) > 0 { fmt.Println() } } +func ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { + result := "" + lastIndex := 0 + + for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { + groups := []string{} + for i := 0; i < len(v); i += 2 { + if v[i] == -1 || v[i+1] == -1 { + groups = append(groups, "") + } else { + groups = append(groups, str[v[i]:v[i+1]]) + } + } + + result += str[lastIndex:v[0]] + repl(groups) + lastIndex = v[1] + } + + return result + str[lastIndex:] +} + +func website_url(doc string) string { + if doc != "" { + doc = strings.TrimSuffix(doc, "/") + if doc != "" { + doc += "/" + } + } + return kitty.WebsiteBaseUrl + doc +} + +var prettify_pat = regexp.MustCompile(":([a-z]+):`([^`]+)`") +var ref_pat = regexp.MustCompile(`\s*<\S+?>`) + +func prettify(text string) string { + return ReplaceAllStringSubmatchFunc(prettify_pat, text, func(groups []string) string { + val := groups[2] + switch groups[1] { + case "file", "env", "envvar": + return italic_fmt(val) + case "doc": + return website_url(val) + case "ref": + return ref_pat.ReplaceAllString(val, ``) + case "code": + return code_fmt(val) + case "option": + idx := strings.LastIndex(val, "--") + if idx < 0 { + idx = strings.Index(val, "-") + } + if idx > -1 { + val = val[idx:] + } + return bold_fmt(val) + case "yellow": + return yellow_fmt(val) + case "blue": + return blue_fmt(val) + case "green": + return green_fmt(val) + case "cyan": + return cyan_fmt(val) + case "emph": + return italic_fmt(val) + default: + return val + } + + }) +} + +func print_with_indent(text string, indent string, screen_width int) { + for _, line := range strings.Split(prettify(text), "\n") { + print_line_with_indent(line, indent, screen_width) + } +} + func show_usage(cmd *cobra.Command) error { ws, tty_size_err := GetTTYSize() screen_width := 80 @@ -184,14 +280,14 @@ func show_usage(cmd *cobra.Command) error { fmt.Print(" ", defval) } fmt.Println() - if flag.Name == "help" { - fmt.Println(" ", "Print this help message") - fmt.Println() - return - } - for _, line := range strings.Split(flag.Usage, "\n") { - print_with_indent(line, " ", screen_width) + msg := flag.Usage + switch flag.Name { + case "help": + msg = "Print this help message" + case "version": + msg = "Print the version of " + RootCmd.Annotations["exe"] + ": " + italic_fmt(RootCmd.Version) } + print_with_indent(msg, " ", screen_width) if cmd.Annotations["choices-"+flag.Name] != "" { fmt.Println(" Choices:", strings.Join(strings.Split(cmd.Annotations["choices-"+flag.Name], "\000"), ", ")) } @@ -218,6 +314,7 @@ func CreateCommand(cmd *cobra.Command, exe string) *cobra.Command { } func Init(root *cobra.Command) { + RootCmd = root root.Version = kitty.VersionString root.PersistentPreRunE = ValidateChoices root.SetUsageFunc(show_usage) diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index 77630b4b2..27bad9ffc 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -20,12 +20,18 @@ func main() { 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.", Run: func(cmd *cobra.Command, args []string) { - cmd.Usage() - fmt.Fprintln(os.Stderr, color.RedString("\nNo command specified for " + exe_name)) + cmd.Usage() + fmt.Fprintln(os.Stderr, color.RedString("\nNo command specified for "+exe_name)) }, }, exe_name) - cli.PersistentChoices(root, "use-password", "If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to always or never use the supplied password.", "if-available", "always", "never") root.Annotations["options_title"] = "Global options" + + root.PersistentFlags().String("password", "", + "A password to use when contacting kitty. This will cause kitty to ask the user"+ + " for permission to perform the specified action, unless the password has been"+ + " accepted before or is pre-configured in :file:`kitty.conf`.") + + cli.PersistentChoices(root, "use-password", "If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to always or never use the supplied password.", "if-available", "always", "never") cli.Init(root) if err := root.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/version.go b/version.go index 6f6a64649..2bb7e4ad7 100644 --- a/version.go +++ b/version.go @@ -18,9 +18,10 @@ type VersionType struct { var VersionString string var Version VersionType var VCSRevision string +var WebsiteBaseUrl string func init() { - var verpat = regexp.MustCompile(`Version\((\d+),\s*(\d+),\s*(\d+)\)`) + verpat := regexp.MustCompile(`Version\((\d+),\s*(\d+),\s*(\d+)\)`) matches := verpat.FindStringSubmatch(raw) major, err := strconv.Atoi(matches[1]) minor, err := strconv.Atoi(matches[2]) @@ -40,5 +41,11 @@ func init() { } } } + website_pat := regexp.MustCompile(`website_base_url\s+=\s+['"](.+?)['"]`) + matches = website_pat.FindStringSubmatch(raw) + WebsiteBaseUrl = matches[1] + if matches[1] == "" { + panic(fmt.Errorf("Failed to find the website base url")) + } }