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