forked from kljohann/eldoc-diffstat
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
eldoc-diffstat.el
259 lines (224 loc) · 10.1 KB
/
eldoc-diffstat.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
;;; eldoc-diffstat.el --- Make VCS diffstat available via eldoc -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Johann Klähn
;; Author: Johann Klähn <[email protected]>
;; Maintainer: Johann Klähn <[email protected]>
;; Keywords: vc, docs
;; Version: 1.0
;; URL: https://github.com/kljohann/eldoc-diffstat/
;; Package-Requires: ((emacs "29.1"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package provides a way to display VCS diffstat information via eldoc.
;; It supports Git and Mercurial repositories.
;;
;; To use, call `eldoc-diffstat-setup' in the desired buffer or mode hook.
;; You might also want to add the following to your config:
;;
;; (eldoc-add-command
;; 'magit-next-line 'magit-previous-line
;; 'magit-section-forward 'magit-section-backward
;; 'magit-section-forward-sibling 'magit-section-backward-sibling)
;;
;; Adapted from Tassilo Horn's blog post:
;; https://www.tsdh.org/posts/2022-07-20-using-eldoc-with-magit-async.html
;;; Code:
(require 'ansi-color)
(require 'eieio)
(declare-function log-view-current-tag "log-view")
(declare-function magit-commit-at-point "magit-git")
(declare-function magit-stash-at-point "magit-git")
;; These slots are used in git-rebase-action (see git-rebase.el).
(eieio-declare-slots action-type target)
;; Extend the list of commands on which eldoc triggers.
(with-eval-after-load 'eldoc
(eldoc-add-command
'magit-next-line 'magit-previous-line
'magit-section-forward 'magit-section-backward
'magit-section-forward-sibling 'magit-section-backward-sibling))
(defgroup eldoc-diffstat nil
"Show VCS diffstat information in echo area."
:group 'eldoc
:group 'vc)
(defcustom eldoc-diffstat-lines nil
"If non-nil, truncate echo area output after this many lines.
If the value is a negative number, force output to exactly the absolute
value number of lines, either by truncating or padding with empty lines
as needed.
See also `eldoc-echo-area-use-multiline-p'."
:type '(choice (const :tag "No limit" nil)
(integer :tag "Maximum number of lines (positive values)" 5)
(integer :tag "Fixed number of lines (negative values)" -5)))
(defvar eldoc-diffstat--process nil
"The latest async process used for fetching diffstat information.
Only one active process at a time; new requests terminate previous ones.
After completion, a cached version of the diffstat output is attached as
a property to the process object.")
(defconst eldoc-diffstat--commands
'((Git "git" "--no-pager" "show" "--color=always"
"--format=format:%an <%ae>, %aD:%n%s" "--stat=80")
(Hg "hg" "--pager=never" "log" "--color=always"
"--template" "{author}, {date|rfc822date}:\n{desc|firstline}\n"
"--stat" "--rev"))
"Alist mapping VCS backend to the command to use for computing the diffstat.")
(defconst eldoc-diffstat--output-regex
(rx buffer-start
;; 1: commit author and date
;; 2: newline between author info and subject (will be removed)
(group-n 1 (+ not-newline)) (group-n 2 ?\n)
;; subject / first line of commit message
(+ not-newline) ?\n
;; 3: point after subject / before diffstat (shortstat will be moved here)
(group-n 3)
;; diffstat for individual files
(+? anything)
;; 4: shortstat, i.e., total # of modified files + added/deleted lines
(group-n 4 (+ not-newline) ?\n)
(* space) buffer-end)
"Regular expression used to rewrite and apply faces to diffstat output.
Has to match `eldoc-diffstat--commands'.")
;;;###autoload
(define-obsolete-function-alias 'eldoc-diffstat-setup 'eldoc-diffstat-mode
"1.0" "Configure eldoc buffer-locally to display diffstat for revision at point.")
;;;###autoload
(define-minor-mode eldoc-diffstat-mode
"Toggle echo area display of VCS diffstat information in the local buffer.
When enabled, diffstat information is shown in supported major modes if
point is on a revision."
:group 'eldoc-diffstat
:lighter nil
(if eldoc-diffstat-mode
(progn
(add-hook 'eldoc-documentation-functions
#'eldoc-diffstat--docstring nil 'local)
(eldoc-mode))
(remove-hook 'eldoc-documentation-functions
#'eldoc-diffstat--docstring 'local)
;; Disable eldoc if diffstat was the only information source.
(unless (eldoc--supported-p)
(eldoc-mode -1))))
;;;###autoload
(define-globalized-minor-mode global-eldoc-diffstat-mode
eldoc-diffstat-mode eldoc-diffstat-mode
:group 'eldoc-diffstat
:predicate
'(git-rebase-mode
log-view-mode
magit-log-mode
magit-status-mode
vc-annotate-mode))
(defun eldoc-diffstat--docstring (callback &rest _ignored)
"Display diffstat for revision at point by calling CALLBACK.
Intended for `eldoc-documentation-functions'."
(when-let* ((info (eldoc-diffstat--get-revision-info))
(backend (car info))
(revision (cdr info))
(command (alist-get backend eldoc-diffstat--commands)))
(if-let* ((result (eldoc-diffstat--get-cache info)))
(funcall callback result)
(eldoc-diffstat--docstring-1 (append command (list revision)) callback info))))
(defun eldoc-diffstat--get-revision-info ()
"Get revision info for the current buffer context.
The returned record should be a cons cell of the form (BACKEND . REVISION) where
BACKEND is a symbol representing the version control system and REVISION is
a string identifying the specific revision."
(cond
((when-let* (((fboundp 'magit-stash-at-point))
(revision (magit-stash-at-point)))
(cons 'Git revision)))
((when-let* (((fboundp 'magit-commit-at-point))
(revision (magit-commit-at-point)))
(cons 'Git revision)))
((and (derived-mode-p 'git-rebase-mode)
(fboundp 'git-rebase-current-line)
(with-slots (action-type target)
(git-rebase-current-line)
(and (eq action-type 'commit)
(cons 'Git target)))))
((and (derived-mode-p 'vc-annotate-mode)
(boundp 'vc-annotate-backend)
(fboundp 'vc-annotate-extract-revision-at-line))
(cons vc-annotate-backend
(car (vc-annotate-extract-revision-at-line))))
((and (derived-mode-p 'log-view-mode)
(boundp 'log-view-vc-backend))
(cons log-view-vc-backend
(log-view-current-tag)))))
(defun eldoc-diffstat--get-cache (revision-info)
"Retrieve cached diffstat result for REVISION-INFO if available."
(when-let* ((proc eldoc-diffstat--process)
((processp proc))
(cached-result (process-get proc :cached-result))
((equal revision-info (car cached-result))))
(cdr cached-result)))
(defun eldoc-diffstat--docstring-1 (command callback revision-info)
"Asynchronously compute diffstat using COMMAND and pass it to CALLBACK.
This function sets up a new asynchronous process to compute the diffstat,
killing any existing process. REVISION-INFO is a unique identifier used for
caching the result, see `eldoc-diffstat--get-cache' for details."
;; Clean up old process and its buffer.
(when (processp eldoc-diffstat--process)
(when (process-live-p eldoc-diffstat--process)
(let (confirm-kill-processes)
(kill-process eldoc-diffstat--process)))
(kill-buffer (process-buffer eldoc-diffstat--process)))
(let ((proc (make-process
:name "eldoc-diffstat"
:buffer (generate-new-buffer " *eldoc-diffstat*")
:noquery t
:command command
:sentinel
(apply-partially #'eldoc-diffstat--sentinel callback))))
(process-put proc :revision-info revision-info)
(setq eldoc-diffstat--process proc))
;; Signal that the doc string is computed asynchronously.
t)
(defun eldoc-diffstat--sentinel (callback proc _event)
"Display output of PROC after its exit by calling CALLBACK."
(when (eq (process-status proc) 'exit)
(with-current-buffer (process-buffer proc)
(eldoc-diffstat--format-output-buffer)
(let ((result (buffer-string))
(revision-info (process-get proc :revision-info)))
(process-put proc :cached-result
(cons revision-info result))
(funcall callback result)))))
(defun eldoc-diffstat--format-output-buffer ()
"Format the diffstat output in the current buffer."
;; Translate color control sequences into text properties.
(let ((ansi-color-apply-face-function
(lambda (beg end face)
(put-text-property beg end 'face face))))
(ansi-color-apply-on-region (point-min) (point-max)))
(goto-char (point-min))
(when (looking-at eldoc-diffstat--output-regex)
;; Make commit author and date bold.
(put-text-property
(match-beginning 1) (match-end 1) 'face 'bold)
;; Join the first two lines.
(replace-match " " nil nil nil 2)
;; Move summary to the second line and make it italic.
(let ((summary (delete-and-extract-region
(match-beginning 4) (match-end 4))))
(replace-match
(propertize summary 'face 'italic) nil nil nil 3)))
;; Optionally truncate / pad output.
(when eldoc-diffstat-lines
(goto-char (point-min))
(let ((remaining (forward-line (abs eldoc-diffstat-lines))))
(delete-region (point) (point-max))
(when (and (< eldoc-diffstat-lines 0) (> remaining 0))
(newline remaining)
;; eldoc skips backwards over spaces, tabs, and newlines, so insert any
;; unobstrusive character to work around that, e.g., a no-break space.
(backward-char) (insert ?\xA0)))))
(provide 'eldoc-diffstat)
;;; eldoc-diffstat.el ends here