main.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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", "-c", "-f", "-"}
  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. init_caches()
  104. create_formatters()
  105. defer func() {
  106. for tdir := range remote_dirs {
  107. os.RemoveAll(tdir)
  108. }
  109. }()
  110. left, err := get_remote_file(args[0])
  111. if err != nil {
  112. return 1, err
  113. }
  114. right, err := get_remote_file(args[1])
  115. if err != nil {
  116. return 1, err
  117. }
  118. if isdir(left) != isdir(right) {
  119. 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.'")
  120. }
  121. if !exists(left) {
  122. return 1, fmt.Errorf("%s does not exist", left)
  123. }
  124. if !exists(right) {
  125. return 1, fmt.Errorf("%s does not exist", right)
  126. }
  127. lp, err = loop.New()
  128. loop.MouseTrackingMode(lp, loop.BUTTONS_AND_DRAG_MOUSE_TRACKING)
  129. if err != nil {
  130. return 1, err
  131. }
  132. h := Handler{left: left, right: right, lp: lp}
  133. lp.OnInitialize = func() (string, error) {
  134. lp.SetCursorVisible(false)
  135. lp.SetCursorShape(loop.BAR_CURSOR, true)
  136. lp.AllowLineWrapping(false)
  137. lp.SetWindowTitle(fmt.Sprintf("%s vs. %s", left, right))
  138. h.initialize()
  139. return "", nil
  140. }
  141. lp.OnWakeup = h.on_wakeup
  142. lp.OnFinalize = func() string {
  143. lp.SetCursorVisible(true)
  144. lp.SetCursorShape(loop.BLOCK_CURSOR, true)
  145. h.finalize()
  146. return ""
  147. }
  148. lp.OnResize = h.on_resize
  149. lp.OnKeyEvent = h.on_key_event
  150. lp.OnText = h.on_text
  151. lp.OnMouseEvent = h.on_mouse_event
  152. err = lp.Run()
  153. if err != nil {
  154. return 1, err
  155. }
  156. ds := lp.DeathSignalName()
  157. if ds != "" {
  158. fmt.Println("Killed by signal: ", ds)
  159. lp.KillIfSignalled()
  160. return 1, nil
  161. }
  162. return
  163. }
  164. func EntryPoint(parent *cli.Command) {
  165. create_cmd(parent, main)
  166. }