stgit.el: Refactor file list formatting
[stgit] / contrib / stgit.el
index 5ed8f4e..9f47e54 100644 (file)
@@ -77,10 +77,9 @@ directory DIR or `default-directory'"
             (if (stgit-patch-empty patch) "(empty) " "")
             (propertize (or (stgit-patch-desc patch) "")
                         'face 'stgit-description-face))
-    (add-text-properties start (point) (list 'entry-type 'patch
-                                             'stgit-patchsym name))
+    (put-text-property start (point) 'entry-type 'patch)
     (when (memq name stgit-expanded-patches)
-      (stgit-insert-patch-files name))
+      (stgit-insert-patch-files patch))
     (put-text-property start (point) 'patch-data patch)))
 
 (defun create-stgit-buffer (dir)
@@ -362,68 +361,85 @@ Cf. `stgit-file-type-change-string'."
                        (propertize (format "%o" new-perm)
                                    'face 'stgit-file-permission-face)))))))
 
-(defun stgit-insert-patch-files (patchsym)
-  (let* ((start (point))
-         (result (with-output-to-string
-                   (stgit-run-git "diff-tree" "-r" "-z"
-                                  (if stgit-expand-find-copies-harder
-                                      "--find-copies-harder"
-                                    "-C")
-                                  (stgit-id patchsym)))))
-    (let (mstart)
-      (while (string-match "\0:\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} \\(\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\\|\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\\)"
-                           result mstart)
-        (let ((copy-or-rename (match-string 4 result))
-              (old-perm       (read (format "#o%s" (match-string 1 result))))
-              (new-perm       (read (format "#o%s" (match-string 2 result))))
-              (line-start (point))
-              status
-              change
-              (properties '(entry-type file)))
-          (if copy-or-rename
-              (let ((cr-score       (match-string 5 result))
-                    (cr-from-file   (match-string 6 result))
-                    (cr-to-file     (match-string 7 result)))
-                (setq status (stgit-file-status-code copy-or-rename
-                                                     cr-score)
-                      properties (list* 'stgit-old-file cr-from-file
-                                        'stgit-new-file cr-to-file
-                                        properties)
-                      change (concat
-                              cr-from-file
-                              (propertize " -> "
-                                          'face 'stgit-description-face)
-                              cr-to-file)))
-            (setq status (stgit-file-status-code (match-string 8 result))
-                  properties (list* 'stgit-file (match-string 9 result)
-                                    properties)
-                  change (match-string 9 result)))
-
-          (let ((mode-change (stgit-file-mode-change-string old-perm
-                                                            new-perm)))
-            (insert "\n    "
-                    (format "%-12s" (stgit-file-status-code-as-string
-                                     status))
+(defstruct (stgit-file)
+  old-perm new-perm copy-or-rename cr-score cr-from cr-to status file)
+
+(defun stgit-insert-file (file)
+  (let ((status (stgit-file-status file))
+        (name (if (stgit-file-copy-or-rename file)
+                  (concat (stgit-file-cr-from file)
+                          (propertize " -> "
+                                      'face 'stgit-description-face)
+                          (stgit-file-cr-to file))
+                (stgit-file-file file)))
+        (mode-change (stgit-file-mode-change-string
+                      (stgit-file-old-perm file)
+                      (stgit-file-new-perm file)))
+        (start (point)))
+    (insert (format "\n    %-12s%1s%s%s"
+                    (stgit-file-status-code-as-string status)
                     mode-change
-                    (if (> (length mode-change) 0) " " "")
-                    change
-                    (propertize (stgit-file-type-change-string old-perm
-                                                               new-perm)
+                    name
+                    (propertize (stgit-file-type-change-string
+                                 (stgit-file-old-perm file)
+                                 (stgit-file-new-perm file))
                                 'face 'stgit-description-face)))
-          (add-text-properties line-start (point) properties))
-        (setq mstart (match-end 0))))
+    (add-text-properties start (point)
+                         `(entry-type file file ,(stgit-file-file file)))))
+
+(defun stgit-insert-patch-files (patch)
+  "Expand (show modification of) the patch with name PATCHSYM (a
+symbol) after the line at point.
+`stgit-expand-find-copies-harder' controls how hard to try to
+find copied files."
+  (let* ((start (point))
+         (patchsym (stgit-patch-name patch))
+         (args (list "-r" "-z" (if stgit-expand-find-copies-harder
+                                   "--find-copies-harder"
+                                 "-C")))
+         (stgbuf (current-buffer)))
+    (with-temp-buffer
+      (apply 'stgit-run-git "diff-tree"
+             (append args (list (stgit-id patchsym))))
+      (goto-char (point-min))
+      (forward-char 41)
+      (while (looking-at ":\\([0-7]+\\) \\([0-7]+\\) [0-9A-Fa-f]\\{40\\} [0-9A-Fa-f]\\{40\\} ")
+        (let ((old-perm (string-to-number (match-string 1) 8))
+              (new-perm (string-to-number (match-string 2) 8)))
+          (goto-char (match-end 0))
+          (let ((file
+                 (cond ((looking-at
+                         "\\([CR]\\)\\([0-9]*\\)\0\\([^\0]*\\)\0\\([^\0]*\\)\0")
+                        (make-stgit-file
+                         :old-perm       old-perm
+                         :new-perm       new-perm
+                         :copy-or-rename t
+                         :cr-score       (string-to-number (match-string 2))
+                         :cr-from        (match-string 3)
+                         :cr-to          (match-string 4)
+                         :status         (stgit-file-status-code (match-string 1))
+                         :file           (match-string 3)))
+                       ((looking-at "\\([ABD-QS-Z]\\)\0\\([^\0]*\\)\0")
+                        (make-stgit-file
+                         :old-perm       old-perm
+                         :new-perm       new-perm
+                         :copy-or-rename nil
+                         :cr-score       nil
+                         :cr-from        nil
+                         :cr-to          nil
+                         :status         (stgit-file-status-code (match-string 1))
+                         :file           (match-string 2))))))
+            (with-current-buffer stgbuf
+              (stgit-insert-file file)))
+          (goto-char (match-end 0)))))
     (when (= start (point))
-      (insert "    <no files>\n"))
-    (put-text-property start (point) 'stgit-file-patchsym patchsym)))
+      (insert "    <no files>\n"))))
 
 (defun stgit-select-file ()
-  (let ((patched-file (stgit-patched-file-at-point)))
-    (unless patched-file
-      (error "No patch or file on the current line"))
-    (let ((filename (expand-file-name (cdr patched-file))))
-      (unless (file-exists-p filename)
-        (error "File does not exist"))
-      (find-file filename))))
+  (let ((filename (expand-file-name (get-text-property (point) 'file))))
+    (unless (file-exists-p filename)
+      (error "File does not exist"))
+    (find-file filename)))
 
 (defun stgit-select-patch ()
   (let ((patchname (stgit-patch-name-at-point)))
@@ -567,23 +583,11 @@ Commands:
 
 (defun stgit-add-mark (patchsym)
   "Mark the patch PATCHSYM."
-  (setq stgit-marked-patches (cons patchsym stgit-marked-patches))
-  (save-excursion
-    (when (stgit-goto-patch patchsym)
-      (move-to-column 1)
-      (let ((inhibit-read-only t))
-        (insert-and-inherit ?*)
-        (delete-char 1)))))
+  (setq stgit-marked-patches (cons patchsym stgit-marked-patches)))
 
 (defun stgit-remove-mark (patchsym)
   "Unmark the patch PATCHSYM."
-  (setq stgit-marked-patches (delq patchsym stgit-marked-patches))
-  (save-excursion
-    (when (stgit-goto-patch patchsym)
-      (move-to-column 1)
-      (let ((inhibit-read-only t))
-        (insert-and-inherit ? )
-        (delete-char 1)))))
+  (setq stgit-marked-patches (delq patchsym stgit-marked-patches)))
 
 (defun stgit-clear-marks ()
   "Unmark all patches."
@@ -636,11 +640,13 @@ the new file names instead of just one name."
 
 (defun stgit-goto-patch (patchsym)
   "Move point to the line containing patch PATCHSYM.
-If that patch cannot be found, return nil."
-  (let ((p (text-property-any (point-min) (point-max)
-                              'stgit-patchsym patchsym)))
-    (when p
-      (goto-char p)
+If that patch cannot be found, do nothing."
+  (let ((node (ewoc-nth stgit-ewoc 0)))
+    (while (and node (not (eq (stgit-patch-name (ewoc-data node))
+                              patchsym)))
+      (setq node (ewoc-next stgit-ewoc node)))
+    (when node
+      (ewoc-goto-node stgit-ewoc node)
       (move-to-column goal-column))))
 
 (defun stgit-init ()
@@ -653,20 +659,29 @@ If that patch cannot be found, return nil."
 (defun stgit-mark ()
   "Mark the patch under point."
   (interactive)
-  (let ((patch (stgit-patch-name-at-point t)))
-    (stgit-add-mark patch))
+  (let* ((node (ewoc-locate stgit-ewoc))
+         (patch (ewoc-data node)))
+    (stgit-add-mark (stgit-patch-name patch))
+    (ewoc-invalidate stgit-ewoc node))
   (stgit-next-patch))
 
 (defun stgit-unmark-up ()
   "Remove mark from the patch on the previous line."
   (interactive)
   (stgit-previous-patch)
-  (stgit-remove-mark (stgit-patch-name-at-point t)))
+  (let* ((node (ewoc-locate stgit-ewoc))
+         (patch (ewoc-data node)))
+    (stgit-remove-mark (stgit-patch-name patch))
+    (ewoc-invalidate stgit-ewoc node))
+  (move-to-column (stgit-goal-column)))
 
 (defun stgit-unmark-down ()
   "Remove mark from the patch on the current line."
   (interactive)
-  (stgit-remove-mark (stgit-patch-name-at-point t))
+  (let* ((node (ewoc-locate stgit-ewoc))
+         (patch (ewoc-data node)))
+    (stgit-remove-mark (stgit-patch-name patch))
+    (ewoc-invalidate stgit-ewoc node))
   (stgit-next-patch))
 
 (defun stgit-rename (name)