├── .gitignore
├── README.org
├── dired-git-info.el
└── images
└── screenshot2.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.elc
2 | *-pkg.el
3 | *-autoloads.el
4 |
--------------------------------------------------------------------------------
/README.org:
--------------------------------------------------------------------------------
1 | #+BEGIN_HTML
2 |
3 | #+END_HTML
4 |
5 | * Description
6 |
7 | This Emacs packages provides a minor mode which shows git information inside
8 | the dired buffer:
9 |
10 | [[./images/screenshot2.png]]
11 |
12 | * Installation
13 |
14 | ** GNU ELPA
15 |
16 | This package is available on [[https://elpa.gnu.org][GNU ELPA]]. You can install it via =M-x package-install RET dired-git-info RET=
17 |
18 | ** Manual
19 |
20 | For manual installation, clone the repository and call:
21 |
22 | #+BEGIN_SRC elisp
23 | (package-install-file "/path/to/dired-git-info.el")
24 | #+END_SRC
25 |
26 | * Config
27 |
28 | ** Bind the minor mode command in dired
29 |
30 | #+BEGIN_SRC elisp
31 | (with-eval-after-load 'dired
32 | (define-key dired-mode-map ")" 'dired-git-info-mode))
33 | #+END_SRC
34 |
35 | ** Don't hide normal Dired file info
36 |
37 | By default, toggling =dired-git-info-mode= also toggles the built-in
38 | =dired-hide-details-mode=, which hides file details such as ownership,
39 | permissions and size. This behaviour can be disabled by overriding
40 | =dgi-auto-hide-details-p=:
41 |
42 | #+BEGIN_SRC elisp
43 | (setq dgi-auto-hide-details-p nil)
44 | #+END_SRC
45 |
46 | ** Enable automatically in every Dired buffer (if in Git repository)
47 |
48 | To enable =dired-git-info-mode= whenever you navigate to a Git repository, use
49 | the following:
50 | #+BEGIN_SRC elisp
51 | (add-hook 'dired-after-readin-hook 'dired-git-info-auto-enable)
52 | #+END_SRC
53 |
54 |
--------------------------------------------------------------------------------
/dired-git-info.el:
--------------------------------------------------------------------------------
1 | ;;; dired-git-info.el --- Show git info in dired -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2018-2019 Free Software Foundation, Inc.
4 |
5 | ;; Author: Clemens Radermacher
6 | ;; URL: https://github.com/clemera/dired-git-info
7 | ;; Version: 0.3.1
8 | ;; Package-Requires: ((emacs "25"))
9 | ;; Keywords: dired, files
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 | ;;
26 | ;; Minor mode which shows last commit message and date (info shown is
27 | ;; configurable) for git project files in dired.
28 | ;;
29 |
30 | ;;; Code:
31 |
32 | (require 'dired)
33 |
34 | (defgroup dired-git-info nil
35 | "Show git info in dired."
36 | :group 'files
37 | :prefix "dgi-")
38 |
39 | (defface dgi-commit-message-face
40 | '((t (:inherit font-lock-comment-face)))
41 | "Face for commit message overlays.")
42 |
43 | (defcustom dgi-auto-hide-details-p t
44 | "If details should get hidden automatically.
45 |
46 | Uses function `dired-hide-details-mode' to hide details when showing git
47 | info."
48 | :type 'boolean)
49 |
50 | (defcustom dgi-commit-message-format "%s\t%cr"
51 | "Format of the commit messages.
52 |
53 | Entries separated by tabs are aligned. Some common placeholders
54 | are (see git-log PRETTY FORMATS for all):
55 |
56 | · %H: commit hash
57 |
58 | · %h: abbreviated commit hash
59 |
60 | · %P: parent hashes
61 |
62 | · %p: abbreviated parent hashes
63 |
64 | · %an: author name
65 |
66 | · %ae: author email
67 |
68 | · %cd: committer date
69 |
70 | · %cr: committer date, relative
71 |
72 | · %cn: committer name
73 |
74 | · %ce: committer email
75 |
76 | · %s: subject
77 |
78 | · %f: sanitized subject line, suitable for a filename"
79 | :type 'string)
80 |
81 | (defvar-local dgi--commit-ovs nil
82 | "Overlays which show the commit messages.")
83 |
84 | (defvar dgi--restore-no-details nil
85 | "If no details view has to be restored.")
86 |
87 | (defun dgi--command-to-string (program &rest args)
88 | "Execute PROGRAM with arguments ARGS and return output string.
89 |
90 | If program returns non zero exit code return nil."
91 | (let* ((ecode nil)
92 | (output (with-output-to-string
93 | (with-current-buffer standard-output
94 | (setq ecode (apply #'process-file program nil t nil args))))))
95 | (when (eq ecode 0)
96 | output)))
97 |
98 |
99 | (defun dgi--get-commit-info (&optional file gitf)
100 | "Get commit message info.
101 |
102 | FILE default to current dired file. GITF determines the commit
103 | info format and defaults to `dgi-commit-message-format'."
104 | (let* ((tfile (or file (dired-get-file-for-visit)))
105 | (file (or (file-remote-p tfile 'localname) tfile)))
106 | (when file
107 | (let ((msg (dgi--command-to-string
108 | "git" "log" "-1"
109 | (concat "--pretty="
110 | (or gitf dgi-commit-message-format))
111 | file)))
112 | (when (and msg (not (string= "" msg)))
113 | (substring msg
114 | ;; skip newline
115 | 0 -1))))))
116 |
117 |
118 | (defmacro dgi--save-marked (&rest body)
119 | "Execute BODY and restore marked files afterwards."
120 | `(let ((marked (save-excursion
121 | (goto-char (point-min))
122 | (dired-get-marked-files)))
123 | (inhibit-message t))
124 | (save-excursion
125 | (unwind-protect
126 | (progn ,@body)
127 | (dired-unmark-all-marks)
128 | (dolist (file marked)
129 | (dired-goto-file file)
130 | (dired-mark 1))))))
131 |
132 |
133 | (defun dgi--cleanup ()
134 | "Remove commit overlays."
135 | (when dgi--restore-no-details
136 | (setq dgi--restore-no-details nil)
137 | (dired-hide-details-mode -1))
138 | (dolist (ov dgi--commit-ovs)
139 | (delete-overlay ov))
140 | (setq dgi--commit-ovs nil))
141 |
142 |
143 | (defun dgi--get-dired-files-length (files)
144 | "Get list of lengths of all FILES as displayed by dired."
145 | (let ((dnames ()))
146 | (dolist (file files (nreverse dnames))
147 | (push (dgi--get-dired-file-length file)
148 | dnames))))
149 |
150 |
151 | (defun dgi--get-dired-file-length (file)
152 | "Get lengths of FILE as displayed by dired."
153 | (save-excursion
154 | (dired-goto-file file)
155 | (let ((opos (point)))
156 | (while (and (not (eolp))
157 | (or (not dired-hide-details-mode)
158 | (not (get-text-property (point) 'invisible))))
159 | (forward-char 1))
160 | (length (buffer-substring opos (point))))))
161 |
162 |
163 | (defun dgi--get-commit-messages (files)
164 | "Get formatted commit messages for FILES."
165 | (let ((messages ()))
166 | (dolist (file files)
167 | (push (dgi--get-commit-info file)
168 | messages))
169 | (with-temp-buffer
170 | (dolist (message (nreverse messages))
171 | (insert (or message "") "\n"))
172 | (let ((indent-tabs-mode nil))
173 | (align-regexp (point-min)
174 | (point-max)
175 | "\\(\\s-*\\)\t" nil nil t))
176 | (goto-char (point-min))
177 | (while (search-forward "\t" nil t)
178 | (replace-match " "))
179 | (split-string (buffer-string) "\n"))))
180 |
181 |
182 | (defun dgi--format-line-overlay (msg)
183 | "Format message MSG for current dired line."
184 | (let* ((le (line-end-position))
185 | (aw (- (window-width)
186 | (1+ (save-excursion
187 | (goto-char le)
188 | (current-column))))))
189 | (if (not (> aw 0))
190 | "\n"
191 | (concat (dgi--clamp-string msg aw)
192 | "\n"))))
193 |
194 | (defun dgi--clamp-string (str max)
195 | "Return STRING truncated to MAX length if needed."
196 | (propertize
197 | (if (> (length str) max)
198 | (concat (substring str 0
199 | (- max (+ (length str) 3)))
200 | "...")
201 | str)
202 | 'face 'dgi-commit-message-face))
203 |
204 |
205 | ;;;###autoload
206 | (define-minor-mode dired-git-info-mode
207 | "Toggle git message info in current dired buffer."
208 | :lighter " dgi"
209 | (if (not dired-git-info-mode)
210 | (dgi--cleanup)
211 | (unless (derived-mode-p 'dired-mode)
212 | (user-error "Not in a dired buffer"))
213 | (unless (locate-dominating-file "." ".git")
214 | (user-error "Not inside a git repository"))
215 | (when dgi-auto-hide-details-p
216 | (unless dired-hide-details-mode
217 | (setq dgi--restore-no-details t)
218 | (dired-hide-details-mode 1)))
219 | (let* ((files (dgi--save-marked
220 | (dired-unmark-all-marks)
221 | (dired-toggle-marks)
222 | (dired-get-marked-files)))
223 | (minspc (1+ (apply #'max (dgi--get-dired-files-length files))))
224 | (messages (dgi--get-commit-messages files)))
225 | (save-excursion
226 | (dolist (file files)
227 | (let ((msg (pop messages)))
228 | (when msg
229 | (dired-goto-file file)
230 | (let ((spc (make-string
231 | (- minspc (dgi--get-dired-file-length file))
232 | ?\s)))
233 | (goto-char (line-end-position))
234 | (let ((ov (make-overlay (point) (1+ (point))))
235 | (ovs (dgi--format-line-overlay (concat spc msg))))
236 | (push ov dgi--commit-ovs)
237 | ;; I don't use after-string because I didn't get it to work
238 | ;; in combination with hl-line-mode overlay
239 | (overlay-put ov 'display ovs)
240 | ;; hl line mode should have priority
241 | (overlay-put ov 'priority -60))))))))))
242 |
243 | ;;;###autoload
244 | (defun dired-git-info-auto-enable ()
245 | "Enable `dired-git-info-mode' if current dired buffer is in a git repo.
246 |
247 | Add this function to `dired-after-readin-hook' to enable the mode
248 | automatically inside git repos."
249 | (when (locate-dominating-file "." ".git")
250 | (dired-git-info-mode)))
251 |
252 |
253 | (provide 'dired-git-info)
254 | ;;; dired-git-info.el ends here
255 |
256 |
257 |
--------------------------------------------------------------------------------
/images/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clemera/dired-git-info/91d57e3a4c5104c66a3abc18e281ee55e8979176/images/screenshot2.png
--------------------------------------------------------------------------------