magit-margin.el (9206B)
1 ;;; magit-margin.el --- margins in Magit buffers -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2010-2021 The Magit Project Contributors 4 ;; 5 ;; You should have received a copy of the AUTHORS.md file which 6 ;; lists all contributors. If not, see http://magit.vc/authors. 7 8 ;; Author: Jonas Bernoulli <jonas@bernoul.li> 9 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> 10 11 ;; SPDX-License-Identifier: GPL-3.0-or-later 12 13 ;; Magit is free software; you can redistribute it and/or modify it 14 ;; under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation; either version 3, or (at your option) 16 ;; any later version. 17 ;; 18 ;; Magit is distributed in the hope that it will be useful, but WITHOUT 19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 20 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 21 ;; License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with Magit. If not, see http://www.gnu.org/licenses. 25 26 ;;; Commentary: 27 28 ;; This library implements support for showing additional information 29 ;; in the margins of Magit buffers. Currently this is only used for 30 ;; commits, for which the committer date or age, and optionally the 31 ;; author name are shown. 32 33 ;;; Code: 34 35 (require 'magit-section) 36 (require 'magit-transient) 37 (require 'magit-mode) 38 39 (defgroup magit-margin nil 40 "Information Magit displays in the margin. 41 42 You can change the STYLE and AUTHOR-WIDTH of all `magit-*-margin' 43 options to the same values by customizing `magit-log-margin' 44 *before* `magit' is loaded. If you do that, then the respective 45 values for the other options will default to what you have set 46 for that variable. Likewise if you set `magit-log-margin's INIT 47 to nil, then that is used in the default of all other options. But 48 setting it to t, i.e. re-enforcing the default for that option, 49 does not carry to other options." 50 :link '(info-link "(magit)Log Margin") 51 :group 'magit-log) 52 53 (defvar-local magit-buffer-margin nil) 54 (put 'magit-buffer-margin 'permanent-local t) 55 56 (defvar-local magit-set-buffer-margin-refresh nil) 57 58 (defvar magit--age-spec) 59 60 ;;; Commands 61 62 (transient-define-prefix magit-margin-settings () 63 "Change what information is displayed in the margin." 64 :info-manual "(magit) Log Margin" 65 ["Margin" 66 ("L" "Toggle visibility" magit-toggle-margin) 67 ("l" "Cycle style" magit-cycle-margin-style) 68 ("d" "Toggle details" magit-toggle-margin-details) 69 ("v" "Change verbosity" magit-refs-set-show-commit-count 70 :if-derived magit-refs-mode)]) 71 72 (defun magit-toggle-margin () 73 "Show or hide the Magit margin." 74 (interactive) 75 (unless (magit-margin-option) 76 (user-error "Magit margin isn't supported in this buffer")) 77 (setcar magit-buffer-margin (not (magit-buffer-margin-p))) 78 (magit-set-buffer-margin)) 79 80 (defun magit-cycle-margin-style () 81 "Cycle style used for the Magit margin." 82 (interactive) 83 (unless (magit-margin-option) 84 (user-error "Magit margin isn't supported in this buffer")) 85 ;; This is only suitable for commit margins (there are not others). 86 (setf (cadr magit-buffer-margin) 87 (pcase (cadr magit-buffer-margin) 88 (`age 'age-abbreviated) 89 (`age-abbreviated 90 (let ((default (cadr (symbol-value (magit-margin-option))))) 91 (if (stringp default) default "%Y-%m-%d %H:%M "))) 92 (_ 'age))) 93 (magit-set-buffer-margin nil t)) 94 95 (defun magit-toggle-margin-details () 96 "Show or hide details in the Magit margin." 97 (interactive) 98 (unless (magit-margin-option) 99 (user-error "Magit margin isn't supported in this buffer")) 100 (setf (nth 3 magit-buffer-margin) 101 (not (nth 3 magit-buffer-margin))) 102 (magit-set-buffer-margin nil t)) 103 104 ;;; Core 105 106 (defun magit-buffer-margin-p () 107 (car magit-buffer-margin)) 108 109 (defun magit-margin-option () 110 (pcase major-mode 111 (`magit-cherry-mode 'magit-cherry-margin) 112 (`magit-log-mode 'magit-log-margin) 113 (`magit-log-select-mode 'magit-log-select-margin) 114 (`magit-reflog-mode 'magit-reflog-margin) 115 (`magit-refs-mode 'magit-refs-margin) 116 (`magit-stashes-mode 'magit-stashes-margin) 117 (`magit-status-mode 'magit-status-margin) 118 (`forge-notifications-mode 'magit-status-margin))) 119 120 (defun magit-set-buffer-margin (&optional reset refresh) 121 (when-let ((option (magit-margin-option))) 122 (let* ((default (symbol-value option)) 123 (default-width (nth 2 default))) 124 (when (or reset (not magit-buffer-margin)) 125 (setq magit-buffer-margin (copy-sequence default))) 126 (pcase-let ((`(,enable ,style ,_width ,details ,details-width) 127 magit-buffer-margin)) 128 (when (functionp default-width) 129 (setf (nth 2 magit-buffer-margin) 130 (funcall default-width style details details-width))) 131 (dolist (window (get-buffer-window-list nil nil 0)) 132 (with-selected-window window 133 (magit-set-window-margin window) 134 (if enable 135 (add-hook 'window-configuration-change-hook 136 'magit-set-window-margin nil t) 137 (remove-hook 'window-configuration-change-hook 138 'magit-set-window-margin t)))) 139 (when (and enable (or refresh magit-set-buffer-margin-refresh)) 140 (magit-refresh-buffer)))))) 141 142 (defun magit-set-window-margin (&optional window) 143 (when (or window (setq window (get-buffer-window))) 144 (with-selected-window window 145 (set-window-margins 146 nil (car (window-margins)) 147 (and (magit-buffer-margin-p) 148 (nth 2 magit-buffer-margin)))))) 149 150 (defun magit-make-margin-overlay (&optional string previous-line) 151 (if previous-line 152 (save-excursion 153 (forward-line -1) 154 (magit-make-margin-overlay string)) 155 ;; Don't put the overlay on the complete line to work around #1880. 156 (let ((o (make-overlay (1+ (line-beginning-position)) 157 (line-end-position) 158 nil t))) 159 (overlay-put o 'evaporate t) 160 (overlay-put o 'before-string 161 (propertize "o" 'display 162 (list (list 'margin 'right-margin) 163 (or string " "))))))) 164 165 (defun magit-maybe-make-margin-overlay () 166 (when (or (magit-section-match 167 '(unpulled unpushed recent stashes local cherries) 168 magit-insert-section--current) 169 (and (eq major-mode 'magit-refs-mode) 170 (magit-section-match 171 '(remote commit tags) 172 magit-insert-section--current))) 173 (magit-make-margin-overlay nil t))) 174 175 ;;; Custom Support 176 177 (defun magit-margin-set-variable (mode symbol value) 178 (set-default symbol value) 179 (message "Updating margins in %s buffers..." mode) 180 (dolist (buffer (buffer-list)) 181 (with-current-buffer buffer 182 (when (eq major-mode mode) 183 (magit-set-buffer-margin t) 184 (magit-refresh)))) 185 (message "Updating margins in %s buffers...done" mode)) 186 187 (defconst magit-log-margin--custom-type 188 '(list (boolean :tag "Show margin initially") 189 (choice :tag "Show committer" 190 (string :tag "date using time-format" "%Y-%m-%d %H:%M ") 191 (const :tag "date's age" age) 192 (const :tag "date's age (abbreviated)" age-abbreviated)) 193 (const :tag "Calculate width using magit-log-margin-width" 194 magit-log-margin-width) 195 (boolean :tag "Show author name by default") 196 (integer :tag "Show author name using width"))) 197 198 ;;; Time Utilities 199 200 (defvar magit--age-spec 201 `((?Y "year" "years" ,(round (* 60 60 24 365.2425))) 202 (?M "month" "months" ,(round (* 60 60 24 30.436875))) 203 (?w "week" "weeks" ,(* 60 60 24 7)) 204 (?d "day" "days" ,(* 60 60 24)) 205 (?h "hour" "hours" ,(* 60 60)) 206 (?m "minute" "minutes" 60) 207 (?s "second" "seconds" 1)) 208 "Time units used when formatting relative commit ages. 209 210 The value is a list of time units, beginning with the longest. 211 Each element has the form (CHAR UNIT UNITS SECONDS). UNIT is the 212 time unit, UNITS is the plural of that unit. CHAR is a character 213 abbreviation. And SECONDS is the number of seconds in one UNIT. 214 215 This is defined as a variable to make it possible to use time 216 units for a language other than English. It is not defined 217 as an option, because most other parts of Magit are always in 218 English.") 219 220 (defun magit--age (date &optional abbreviate) 221 (cl-labels ((fn (age spec) 222 (pcase-let ((`(,char ,unit ,units ,weight) (car spec))) 223 (let ((cnt (round (/ age weight 1.0)))) 224 (if (or (not (cdr spec)) 225 (>= (/ age weight) 1)) 226 (list cnt (cond (abbreviate char) 227 ((= cnt 1) unit) 228 (t units))) 229 (fn age (cdr spec))))))) 230 (fn (abs (- (float-time) 231 (if (stringp date) 232 (string-to-number date) 233 date))) 234 magit--age-spec))) 235 236 ;;; _ 237 (provide 'magit-margin) 238 ;;; magit-margin.el ends here