main.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package diff
  3. import (
  4. "archive/tar"
  5. "bytes"
  6. "fmt"
  7. "io/fs"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "strings"
  12. "kitty/kittens/ssh"
  13. "kitty/tools/cli"
  14. "kitty/tools/config"
  15. "kitty/tools/tui/loop"
  16. "kitty/tools/utils"
  17. )
  18. var _ = fmt.Print
  19. func load_config(opts *Options) (ans *Config, err error) {
  20. ans = NewConfig()
  21. p := config.ConfigParser{LineHandler: ans.Parse}
  22. err = p.LoadConfig("diff.conf", opts.Config, opts.Override)
  23. if err != nil {
  24. return nil, err
  25. }
  26. ans.KeyboardShortcuts = config.ResolveShortcuts(ans.KeyboardShortcuts)
  27. return ans, nil
  28. }
  29. var conf *Config
  30. var opts *Options
  31. var lp *loop.Loop
  32. func isdir(path string) bool {
  33. if s, err := os.Stat(path); err == nil {
  34. return s.IsDir()
  35. }
  36. return false
  37. }
  38. func exists(path string) bool {
  39. _, err := os.Stat(path)
  40. return err == nil
  41. }
  42. func get_ssh_file(hostname, rpath string) (string, error) {
  43. tdir, err := os.MkdirTemp("", "*-"+hostname)
  44. if err != nil {
  45. return "", err
  46. }
  47. add_remote_dir(tdir)
  48. is_abs := strings.HasPrefix(rpath, "/")
  49. for strings.HasPrefix(rpath, "/") {
  50. rpath = rpath[1:]
  51. }
  52. cmd := []string{ssh.SSHExe(), hostname, "tar", "--dereference", "--create", "--file", "-"}
  53. if is_abs {
  54. cmd = append(cmd, "-C", "/")
  55. }
  56. cmd = append(cmd, rpath)
  57. c := exec.Command(cmd[0], cmd[1:]...)
  58. c.Stdin, c.Stderr = os.Stdin, os.Stderr
  59. stdout, err := c.Output()
  60. if err != nil {
  61. return "", fmt.Errorf("Failed to ssh into remote host %s to get file %s with error: %w", hostname, rpath, err)
  62. }
  63. tf := tar.NewReader(bytes.NewReader(stdout))
  64. count, err := utils.ExtractAllFromTar(tf, tdir)
  65. if err != nil {
  66. return "", fmt.Errorf("Failed to untar data from remote host %s to get file %s with error: %w", hostname, rpath, err)
  67. }
  68. ans := filepath.Join(tdir, rpath)
  69. if count == 1 {
  70. if err = filepath.WalkDir(tdir, func(path string, d fs.DirEntry, err error) error {
  71. if !d.IsDir() {
  72. ans = path
  73. return fs.SkipAll
  74. }
  75. return nil
  76. }); err != nil {
  77. return "", err
  78. }
  79. }
  80. return ans, nil
  81. }
  82. func get_remote_file(path string) (string, error) {
  83. if strings.HasPrefix(path, "ssh:") {
  84. parts := strings.SplitN(path, ":", 3)
  85. if len(parts) == 3 {
  86. return get_ssh_file(parts[1], parts[2])
  87. }
  88. }
  89. return path, nil
  90. }
  91. func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
  92. opts = opts_
  93. conf, err = load_config(opts)
  94. if err != nil {
  95. return 1, err
  96. }
  97. if len(args) != 2 {
  98. return 1, fmt.Errorf("You must specify exactly two files/directories to compare")
  99. }
  100. if err = set_diff_command(conf.Diff_cmd); err != nil {
  101. return 1, err
  102. }
  103. switch conf.Color_scheme {
  104. case Color_scheme_light:
  105. use_light_colors = true
  106. case Color_scheme_dark:
  107. use_light_colors = false
  108. case Color_scheme_auto:
  109. use_light_colors = false
  110. }
  111. init_caches()
  112. defer func() {
  113. for tdir := range remote_dirs {
  114. os.RemoveAll(tdir)
  115. }
  116. }()
  117. left, err := get_remote_file(args[0])
  118. if err != nil {
  119. return 1, err
  120. }
  121. right, err := get_remote_file(args[1])
  122. if err != nil {
  123. return 1, err
  124. }
  125. if isdir(left) != isdir(right) {
  126. return 1, fmt.Errorf("The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'")
  127. }
  128. if !exists(left) {
  129. return 1, fmt.Errorf("%s does not exist", left)
  130. }
  131. if !exists(right) {
  132. return 1, fmt.Errorf("%s does not exist", right)
  133. }
  134. lp, err = loop.New()
  135. loop.MouseTrackingMode(lp, loop.BUTTONS_AND_DRAG_MOUSE_TRACKING)
  136. if err != nil {
  137. return 1, err
  138. }
  139. lp.ColorSchemeChangeNotifications()
  140. h := Handler{left: left, right: right, lp: lp}
  141. lp.OnInitialize = func() (string, error) {
  142. lp.SetCursorVisible(false)
  143. lp.SetCursorShape(loop.BAR_CURSOR, true)
  144. lp.AllowLineWrapping(false)
  145. lp.SetWindowTitle(fmt.Sprintf("%s vs. %s", left, right))
  146. lp.QueryCapabilities()
  147. h.initialize()
  148. return "", nil
  149. }
  150. lp.OnCapabilitiesReceived = func(tc loop.TerminalCapabilities) error {
  151. if !tc.KeyboardProtocol {
  152. return fmt.Errorf("This terminal does not support the kitty keyboard protocol, or you are running inside a terminal multiplexer that is blocking querying for kitty keyboard protocol support. The diff kitten cannot function without it.")
  153. }
  154. h.on_capabilities_received(tc)
  155. return nil
  156. }
  157. lp.OnWakeup = h.on_wakeup
  158. lp.OnFinalize = func() string {
  159. lp.SetCursorVisible(true)
  160. lp.SetCursorShape(loop.BLOCK_CURSOR, true)
  161. h.finalize()
  162. return ""
  163. }
  164. lp.OnResize = h.on_resize
  165. lp.OnKeyEvent = h.on_key_event
  166. lp.OnText = h.on_text
  167. lp.OnMouseEvent = h.on_mouse_event
  168. err = lp.Run()
  169. if err != nil {
  170. return 1, err
  171. }
  172. ds := lp.DeathSignalName()
  173. if ds != "" {
  174. fmt.Println("Killed by signal: ", ds)
  175. lp.KillIfSignalled()
  176. return 1, nil
  177. }
  178. return
  179. }
  180. func EntryPoint(parent *cli.Command) {
  181. create_cmd(parent, main)
  182. }