fish.go 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package cli
  3. import (
  4. "fmt"
  5. "slices"
  6. "strings"
  7. "kitty/tools/cli/markup"
  8. "kitty/tools/utils"
  9. )
  10. var _ = fmt.Print
  11. func fish_completion_script(commands []string) (string, error) {
  12. // One command in fish requires one completion script.
  13. // Usage: kitten __complete__ setup fish [kitty|kitten|clone-in-kitty]
  14. all_commands := map[string]bool{
  15. "kitty": true,
  16. "clone-in-kitty": true,
  17. "kitten": true,
  18. }
  19. if len(commands) == 0 {
  20. commands = append(commands, utils.Keys(all_commands)...)
  21. }
  22. script := strings.Builder{}
  23. script.WriteString(`function __ksi_completions
  24. set --local ct (commandline --current-token)
  25. set --local tokens (commandline --tokenize --cut-at-cursor --current-process)
  26. printf "%s\n" $tokens $ct | command kitten __complete__ fish | source -
  27. end
  28. `)
  29. slices.Sort(commands)
  30. for _, cmd := range commands {
  31. if all_commands[cmd] {
  32. fmt.Fprintf(&script, "complete -f -c %s -a \"(__ksi_completions)\"\n", cmd)
  33. } else if strings.Contains(cmd, "=") {
  34. // Reserved for `setup SHELL [KEY=VALUE ...]`, not used now.
  35. continue
  36. } else {
  37. return "", fmt.Errorf("No fish completion script for command: %s", cmd)
  38. }
  39. }
  40. return script.String(), nil
  41. }
  42. func fish_output_serializer(completions []*Completions, shell_state map[string]string) ([]byte, error) {
  43. output := strings.Builder{}
  44. f := func(format string, args ...any) { fmt.Fprintf(&output, format+"\n", args...) }
  45. n := completions[0].Delegate.NumToRemove
  46. fm := markup.New(false) // fish freaks out if there are escape codes in the description strings
  47. legacy_completion := shell_state["_legacy_completion"]
  48. if legacy_completion == "fish2" {
  49. for _, mg := range completions[0].Groups {
  50. for _, m := range mg.Matches {
  51. f("%s", strings.ReplaceAll(m.Word+"\t"+fm.Prettify(m.Description), "\n", " "))
  52. }
  53. }
  54. } else if n > 0 {
  55. words := make([]string, len(completions[0].AllWords)-n+1)
  56. words[0] = completions[0].Delegate.Command
  57. copy(words[1:], completions[0].AllWords[n:])
  58. for i, w := range words {
  59. words[i] = fmt.Sprintf("(string escape -- %s)", utils.QuoteStringForFish(w))
  60. }
  61. cmdline := strings.Join(words, " ")
  62. f("set __ksi_cmdline " + cmdline)
  63. f("complete -C \"$__ksi_cmdline\"")
  64. f("set --erase __ksi_cmdline")
  65. } else {
  66. for _, mg := range completions[0].Groups {
  67. for _, m := range mg.Matches {
  68. f("echo -- %s", utils.QuoteStringForFish(m.Word+"\t"+fm.Prettify(m.Description)))
  69. }
  70. }
  71. }
  72. // debugf("%#v", output.String())
  73. return []byte(output.String()), nil
  74. }
  75. func init() {
  76. completion_scripts["fish"] = fish_completion_script
  77. input_parsers["fish"] = shell_input_parser
  78. output_serializers["fish"] = fish_output_serializer
  79. }