stgit.el: Move point properly after stgit-{file-toggle-index,reload}
[stgit] / contrib / stgit.el
index c3c8531..0d99467 100644 (file)
@@ -64,31 +64,26 @@ directory DIR or `default-directory'"
   status name desc empty files-ewoc)
 
 (defun stgit-patch-pp (patch)
-  (let ((status (stgit-patch-status patch))
-        (start (point))
-        (name (stgit-patch-name patch)))
-    (case name
-       (:index (insert "  "
-                       (propertize "Index"
-                                   'face 'stgit-index-work-tree-title-face)))
-       (:work  (insert "  "
-                       (propertize "Work tree"
-                                   'face 'stgit-index-work-tree-title-face)))
-       (t (insert (case status
-                    ('applied "+")
-                    ('top ">")
-                    ('unapplied "-"))
-                  (if (memq name stgit-marked-patches)
-                      "*" " ")
-                  (format "%-30s"
-                          (propertize
-                           (symbol-name name)
-                           'face (cdr (assq status
-                                            stgit-patch-status-face-alist))))
-                  "  "
-                  (if (stgit-patch-empty patch) "(empty) " "")
-                  (propertize (or (stgit-patch-desc patch) "")
-                              'face 'stgit-description-face))))
+  (let* ((status (stgit-patch-status patch))
+         (start (point))
+         (name (stgit-patch-name patch))
+         (face (cdr (assq status stgit-patch-status-face-alist))))
+    (insert (case status
+              ('applied "+")
+              ('top ">")
+              ('unapplied "-")
+              (t " "))
+            (if (memq name stgit-marked-patches)
+                "*" " "))
+    (if (memq status '(index work))
+        (insert (propertize (if (eq status 'index) "Index" "Work tree")
+                            'face face))
+      (insert (format "%-30s"
+                      (propertize (symbol-name name) 'face face))
+              "  "
+              (if (stgit-patch-empty patch) "(empty) " "")
+              (propertize (or (stgit-patch-desc patch) "")
+                          'face 'stgit-description-face)))
     (insert "\n")
     (put-text-property start (point) 'entry-type 'patch)
     (when (memq name stgit-expanded-patches)
@@ -247,7 +242,8 @@ Returns nil if there was no output."
   (interactive)
   (let ((inhibit-read-only t)
         (curline (line-number-at-pos))
-        (curpatch (stgit-patch-name-at-point)))
+        (curpatch (stgit-patch-name-at-point))
+        (curfile (stgit-patched-file-at-point)))
     (ewoc-filter stgit-ewoc #'(lambda (x) nil))
     (ewoc-set-hf stgit-ewoc
                  (concat "Branch: "
@@ -265,7 +261,7 @@ Returns nil if there was no output."
                    'face 'stgit-description-face)))
     (stgit-run-series stgit-ewoc)
     (if curpatch
-        (stgit-goto-patch curpatch)
+        (stgit-goto-patch curpatch (and curfile (stgit-file-file curfile)))
       (goto-line curline)))
   (stgit-refresh-git-status))
 
@@ -323,6 +319,11 @@ Returns nil if there was no output."
   "StGit mode face used for unknown file status"
   :group 'stgit)
 
+(defface stgit-ignored-file-face
+  '((((class color) (background light)) (:foreground "grey60"))
+    (((class color) (background dark)) (:foreground "grey40")))
+  "StGit mode face used for ignored files")
+
 (defface stgit-file-permission-face
   '((((class color) (background light)) (:foreground "green" :bold t))
     (((class color) (background dark)) (:foreground "green" :bold t)))
@@ -356,15 +357,16 @@ flag, which reduces performance."
             (rename      "Renamed"     stgit-modified-file-face)
             (mode-change "Mode change" stgit-modified-file-face)
             (unmerged    "Unmerged"    stgit-unmerged-file-face)
-            (unknown     "Unknown"     stgit-unknown-file-face)))
+            (unknown     "Unknown"     stgit-unknown-file-face)
+            (ignore      "Ignored"     stgit-ignored-file-face)))
   "Alist of code symbols to description strings")
 
 (defconst stgit-patch-status-face-alist
   '((applied   . stgit-applied-patch-face)
     (top       . stgit-top-patch-face)
     (unapplied . stgit-unapplied-patch-face)
-    (index     . nil)
-    (work      . nil))
+    (index     . stgit-index-work-tree-title-face)
+    (work      . stgit-index-work-tree-title-face))
   "Alist of face to use for a given patch status")
 
 (defun stgit-file-status-code-as-string (file)
@@ -385,6 +387,7 @@ flag, which reduces performance."
   (let ((code (assoc str '(("A" . add)
                            ("C" . copy)
                            ("D" . delete)
+                           ("I" . ignore)
                            ("M" . modify)
                            ("R" . rename)
                            ("T" . mode-change)
@@ -484,6 +487,16 @@ Cf. `stgit-file-type-change-string'."
       "--find-copies-harder"
     "-C"))
 
