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
260
261
262
263
264
265
266
267
268
269
270
271
|
;;; magit-margin.el --- Margins in Magit buffers -*- lexical-binding:t -*-
;; Copyright (C) 2008-2026 The Magit Project Contributors
;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Magit 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.
;;
;; Magit 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 Magit. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements support for showing additional information
;; in the margins of Magit buffers. Currently this is only used for
;; commits, for which the committer date or age, and optionally the
;; author name are shown.
;;; Code:
(require 'magit-base)
(require 'magit-transient)
(require 'magit-mode)
;;; Options
(defgroup magit-margin nil
"Information Magit displays in the margin.
You can change the STYLE and AUTHOR-WIDTH of all `magit-*-margin'
options to the same values by customizing `magit-log-margin'
*before* `magit' is loaded. If you do that, then the respective
values for the other options will default to what you have set
for that variable. Likewise if you set `magit-log-margin's INIT
to nil, then that is used in the default of all other options. But
setting it to t, i.e., re-enforcing the default for that option,
does not carry to other options."
:link '(info-link "(magit)Log Margin")
:group 'magit-log)
;;; Settings
(defvar-local magit--right-margin-delayed nil)
(defvar-local magit--right-margin-config nil)
(put 'magit--right-margin-config 'permanent-local t)
(defun magit--right-margin-active ()
(car magit--right-margin-config))
(defun magit--right-margin-option ()
(pcase major-mode
('magit-cherry-mode 'magit-cherry-margin)
('magit-log-mode 'magit-log-margin)
('magit-log-select-mode 'magit-log-select-margin)
('magit-reflog-mode 'magit-reflog-margin)
('magit-refs-mode 'magit-refs-margin)
('magit-stashes-mode 'magit-stashes-margin)
('magit-status-mode 'magit-status-margin)
('forge-notifications-mode 'magit-status-margin)
('forge-topics-mode 'magit-status-margin)))
;;; Commands
(transient-define-prefix magit-margin-settings ()
"Change what information is displayed in the right margin."
:info-manual "(magit) Log Margin"
["Margin"
(magit-toggle-margin)
(magit-cycle-margin-style)
(magit-toggle-margin-details)
(magit-refs-set-show-commit-count)])
(transient-define-suffix magit-toggle-margin ()
"Show or hide the right margin."
:description "Toggle visibility"
:key "L"
:transient t
(interactive)
(unless (magit--right-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
(setcar magit--right-margin-config (not (magit--right-margin-active)))
(magit-set-buffer-margins))
(defvar magit-margin-default-time-format nil
"See https://github.com/magit/magit/pull/4605.")
(transient-define-suffix magit-cycle-margin-style ()
"Cycle style used for the right margin."
:description "Cycle style"
:key "l"
:transient t
(interactive)
(unless (magit--right-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
;; This is only suitable for commit margins (there are not others).
(setf (cadr magit--right-margin-config)
(pcase (cadr magit--right-margin-config)
('age 'age-abbreviated)
('age-abbreviated
(let ((default (or magit-margin-default-time-format
(cadr (symbol-value (magit--right-margin-option))))))
(if (stringp default) default "%Y-%m-%d %H:%M ")))
(_ 'age)))
(magit-set-buffer-margins nil t))
(transient-define-suffix magit-toggle-margin-details ()
"Show or hide details in the right margin."
:description "Toggle details"
:key "d"
:transient t
(interactive)
(unless (magit--right-margin-option)
(user-error "Magit margin isn't supported in this buffer"))
(setf (nth 3 magit--right-margin-config)
(not (nth 3 magit--right-margin-config)))
(magit-set-buffer-margins nil t))
;;; Core
(defun magit-set-buffer-margins (&optional reset-right refresh-right)
(let ((lmargin nil)
(rmargin nil)
(roption (magit--right-margin-option)))
(when (or lmargin roption)
(when roption
(let* ((default (symbol-value roption))
(default-width (nth 2 default)))
(when (or reset-right (not magit--right-margin-config))
(setq magit--right-margin-config (copy-sequence default)))
(pcase-let ((`(,enable ,style ,_width ,details ,details-width)
magit--right-margin-config))
(setq rmargin enable)
(when (functionp default-width)
(setf (nth 2 magit--right-margin-config)
(funcall default-width style details details-width))))))
(dolist (window (get-buffer-window-list nil nil 0))
(with-selected-window window
(magit-set-window-margins window)
(if (or lmargin rmargin)
(add-hook 'window-configuration-change-hook
#'magit-set-window-margins nil t)
(remove-hook 'window-configuration-change-hook
#'magit-set-window-margins t))))
(when (and rmargin (or refresh-right magit--right-margin-delayed))
(magit-refresh-buffer)))))
(defun magit-set-window-margins (&optional window)
(when (or window (setq window (get-buffer-window)))
(with-selected-window window
(set-window-margins
nil
(if (characterp (car (magit-section-visibility-indicator)))
1
(car (window-margins)))
(and (magit--right-margin-active)
(nth 2 magit--right-margin-config))))))
(cl-defun magit-make-margin-overlay (&optional string (previous-line nil sline))
"Display STRING in the margin of the previous (or current) line.
If point is at the beginning of a line, set the margin string for
the previous line, otherwise for the current line. Semi-obsolete
optional PREVIOUS-LINE can be used to explicitly specify which
line is affected."
(save-excursion
(forward-line (if (if sline previous-line (bolp)) -1 0))
;; Don't put the overlay on the complete line to work around #1880.
(let ((o (make-overlay (1+ (point)) (line-end-position) nil t)))
(overlay-put o 'evaporate t)
(overlay-put o 'before-string
(propertize "o" 'display
(list (list 'margin 'right-margin)
(or string " ")))))))
(defvar magit-margin-overlay-conditions
'( unpulled unpushed recent stashes local cherries
[remote branchbuf]
[shelved branchbuf]
[tags branchbuf]
topics discussions issues pullreqs))
(defun magit-maybe-make-margin-overlay ()
(when (magit-section-match magit-margin-overlay-conditions
magit-insert-section--current)
(magit-make-margin-overlay)))
;;; Custom Support
(defun magit-margin-set-variable (mode symbol value)
(set-default symbol value)
(message "Updating margins in %s buffers..." mode)
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (eq major-mode mode)
(magit-set-buffer-margins t)
(magit-refresh))))
(message "Updating margins in %s buffers...done" mode))
(defconst magit-log-margin--custom-type
'(list (boolean :tag "Show margin initially")
(choice :tag "Show committer"
(string :tag "date using time-format" "%Y-%m-%d %H:%M ")
(const :tag "date's age" age)
(const :tag "date's age (abbreviated)" age-abbreviated))
(const :tag "Calculate width using magit-log-margin-width"
magit-log-margin-width)
(boolean :tag "Show author name by default")
(integer :tag "Show author name using width")))
;;; Time Utilities
(defvar magit--age-spec
`((?Y "year" "years" ,(round (* 60 60 24 365.2425)))
(?M "month" "months" ,(round (* 60 60 24 30.436875)))
(?w "week" "weeks" ,(* 60 60 24 7))
(?d "day" "days" ,(* 60 60 24))
(?h "hour" "hours" ,(* 60 60))
(?m "minute" "minutes" 60)
(?s "second" "seconds" 1))
"Time units used when formatting relative commit ages.
The value is a list of time units, beginning with the longest.
Each element has the form (CHAR UNIT UNITS SECONDS). UNIT is the
time unit, UNITS is the plural of that unit. CHAR is a character
abbreviation. And SECONDS is the number of seconds in one UNIT.
This is defined as a variable to make it possible to use time
units for a language other than English. It is not defined
as an option, because most other parts of Magit are always in
English.")
(defun magit--age (date &optional abbreviate)
(named-let calc ((age (abs (- (float-time)
(if (stringp date)
(string-to-number date)
date))))
(spec magit--age-spec))
(pcase-let* ((`((,char ,unit ,units ,weight) . ,spec) spec)
(cnt (round (/ age weight 1.0))))
(if (or (not spec)
(>= (/ age weight) 1))
(list cnt (cond (abbreviate char)
((= cnt 1) unit)
(units)))
(calc age spec)))))
;;; _
(provide 'magit-margin)
;; Local Variables:
;; read-symbol-shorthands: (
;; ("and$" . "cond-let--and$")
;; ("and>" . "cond-let--and>")
;; ("and-let" . "cond-let--and-let")
;; ("if-let" . "cond-let--if-let")
;; ("when-let" . "cond-let--when-let")
;; ("while-let" . "cond-let--while-let")
;; ("match-string" . "match-string")
;; ("match-str" . "match-string-no-properties"))
;; End:
;;; magit-margin.el ends here
|