+(defun stgit-insert-ls-files (args file-flag)
+  (let ((start (point)))
+    (apply 'stgit-run-git
+           (append '("ls-files" "--exclude-standard" "-z") args))
+    (goto-char start)
+    (while (looking-at "\\([^\0]*\\)\0")
+      (let ((name-len (- (match-end 0) (match-beginning 0))))
+        (insert ":0 0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 " file-flag "\0")
+        (forward-char name-len)))))
+
 (defun stgit-insert-patch-files (patch)
   "Expand (show modification of) the patch PATCH after the line
 at point."
@@ -501,6 +514,15 @@ at point."
                     `("diff-index" ,@args "--cached" "HEAD"))
                    (t
                     `("diff-tree" ,@args "-r" ,(stgit-id patchsym)))))
+
+      (when (and (eq patchsym :work))
+        (when stgit-show-ignored
+          (stgit-insert-ls-files '("--ignored" "--others") "I"))
+        (when stgit-show-unknown
+          (stgit-insert-ls-files '("--others") "X"))
+        (sort-regexp-fields nil ":[^\0]*\0\\([^\0]*\\)\0" "\\1"
+                            (point-min) (point-max)))
+
       (goto-char (point-min))
       (unless (or (eobp) (memq patchsym '(:work :index)))
         (forward-char 41))
@@ -643,7 +665,9 @@ file for (applied) copies and renames."
   (let ((toggle-map (make-keymap)))
     (suppress-keymap toggle-map)
     (mapc (lambda (arg) (define-key toggle-map (car arg) (cdr arg)))
-          '(("t" .        stgit-toggle-worktree)))
+          '(("t" .        stgit-toggle-worktree)
+            ("i" .        stgit-toggle-ignored)
+            ("u" .        stgit-toggle-unknown)))
     (setq stgit-mode-map (make-keymap))
     (suppress-keymap stgit-mode-map)
     (mapc (lambda (arg) (define-key stgit-mode-map (car arg) (cdr arg)))
@@ -760,13 +784,26 @@ index or work tree."
           (list patch)
         '()))))
 
-(defun stgit-goto-patch (patchsym)
+(defun stgit-goto-patch (patchsym &optional file)
   "Move point to the line containing patch PATCHSYM.
-If that patch cannot be found, do nothing."
+If that patch cannot be found, do nothing.
+
+If the patch was found and FILE is not nil, instead move to that
+file's line. If FILE cannot be found, stay on the line of
+PATCHSYM."
   (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 (and node file)
+      (let* ((file-ewoc (stgit-patch-files-ewoc (ewoc-data node)))
+             (file-node (ewoc-nth file-ewoc 0)))
+        (while (and file-node (not (equal (stgit-file-file (ewoc-data file-node)) file)))
+          (setq file-node (ewoc-next file-ewoc file-node)))
+        (when file-node
+          (ewoc-goto-node file-ewoc file-node)
+          (move-to-column (stgit-goal-column))
+          (setq node nil))))
     (when node
       (ewoc-goto-node stgit-ewoc node)
       (move-to-column goal-column))))
@@ -891,6 +928,12 @@ working tree."
     (unless (memq patch-name '(:work :index))
       (error "No index or working tree file on this line"))
 
+    (when (eq file-status 'ignore)
+      (error "Cannot revert ignored files"))
+
+    (when (eq file-status 'unknown)
+      (error "Cannot revert unknown files"))
+
     (let ((nfiles (+ (if rm-file 1 0) (if co-file 1 0))))
       (when (yes-or-no-p (format "Revert %d file%s? "
                                  nfiles
@@ -1030,22 +1073,48 @@ If PATCHSYM is a keyword, returns PATCHSYM unmodified."
     (stgit-run-git "reset" "-q" "--" file)))
 
 (defun stgit-file-toggle-index ()
-  "Move modified file in or out of the index."
+  "Move modified file in or out of the index.
+
+Leaves the point where it is, but moves the mark to where the
+file ended up. You can then jump to the file with \
+\\[exchange-point-and-mark]."
   (interactive)
   (let ((patched-file (stgit-patched-file-at-point)))
     (unless patched-file
       (error "No file on the current line"))
     (when (eq (stgit-file-status patched-file) 'unmerged)
       (error (substitute-command-keys "Use \\[stgit-resolve-file] to move an unmerged file to the index")))
-    (let ((patch-name (stgit-patch-name-at-point)))
+    (when (eq (stgit-file-status patched-file) 'ignore)
+      (error "You cannot add ignored files to the index"))
+    (let* ((patch      (stgit-patch-at-point))
+           (patch-name (stgit-patch-name patch))
+           (old-point  (point))
+           next-file)
+
+      ;; find the next file in the patch, or the previous one if this
+      ;; was the last file
+      (and (zerop (forward-line 1))
+           (let ((f (stgit-patched-file-at-point)))
+             (and f (setq next-file (stgit-file-file f)))))
+      (goto-char old-point)
+      (unless next-file
+        (and (zerop (forward-line -1))
+             (let ((f (stgit-patched-file-at-point)))
+               (and f (setq next-file (stgit-file-file f)))))
+        (goto-char old-point))
+
       (cond ((eq patch-name :work)
              (stgit-move-change-to-index (stgit-file-file patched-file)))
             ((eq patch-name :index)
              (stgit-remove-change-from-index (stgit-file-file patched-file)))
             (t
-             (error "Can only move files in the working tree to index")))))
-  (stgit-refresh-worktree)
-  (stgit-refresh-index))
+             (error "Can only move files in the working tree to index")))
+      (stgit-refresh-worktree)
+      (stgit-refresh-index)
+      (stgit-goto-patch (if (eq patch-name :index) :work :index)
+                        (stgit-file-file patched-file))
+      (push-mark nil t t)
+      (stgit-goto-patch patch-name next-file))))
 
 (defun stgit-edit ()
   "Edit the patch on the current line."
@@ -1315,6 +1384,12 @@ This value is used as the default value for `stgit-show-worktree'."
 
 See also `stgit-show-worktree-mode'.")
 
+(defvar stgit-show-ignored nil
+  "If nil, inhibit showing files ignored by git.")
+
+(defvar stgit-show-unknown nil
+  "If nil, inhibit showing files not registered with git.")
+
 (defun stgit-toggle-worktree (&optional arg)
   "Toggle the visibility of the work tree.
 With arg, show the work tree if arg is positive.
@@ -1330,4 +1405,28 @@ work tree will show up."
           (not stgit-show-worktree)))
   (stgit-reload))
 
+(defun stgit-toggle-ignored (&optional arg)
+  "Toggle the visibility of files ignored by git in the work
+tree. With ARG, show these files if ARG is positive.
+
+Use \\[stgit-toggle-worktree] to show the work tree."
+  (interactive)
+  (setq stgit-show-ignored
+        (if (numberp arg)
+            (> arg 0)
+          (not stgit-show-ignored)))
+  (stgit-reload))
+
+(defun stgit-toggle-unknown (&optional arg)
+  "Toggle the visibility of files not registered with git in the
+work tree. With ARG, show these files if ARG is positive.
+
+Use \\[stgit-toggle-worktree] to show the work tree."
+  (interactive)
+  (setq stgit-show-unknown
+        (if (numberp arg)
+            (> arg 0)
+          (not stgit-show-unknown)))
+  (stgit-reload))
+
 (provide 'stgit)