| 1 | ;;; magit-mode.el --- Create and refresh Magit buffers -*- lexical-binding:t -*- |
| 2 | |
| 3 | ;; Copyright (C) 2008-2026 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 the abstract major-mode `magit-mode' from |
| 26 | ;; which almost all other Magit major-modes derive. The code in here |
| 27 | ;; is mostly concerned with creating and refreshing Magit buffers. |
| 28 | |
| 29 | ;;; Code: |
| 30 | |
| 31 | (require 'magit-base) |
| 32 | (require 'magit-git) |
| 33 | |
| 34 | (require 'benchmark) |
| 35 | (require 'browse-url) |
| 36 | (require 'help-mode) |
| 37 | |
| 38 | (require 'transient) |
| 39 | |
| 40 | (defvar bookmark-make-record-function) |
| 41 | |
| 42 | (eval-when-compile (require 'elp)) |
| 43 | (declare-function elp-reset-all "elp" ()) |
| 44 | (declare-function elp-instrument-package "elp" (prefix)) |
| 45 | (declare-function elp-results "elp" ()) |
| 46 | (declare-function elp-restore-all "elp" ()) |
| 47 | |
| 48 | (eval-when-compile (require 'hi-lock)) |
| 49 | (declare-function hi-lock-revert-buffer-rehighlight "hi-lock" ()) |
| 50 | |
| 51 | (defvar magit--wip-inhibit-autosave) |
| 52 | (defvar magit-wip-mode) |
| 53 | (declare-function magit-wip-get-ref "magit-wip" ()) |
| 54 | (declare-function magit-wip-commit-worktree "magit-wip" (ref files msg)) |
| 55 | |
| 56 | (declare-function magit--blob-cache-zap "magit-files" ()) |
| 57 | |
| 58 | ;;; Options |
| 59 | |
| 60 | (defcustom magit-mode-hook nil |
| 61 | "Hook run when entering a mode derived from Magit mode." |
| 62 | :package-version '(magit . "4.4.2") |
| 63 | :group 'magit-modes |
| 64 | :type 'hook |
| 65 | :options (list #'bug-reference-mode)) |
| 66 | |
| 67 | (defcustom magit-setup-buffer-hook |
| 68 | (list #'magit-maybe-save-repository-buffers |
| 69 | 'magit-set-buffer-margins) ; from magit-margin.el |
| 70 | "Hook run by `magit-setup-buffer'. |
| 71 | |
| 72 | This is run right after displaying the buffer and right before |
| 73 | generating or updating its content. `magit-mode-hook' and other, |
| 74 | more specific, `magit-mode-*-hook's on the other hand are run |
| 75 | right before displaying the buffer. Usually one of these hooks |
| 76 | should be used instead of this one." |
| 77 | :package-version '(magit . "2.3.0") |
| 78 | :group 'magit-modes |
| 79 | :type 'hook |
| 80 | :options (list #'magit-maybe-save-repository-buffers |
| 81 | 'magit-set-buffer-margins)) |
| 82 | |
| 83 | (defcustom magit-pre-refresh-hook |
| 84 | (list #'magit-maybe-save-repository-buffers) |
| 85 | "Hook run before refreshing in `magit-refresh'. |
| 86 | |
| 87 | This hook, or `magit-post-refresh-hook', should be used |
| 88 | for functions that are not tied to a particular buffer. |
| 89 | |
| 90 | To run a function with a particular buffer current, use |
| 91 | `magit-refresh-buffer-hook' and use `derived-mode-p' |
| 92 | inside your function." |
| 93 | :package-version '(magit . "2.4.0") |
| 94 | :group 'magit-refresh |
| 95 | :type 'hook |
| 96 | :options (list #'magit-maybe-save-repository-buffers)) |
| 97 | |
| 98 | (defcustom magit-post-refresh-hook |
| 99 | ;; Do not function-quote to avoid circular dependencies. |
| 100 | ;; Functions added here have to be autoloaded. |
| 101 | '(magit-auto-revert-buffers |
| 102 | magit-run-post-commit-hook |
| 103 | magit-run-post-stage-hook |
| 104 | magit-run-post-unstage-hook) |
| 105 | "Hook run after refreshing in `magit-refresh'. |
| 106 | |
| 107 | This hook, or `magit-pre-refresh-hook', should be used |
| 108 | for functions that are not tied to a particular buffer. |
| 109 | |
| 110 | To run a function with a particular buffer current, use |
| 111 | `magit-refresh-buffer-hook' and use `derived-mode-p' |
| 112 | inside your function." |
| 113 | :package-version '(magit . "2.4.0") |
| 114 | :group 'magit-refresh |
| 115 | :type 'hook |
| 116 | :options '(magit-auto-revert-buffers |
| 117 | magit-run-post-commit-hook |
| 118 | magit-run-post-stage-hook |
| 119 | magit-run-post-unstage-hook)) |
| 120 | |
| 121 | (defcustom magit-display-buffer-function #'magit-display-buffer-traditional |
| 122 | "The function used to display a Magit buffer. |
| 123 | |
| 124 | All Magit buffers (buffers whose major-modes derive from |
| 125 | `magit-mode') are displayed using `magit-display-buffer', |
| 126 | which in turn uses the function specified here." |
| 127 | :package-version '(magit . "2.3.0") |
| 128 | :group 'magit-buffers |
| 129 | :type `(radio (function-item ,#'magit-display-buffer-traditional) |
| 130 | (function-item ,#'magit-display-buffer-same-window-except-diff-v1) |
| 131 | (function-item ,#'magit-display-buffer-fullframe-status-v1) |
| 132 | (function-item ,#'magit-display-buffer-fullframe-status-topleft-v1) |
| 133 | (function-item ,#'magit-display-buffer-fullcolumn-most-v1) |
| 134 | (function-item ,#'display-buffer) |
| 135 | (function :tag "Function"))) |
| 136 | |
| 137 | (defcustom magit-pre-display-buffer-hook |
| 138 | (list #'magit-save-window-configuration) |
| 139 | "Hook run by `magit-display-buffer' before displaying the buffer." |
| 140 | :package-version '(magit . "2.3.0") |
| 141 | :group 'magit-buffers |
| 142 | :type 'hook |
| 143 | :get #'magit-hook-custom-get |
| 144 | :options (list #'magit-save-window-configuration)) |
| 145 | |
| 146 | (defcustom magit-post-display-buffer-hook (list #'magit-maybe-set-dedicated) |
| 147 | "Hook run by `magit-display-buffer' after displaying the buffer." |
| 148 | :package-version '(magit . "2.3.0") |
| 149 | :group 'magit-buffers |
| 150 | :type 'hook |
| 151 | :get #'magit-hook-custom-get |
| 152 | :options (list #'magit-maybe-set-dedicated)) |
| 153 | |
| 154 | (defcustom magit-generate-buffer-name-function |
| 155 | #'magit-generate-buffer-name-default-function |
| 156 | "The function used to generate the name for a Magit buffer." |
| 157 | :package-version '(magit . "2.3.0") |
| 158 | :group 'magit-buffers |
| 159 | :type `(radio (function-item ,#'magit-generate-buffer-name-default-function) |
| 160 | (function :tag "Function"))) |
| 161 | |
| 162 | (defcustom magit-buffer-name-format "%x%M%v: %t%x" |
| 163 | "The format string used to name Magit buffers. |
| 164 | |
| 165 | The following %-sequences are supported: |
| 166 | |
| 167 | `%m' The name of the major-mode, but with the `-mode' suffix |
| 168 | removed. |
| 169 | |
| 170 | `%M' Like \"%m\" but abbreviate `magit-status-mode' as `magit'. |
| 171 | |
| 172 | `%v' The value the buffer is locked to, in parentheses, or an |
| 173 | empty string if the buffer is not locked to a value. |
| 174 | |
| 175 | `%V' Like \"%v\", but the string is prefixed with a space, unless |
| 176 | it is an empty string. |
| 177 | |
| 178 | `%t' The top-level directory of the working tree of the |
| 179 | repository, or if `magit-uniquify-buffer-names' is non-nil |
| 180 | an abbreviation of that. |
| 181 | |
| 182 | `%x' If `magit-uniquify-buffer-names' is nil \"*\", otherwise the |
| 183 | empty string. Due to limitations of the `uniquify' package, |
| 184 | buffer names must end with the path. |
| 185 | |
| 186 | The value should always contain \"%m\" or \"%M\", \"%v\" or \"%V\", and |
| 187 | \"%t\". If `magit-uniquify-buffer-names' is non-nil, then the |
| 188 | value must end with \"%t\" or \"%t%x\". See issue #2841. |
| 189 | |
| 190 | This is used by `magit-generate-buffer-name-default-function'. |
| 191 | If another `magit-generate-buffer-name-function' is used, then |
| 192 | it may not respect this option, or on the contrary it may |
| 193 | support additional %-sequences." |
| 194 | :package-version '(magit . "2.12.0") |
| 195 | :group 'magit-buffers |
| 196 | :type 'string) |
| 197 | |
| 198 | (defcustom magit-uniquify-buffer-names t |
| 199 | "Whether to uniquify the names of Magit buffers." |
| 200 | :package-version '(magit . "2.3.0") |
| 201 | :group 'magit-buffers |
| 202 | :type 'boolean) |
| 203 | |
| 204 | (defcustom magit-bury-buffer-function #'magit-mode-quit-window |
| 205 | "The function used to bury or kill the current Magit buffer." |
| 206 | :package-version '(magit . "3.2.0") |
| 207 | :group 'magit-buffers |
| 208 | :type `(radio (function-item ,#'quit-window) |
| 209 | (function-item ,#'magit-mode-quit-window) |
| 210 | (function-item ,#'magit-restore-window-configuration) |
| 211 | (function :tag "Function"))) |
| 212 | |
| 213 | (defcustom magit-prefix-use-buffer-arguments 'selected |
| 214 | "Whether certain prefix commands reuse arguments active in relevant buffer. |
| 215 | |
| 216 | This affects the transient prefix commands `magit-diff', |
| 217 | `magit-log' and `magit-show-refs'. |
| 218 | |
| 219 | Valid values are: |
| 220 | |
| 221 | `always': Always use the set of arguments that is currently |
| 222 | active in the respective buffer, provided that buffer exists |
| 223 | of course. |
| 224 | `selected': Use the set of arguments from the respective |
| 225 | buffer, but only if it is displayed in a window of the current |
| 226 | frame. This is the default. |
| 227 | `current': Use the set of arguments from the respective buffer, |
| 228 | but only if it is the current buffer. |
| 229 | `never': Never use the set of arguments from the respective |
| 230 | buffer. |
| 231 | |
| 232 | For more information see info node `(magit)Transient Arguments |
| 233 | and Buffer Variables'." |
| 234 | :package-version '(magit . "3.0.0") |
| 235 | :group 'magit-buffers |
| 236 | :group 'magit-commands |
| 237 | :group 'magit-diff |
| 238 | :group 'magit-log |
| 239 | :type '(choice |
| 240 | (const :tag "Always use args from buffer" always) |
| 241 | (const :tag "Use args from buffer if displayed in frame" selected) |
| 242 | (const :tag "Use args from buffer if it is current" current) |
| 243 | (const :tag "Never use args from buffer" never))) |
| 244 | |
| 245 | (defcustom magit-direct-use-buffer-arguments 'selected |
| 246 | "Whether certain commands reuse arguments active in relevant buffer. |
| 247 | |
| 248 | This affects certain commands such as `magit-show-commit' that |
| 249 | are suffixes of the diff or log transient prefix commands, but |
| 250 | only if they are invoked directly, i.e., *not* as a suffix. |
| 251 | |
| 252 | Valid values are: |
| 253 | |
| 254 | `always': Always use the set of arguments that is currently |
| 255 | active in the respective buffer, provided that buffer exists |
| 256 | of course. |
| 257 | `selected': Use the set of arguments from the respective |
| 258 | buffer, but only if it is displayed in a window of the current |
| 259 | frame. This is the default. |
| 260 | `current': Use the set of arguments from the respective buffer, |
| 261 | but only if it is the current buffer. |
| 262 | `never': Never use the set of arguments from the respective |
| 263 | buffer. |
| 264 | |
| 265 | For more information see info node `(magit)Transient Arguments |
| 266 | and Buffer Variables'." |
| 267 | :package-version '(magit . "3.0.0") |
| 268 | :group 'magit-buffers |
| 269 | :group 'magit-commands |
| 270 | :group 'magit-diff |
| 271 | :group 'magit-log |
| 272 | :type '(choice |
| 273 | (const :tag "Always use args from buffer" always) |
| 274 | (const :tag "Use args from buffer if displayed in frame" selected) |
| 275 | (const :tag "Use args from buffer if it is current" current) |
| 276 | (const :tag "Never use args from buffer" never))) |
| 277 | |
| 278 | (defcustom magit-region-highlight-hook |
| 279 | '(magit-diff-update-hunk-region) ; from magit-diff.el |
| 280 | "Functions used to highlight the region. |
| 281 | |
| 282 | Each function is run with the current section as only argument |
| 283 | until one of them returns non-nil. If all functions return nil, |
| 284 | then fall back to regular region highlighting." |
| 285 | :package-version '(magit . "2.1.0") |
| 286 | :group 'magit-refresh |
| 287 | :type 'hook |
| 288 | :options '(magit-diff-update-hunk-region)) |
| 289 | |
| 290 | (defcustom magit-create-buffer-hook nil |
| 291 | "Normal hook run while creating a new `magit-mode' buffer. |
| 292 | Runs before the buffer is populated with sections. Also see |
| 293 | `magit-post-create-buffer-hook'." |
| 294 | :package-version '(magit . "2.90.0") |
| 295 | :group 'magit-refresh |
| 296 | :type 'hook) |
| 297 | |
| 298 | (defcustom magit-post-create-buffer-hook nil |
| 299 | "Normal hook run after creating a new `magit-mode' buffer. |
| 300 | Runs after the buffer is populated with sections for the first |
| 301 | time. Also see `magit-create-buffer-hook' (which runs earlier) |
| 302 | and `magit-refresh-buffer-hook' (which runs on every refresh)." |
| 303 | :package-version '(magit . "4.0.0") |
| 304 | :group 'magit-refresh |
| 305 | :type 'hook) |
| 306 | |
| 307 | (defcustom magit-refresh-buffer-hook nil |
| 308 | "Normal hook for `magit-refresh-buffer' to run after refreshing." |
| 309 | :package-version '(magit . "2.1.0") |
| 310 | :group 'magit-refresh |
| 311 | :type 'hook) |
| 312 | |
| 313 | (defcustom magit-refresh-status-buffer t |
| 314 | "Whether the status buffer is refreshed after running git. |
| 315 | |
| 316 | When this is non-nil, then the status buffer is automatically |
| 317 | refreshed after running git for side-effects, in addition to the |
| 318 | current Magit buffer, which is always refreshed automatically. |
| 319 | |
| 320 | Only set this to nil after exhausting all other options to |
| 321 | improve performance." |
| 322 | :package-version '(magit . "2.4.0") |
| 323 | :group 'magit-refresh |
| 324 | :group 'magit-status |
| 325 | :type 'boolean) |
| 326 | |
| 327 | (defcustom magit-refresh-verbose nil |
| 328 | "Whether to revert Magit buffers verbosely." |
| 329 | :package-version '(magit . "2.1.0") |
| 330 | :group 'magit-refresh |
| 331 | :type 'boolean) |
| 332 | |
| 333 | (defcustom magit-save-repository-buffers t |
| 334 | "Whether to save file-visiting buffers when appropriate. |
| 335 | |
| 336 | If non-nil, then all modified file-visiting buffers belonging |
| 337 | to the current repository may be saved before running Magit |
| 338 | commands and before creating or refreshing Magit buffers. |
| 339 | If `dontask', then this is done without user intervention, for |
| 340 | any other non-nil value the user has to confirm each save. |
| 341 | |
| 342 | The default is t to avoid surprises, but `dontask' is the |
| 343 | recommended value." |
| 344 | :group 'magit-essentials |
| 345 | :group 'magit-buffers |
| 346 | :type '(choice (const :tag "Never" nil) |
| 347 | (const :tag "Ask" t) |
| 348 | (const :tag "Save without asking" dontask))) |
| 349 | |
| 350 | ;;; Key Bindings |
| 351 | |
| 352 | (defvar-keymap magit-mode-map |
| 353 | :doc "Parent keymap for all keymaps of modes derived from `magit-mode'." |
| 354 | :parent magit-section-mode-map |
| 355 | ;; Don't function-quote but make sure all commands are autoloaded. |
| 356 | "C-<return>" 'magit-visit-thing |
| 357 | "RET" 'magit-visit-thing |
| 358 | "M-TAB" 'magit-dired-jump |
| 359 | "M-<tab>" 'magit-section-cycle-diffs |
| 360 | "SPC" 'magit-diff-show-or-scroll-up |
| 361 | "S-SPC" 'magit-diff-show-or-scroll-down |
| 362 | "DEL" 'magit-diff-show-or-scroll-down |
| 363 | "+" 'magit-diff-more-context |
| 364 | "-" 'magit-diff-less-context |
| 365 | "0" 'magit-diff-default-context |
| 366 | "a" 'magit-cherry-apply |
| 367 | "A" 'magit-cherry-pick |
| 368 | "b" 'magit-branch |
| 369 | "B" 'magit-bisect |
| 370 | "c" 'magit-commit |
| 371 | "C" 'magit-clone |
| 372 | "d" 'magit-diff |
| 373 | "D" 'magit-diff-refresh |
| 374 | "e" 'magit-ediff-dwim |
| 375 | "E" 'magit-ediff |
| 376 | "f" 'magit-fetch |
| 377 | "F" 'magit-pull |
| 378 | "g" 'magit-refresh |
| 379 | "G" 'magit-refresh-all |
| 380 | "h" 'magit-dispatch |
| 381 | "?" 'magit-dispatch |
| 382 | "H" 'magit-describe-section |
| 383 | "i" 'magit-gitignore |
| 384 | "I" 'magit-init |
| 385 | "j" 'magit-status-quick |
| 386 | "J" 'magit-display-repository-buffer |
| 387 | "k" 'magit-delete-thing |
| 388 | "K" 'magit-file-untrack |
| 389 | "l" 'magit-log |
| 390 | "L" 'magit-log-refresh |
| 391 | "m" 'magit-merge |
| 392 | "M" 'magit-remote |
| 393 | ;; "n" magit-section-forward in magit-section-mode-map |
| 394 | ;; "N" forge-dispatch, added by forge package |
| 395 | "o" 'magit-submodule |
| 396 | "O" 'magit-subtree |
| 397 | ;; "p" magit-section-backward in magit-section-mode-map |
| 398 | "P" 'magit-push |
| 399 | "q" 'magit-mode-bury-buffer |
| 400 | "Q" 'magit-git-command |
| 401 | ":" 'magit-git-command |
| 402 | "r" 'magit-rebase |
| 403 | "R" 'magit-file-rename |
| 404 | "s" 'magit-stage-files |
| 405 | "S" 'magit-stage-modified |
| 406 | "t" 'magit-tag |
| 407 | "T" 'magit-notes |
| 408 | "u" 'magit-unstage-files |
| 409 | "U" 'magit-unstage-all |
| 410 | "v" 'magit-revert-no-commit |
| 411 | "V" 'magit-revert |
| 412 | "w" 'magit-am |
| 413 | "W" 'magit-patch |
| 414 | "x" 'magit-reset-quickly |
| 415 | "X" 'magit-reset |
| 416 | "y" 'magit-show-refs |
| 417 | "Y" 'magit-cherry |
| 418 | "z" 'magit-stash |
| 419 | "Z" 'magit-worktree |
| 420 | "%" 'magit-worktree |
| 421 | "$" 'magit-process-buffer |
| 422 | "!" 'magit-run |
| 423 | ">" 'magit-sparse-checkout |
| 424 | "C-c C-c" 'magit-dispatch |
| 425 | "C-c C-r" 'magit-next-reference |
| 426 | "C-c C-e" 'magit-edit-thing |
| 427 | "C-c C-o" 'magit-browse-thing |
| 428 | "C-c C-w" 'magit-copy-thing |
| 429 | "C-w" 'magit-copy-section-value |
| 430 | "M-w" 'magit-copy-buffer-revision |
| 431 | "<remap> <mouse-set-point>" 'magit-mouse-set-point |
| 432 | "<remap> <back-to-indentation>" 'magit-back-to-indentation |
| 433 | "<remap> <previous-line>" 'magit-previous-line |
| 434 | "<remap> <next-line>" 'magit-next-line |
| 435 | "<remap> <evil-previous-line>" 'evil-previous-visual-line |
| 436 | "<remap> <evil-next-line>" 'evil-next-visual-line) |
| 437 | |
| 438 | (defun magit-delete-thing () |
| 439 | "This is a placeholder command, which signals an error if called. |
| 440 | Where applicable, other keymaps remap this command to another, |
| 441 | which actually deletes the thing at point." |
| 442 | (declare (completion ignore)) |
| 443 | (interactive) |
| 444 | (user-error "There is no thing at point that could be deleted")) |
| 445 | |
| 446 | (defun magit-visit-thing () |
| 447 | "This is a placeholder command, which may signal an error if called. |
| 448 | Where applicable, other keymaps remap this command to another, |
| 449 | which actually visits the thing at point." |
| 450 | (declare (completion ignore)) |
| 451 | (interactive) |
| 452 | (cond-let |
| 453 | ((eq transient-current-command 'magit-dispatch) |
| 454 | (call-interactively (key-binding (this-command-keys)))) |
| 455 | ([url (thing-at-point 'url t)] |
| 456 | (browse-url url)) |
| 457 | ((user-error "There is no thing at point that could be visited")))) |
| 458 | |
| 459 | (defun magit-edit-thing () |
| 460 | "This is a placeholder command, which may signal an error if called. |
| 461 | Where applicable, other keymaps remap this command to another, |
| 462 | which actually lets you edit the thing at point, likely in another |
| 463 | buffer." |
| 464 | (declare (completion ignore)) |
| 465 | (interactive) |
| 466 | (if (eq transient-current-command 'magit-dispatch) |
| 467 | (call-interactively (key-binding (this-command-keys))) |
| 468 | (user-error "There is no thing at point that could be edited"))) |
| 469 | |
| 470 | (defun magit-browse-thing () |
| 471 | "This is a placeholder command, which may signal an error if called. |
| 472 | Where applicable, other keymaps remap this command to another, |
| 473 | which actually visits thing at point using `browse-url'." |
| 474 | (declare (completion ignore)) |
| 475 | (interactive) |
| 476 | (if-let ((url (thing-at-point 'url t))) |
| 477 | (browse-url url) |
| 478 | (user-error "There is no thing at point that could be browsed"))) |
| 479 | |
| 480 | (defun magit-copy-thing () |
| 481 | "This is a placeholder command, which signals an error if called. |
| 482 | Where applicable, other keymaps remap this command to another, |
| 483 | which actually copies some representation of the thing at point |
| 484 | to the kill ring." |
| 485 | (declare (completion ignore)) |
| 486 | (interactive) |
| 487 | (user-error "There is no thing at point that we know how to copy")) |
| 488 | |
| 489 | ;;;###autoload |
| 490 | (defun magit-info () |
| 491 | "Visit the Magit manual." |
| 492 | (interactive) |
| 493 | (info "magit")) |
| 494 | |
| 495 | (defvar bug-reference-map) |
| 496 | (with-eval-after-load 'bug-reference |
| 497 | (keymap-set bug-reference-map "<remap> <magit-visit-thing>" |
| 498 | 'bug-reference-push-button)) |
| 499 | |
| 500 | (easy-menu-define magit-mode-menu magit-mode-map |
| 501 | "Magit menu." |
| 502 | ;; Similar to `magit-dispatch' but exclude: |
| 503 | ;; - commands that are available from context menus: |
| 504 | ;; apply, reverse, discard, stage, unstage, |
| 505 | ;; cherry-pick, revert, reset, |
| 506 | ;; describe-section |
| 507 | ;; - commands that are available from submenus: |
| 508 | ;; git-command, ediff-dwim |
| 509 | ;; - and: refresh-all, status-jump, status-quick. |
| 510 | '("Magit" |
| 511 | "---" "Inspect" |
| 512 | [" Bisect..." magit-bisect t] |
| 513 | [" Cherries..." magit-cherry t] |
| 514 | [" Diff..." magit-diff t] |
| 515 | [" Ediff..." magit-ediff t] |
| 516 | [" Log..." magit-log t] |
| 517 | [" References..." magit-show-refs t] |
| 518 | "---" "Manipulate" |
| 519 | [" Commit..." magit-commit t] |
| 520 | [" Stash..." magit-stash t] |
| 521 | [" Tag..." magit-tag t] |
| 522 | "---" |
| 523 | [" Branch..." magit-branch t] |
| 524 | [" Remote..." magit-remote t] |
| 525 | "---" |
| 526 | [" Merge..." magit-merge t] |
| 527 | [" Rebase..." magit-rebase t] |
| 528 | "---" "Transfer" |
| 529 | [" Fetch..." magit-fetch t] |
| 530 | [" Pull..." magit-pull t] |
| 531 | [" Push..." magit-push t] |
| 532 | "---" "Setup" |
| 533 | [" Clone..." magit-clone t] |
| 534 | [" Ignore..." magit-gitignore t] |
| 535 | [" Init..." magit-init t] |
| 536 | "---" |
| 537 | ("Advanced" |
| 538 | ["Run..." magit-run t] |
| 539 | "---" |
| 540 | ["Apply patches..." magit-am t] |
| 541 | ["Format patches..." magit-patch t] |
| 542 | "---" |
| 543 | ["Note..." magit-notes t] |
| 544 | "---" |
| 545 | ["Submodule..." magit-submodule t] |
| 546 | ["Subtree..." magit-subtree t] |
| 547 | ["Worktree..." magit-worktree t]) |
| 548 | "---" |
| 549 | ["Show command dispatcher..." magit-dispatch t] |
| 550 | ["Show manual" magit-info t] |
| 551 | ["Show another buffer" magit-display-repository-buffer t] |
| 552 | "---" |
| 553 | ("Change buffer arguments" |
| 554 | ["Diff arguments" magit-diff-refresh t] |
| 555 | ["Log arguments" magit-log-refresh t]) |
| 556 | ["Refresh buffer" magit-refresh t] |
| 557 | ["Bury buffer" magit-mode-bury-buffer t])) |
| 558 | |
| 559 | ;;; Mode |
| 560 | |
| 561 | (define-derived-mode magit-mode magit-section-mode "Magit" |
| 562 | "Parent major mode from which Magit major modes inherit. |
| 563 | |
| 564 | Magit is documented in info node `(magit)'." |
| 565 | :interactive nil |
| 566 | :group 'magit |
| 567 | (magit-hack-dir-local-variables) |
| 568 | (face-remap-add-relative 'header-line 'magit-header-line) |
| 569 | (setq mode-line-process (magit-repository-local-get 'mode-line-process)) |
| 570 | (setq-local revert-buffer-function #'magit-revert-buffer) |
| 571 | (setq-local bookmark-make-record-function #'magit--make-bookmark) |
| 572 | (setq-local imenu-create-index-function #'magit--imenu-create-index) |
| 573 | (setq-local imenu-default-goto-function #'magit--imenu-goto-function) |
| 574 | (setq-local isearch-filter-predicate #'magit-section--open-temporarily) |
| 575 | (setq-local hi-lock-use-overlays t)) |
| 576 | |
| 577 | (defun magit-hack-dir-local-variables () |
| 578 | "Like `hack-dir-local-variables-non-file-buffer' but ignore some variables." |
| 579 | (let ((ignored-local-variables |
| 580 | `(show-trailing-whitespace |
| 581 | ,@ignored-local-variables))) |
| 582 | (hack-dir-local-variables-non-file-buffer))) |
| 583 | |
| 584 | ;;; Local Variables |
| 585 | |
| 586 | (defvaralias 'magit-buffer-refname 'magit-buffer-revision) |
| 587 | (defvar-local magit-buffer-revision nil) |
| 588 | (defvar-local magit-buffer-revision-oid nil) |
| 589 | (defvar-local magit-buffer-blob-oid nil) |
| 590 | (defvar-local magit-buffer-file-name nil) |
| 591 | |
| 592 | ;; Preserve when major-mode is changed in file-visiting buffers. |
| 593 | (put 'magit-buffer-revision 'permanent-local t) |
| 594 | (put 'magit-buffer-revision-oid 'permanent-local t) |
| 595 | (put 'magit-buffer-blob-oid 'permanent-local t) |
| 596 | (put 'magit-buffer-file-name 'permanent-local t) |
| 597 | |
| 598 | (eval-and-compile |
| 599 | (defvar magit-define-aliases-for:magit-buffer-* t) |
| 600 | (when magit-define-aliases-for:magit-buffer-* |
| 601 | ;; Unfortunately defvar-local can only be used at top-level, |
| 602 | ;; so instead we have to use make-variable-buffer-local below. |
| 603 | (defvar magit-buffer-arguments nil) |
| 604 | (make-obsolete-variable 'magit-buffer-arguments |
| 605 | "use a mode- or package-specific `magit-buffer-{*}-args' instead" |
| 606 | "magit 4.6.0") |
| 607 | (defvar magit-buffer-upstream nil) |
| 608 | (make-obsolete-variable 'magit-buffer-upstream |
| 609 | "use a mode- or package-specific `magit-buffer-{*}-upstream' instead" |
| 610 | "magit 4.6.0") |
| 611 | (define-obsolete-variable-alias 'magit-buffer-range-hashed |
| 612 | 'magit-buffer-diff-range-oids "magit 4.6.0") |
| 613 | (define-obsolete-variable-alias 'magit-buffer-revisions |
| 614 | 'magit-buffer-log-revisions "magit 4.6.0") |
| 615 | (define-obsolete-variable-alias 'magit-buffer-revision-hash |
| 616 | 'magit-buffer-revision-oid "magit 4.6.0") |
| 617 | (define-obsolete-variable-alias 'magit-buffer-typearg |
| 618 | 'magit-buffer-diff-typearg "magit 4.6.0"))) |
| 619 | (make-variable-buffer-local 'magit-buffer-arguments) |
| 620 | (make-variable-buffer-local 'magit-buffer-upstream) |
| 621 | |
| 622 | (defun magit-buffer-file-name () |
| 623 | "Return `magit-buffer-file-name' or if that is nil `buffer-file-name'. |
| 624 | In an indirect buffer get the value for its base buffer." |
| 625 | (or magit-buffer-file-name |
| 626 | (buffer-file-name (buffer-base-buffer)))) |
| 627 | |
| 628 | (defun magit-buffer-revision () |
| 629 | "Return `magit-buffer-revision' or if that is nil \"{worktree}\". |
| 630 | If not visiting a blob or file, or the file isn't being tracked, |
| 631 | return nil. If visiting a blob but `magit-buffer-revision' is nil, |
| 632 | return nil." |
| 633 | (or magit-buffer-revision |
| 634 | (and buffer-file-name |
| 635 | (magit-file-tracked-p buffer-file-name) |
| 636 | "{worktree}"))) |
| 637 | |
| 638 | (cl-defgeneric magit-buffer-value () |
| 639 | "Return the value of the current buffer. |
| 640 | The \"value\" identifies what is being displayed in the buffer. |
| 641 | The buffer's major-mode should derive from `magit-section-mode'." |
| 642 | nil) |
| 643 | |
| 644 | (defvar-local magit-previous-section nil) |
| 645 | (put 'magit-previous-section 'permanent-local t) |
| 646 | |
| 647 | ;;; Setup Buffer |
| 648 | |
| 649 | (defmacro magit-setup-buffer (mode &optional locked &rest args) |
| 650 | "\n\n(fn MODE &optional LOCKED &key BUFFER DIRECTORY \ |
| 651 | INITIAL-SECTION SELECT-SECTION &rest BINDINGS)" |
| 652 | (declare (indent 2) |
| 653 | (debug (form [&optional locked] |
| 654 | [&rest keywordp form] |
| 655 | [&rest (symbolp form)]))) |
| 656 | (let (kwargs) |
| 657 | (while (keywordp (car args)) |
| 658 | (push (pop args) kwargs) |
| 659 | (push (pop args) kwargs)) |
| 660 | `(magit-setup-buffer-internal |
| 661 | ,mode ,locked |
| 662 | ,(cons 'list (mapcar (pcase-lambda (`(,var ,form)) |
| 663 | `(list ',var ,form)) |
| 664 | args)) |
| 665 | ,@(nreverse kwargs)))) |
| 666 | |
| 667 | (cl-defun magit-setup-buffer-internal |
| 668 | ( mode locked bindings |
| 669 | &key buffer directory initial-section select-section) |
| 670 | (let* ((value (and locked |
| 671 | (with-temp-buffer |
| 672 | (mapc (##apply #'set-local %) bindings) |
| 673 | (let ((major-mode mode)) |
| 674 | (magit-buffer-value))))) |
| 675 | (buffer (if buffer |
| 676 | (get-buffer-create buffer) |
| 677 | (magit-get-mode-buffer mode value))) |
| 678 | (section (and buffer (magit-current-section))) |
| 679 | (created (not buffer))) |
| 680 | (unless buffer |
| 681 | (setq buffer (magit-generate-new-buffer mode value))) |
| 682 | (with-current-buffer buffer |
| 683 | (setq magit-previous-section section) |
| 684 | (when directory |
| 685 | (setq default-directory directory)) |
| 686 | (funcall mode) |
| 687 | (magit-xref-setup #'magit-setup-buffer-internal bindings) |
| 688 | (mapc (##apply #'set-local %) bindings) |
| 689 | (when created |
| 690 | (run-hooks 'magit-create-buffer-hook))) |
| 691 | (magit-display-buffer buffer) |
| 692 | (with-current-buffer buffer |
| 693 | (run-hooks 'magit-setup-buffer-hook) |
| 694 | (magit-refresh-buffer created |
| 695 | :initial-section initial-section |
| 696 | :select-section select-section) |
| 697 | (when created |
| 698 | (run-hooks 'magit-post-create-buffer-hook))) |
| 699 | buffer)) |
| 700 | |
| 701 | ;;; Display Buffer |
| 702 | |
| 703 | (defvar magit-display-buffer-noselect nil |
| 704 | "If non-nil, then `magit-display-buffer' doesn't call `select-window'.") |
| 705 | |
| 706 | (defun magit-display-buffer (buffer &optional display-function) |
| 707 | "Display BUFFER in some window and maybe select it. |
| 708 | |
| 709 | If optional DISPLAY-FUNCTION is non-nil, then use that to display |
| 710 | the buffer. Otherwise use `magit-display-buffer-function', which |
| 711 | is the normal case. |
| 712 | |
| 713 | Then, unless `magit-display-buffer-noselect' is non-nil, select |
| 714 | the window which was used to display the buffer. |
| 715 | |
| 716 | Also run the hooks `magit-pre-display-buffer-hook' |
| 717 | and `magit-post-display-buffer-hook'." |
| 718 | (with-current-buffer buffer |
| 719 | (run-hooks 'magit-pre-display-buffer-hook)) |
| 720 | (let ((window (funcall (or display-function magit-display-buffer-function) |
| 721 | buffer))) |
| 722 | (unless magit-display-buffer-noselect |
| 723 | (let ((old-frame (selected-frame)) |
| 724 | (new-frame (window-frame window))) |
| 725 | (select-window window) |
| 726 | (unless (eq old-frame new-frame) |
| 727 | (select-frame-set-input-focus new-frame))))) |
| 728 | (with-current-buffer buffer |
| 729 | (run-hooks 'magit-post-display-buffer-hook))) |
| 730 | |
| 731 | (defun magit-display-buffer-traditional (buffer) |
| 732 | "Display BUFFER the way this has traditionally been done." |
| 733 | (display-buffer |
| 734 | buffer (if (and (derived-mode-p 'magit-mode) |
| 735 | (not (memq (with-current-buffer buffer major-mode) |
| 736 | '(magit-process-mode |
| 737 | magit-revision-mode |
| 738 | magit-diff-mode |
| 739 | magit-stash-mode |
| 740 | magit-status-mode)))) |
| 741 | '(display-buffer-same-window) |
| 742 | nil))) ; display in another window |
| 743 | |
| 744 | (defun magit-display-buffer-same-window-except-diff-v1 (buffer) |
| 745 | "Display BUFFER in the selected window except for some modes. |
| 746 | If a buffer's `major-mode' derives from `magit-diff-mode' or |
| 747 | `magit-process-mode', display it in another window. Display all |
| 748 | other buffers in the selected window." |
| 749 | (display-buffer |
| 750 | buffer (if (with-current-buffer buffer |
| 751 | (derived-mode-p 'magit-diff-mode 'magit-process-mode)) |
| 752 | '(nil (inhibit-same-window . t)) |
| 753 | '(display-buffer-same-window)))) |
| 754 | |
| 755 | (defun magit--display-buffer-fullframe (buffer alist) |
| 756 | (when-let ((window (or (display-buffer-reuse-window buffer alist) |
| 757 | (display-buffer-same-window buffer alist) |
| 758 | (display-buffer-pop-up-window buffer alist) |
| 759 | (display-buffer-use-some-window buffer alist)))) |
| 760 | (delete-other-windows window) |
| 761 | window)) |
| 762 | |
| 763 | (defun magit-display-buffer-fullframe-status-v1 (buffer) |
| 764 | "Display BUFFER, filling entire frame if BUFFER is a status buffer. |
| 765 | Otherwise, behave like `magit-display-buffer-traditional'." |
| 766 | (if (eq (with-current-buffer buffer major-mode) |
| 767 | 'magit-status-mode) |
| 768 | (display-buffer buffer '(magit--display-buffer-fullframe)) |
| 769 | (magit-display-buffer-traditional buffer))) |
| 770 | |
| 771 | (defun magit--display-buffer-topleft (buffer alist) |
| 772 | (or (display-buffer-reuse-window buffer alist) |
| 773 | (when-let ((window2 (display-buffer-pop-up-window buffer alist))) |
| 774 | (let ((window1 (get-buffer-window)) |
| 775 | (buffer1 (current-buffer)) |
| 776 | (buffer2 (window-buffer window2)) |
| 777 | (w2-quit-restore (window-parameter window2 'quit-restore))) |
| 778 | (set-window-buffer window1 buffer2) |
| 779 | (set-window-buffer window2 buffer1) |
| 780 | (select-window window2) |
| 781 | ;; Swap some window state that `magit-mode-quit-window' and |
| 782 | ;; `quit-restore-window' inspect. |
| 783 | (set-window-prev-buffers window2 (cdr (window-prev-buffers window1))) |
| 784 | (set-window-prev-buffers window1 nil) |
| 785 | (set-window-parameter window2 'magit-dedicated |
| 786 | (window-parameter window1 'magit-dedicated)) |
| 787 | (set-window-parameter window1 'magit-dedicated t) |
| 788 | (set-window-parameter window1 'quit-restore |
| 789 | (list 'window 'window |
| 790 | (nth 2 w2-quit-restore) |
| 791 | (nth 3 w2-quit-restore))) |
| 792 | (set-window-parameter window2 'quit-restore nil) |
| 793 | window1)))) |
| 794 | |
| 795 | (defun magit-display-buffer-fullframe-status-topleft-v1 (buffer) |
| 796 | "Display BUFFER, filling entire frame if BUFFER is a status buffer. |
| 797 | When BUFFER derives from `magit-diff-mode' or |
| 798 | `magit-process-mode', try to display BUFFER to the top or left of |
| 799 | the current buffer rather than to the bottom or right, as |
| 800 | `magit-display-buffer-fullframe-status-v1' would. Whether the |
| 801 | split is made vertically or horizontally is determined by |
| 802 | `split-window-preferred-function'." |
| 803 | (display-buffer |
| 804 | buffer |
| 805 | (cond ((eq (with-current-buffer buffer major-mode) |
| 806 | 'magit-status-mode) |
| 807 | '(magit--display-buffer-fullframe)) |
| 808 | ((with-current-buffer buffer |
| 809 | (derived-mode-p 'magit-diff-mode 'magit-process-mode)) |
| 810 | '(magit--display-buffer-topleft)) |
| 811 | ('(display-buffer-same-window))))) |
| 812 | |
| 813 | (defun magit--display-buffer-fullcolumn (buffer alist) |
| 814 | (when-let ((window (or (display-buffer-reuse-window buffer alist) |
| 815 | (display-buffer-same-window buffer alist) |
| 816 | (display-buffer-below-selected buffer alist)))) |
| 817 | (delete-other-windows-vertically window) |
| 818 | window)) |
| 819 | |
| 820 | (defun magit-display-buffer-fullcolumn-most-v1 (buffer) |
| 821 | "Display BUFFER using the full column except in some cases. |
| 822 | For most cases where BUFFER's `major-mode' derives from |
| 823 | `magit-mode', display it in the selected window and grow that |
| 824 | window to the full height of the frame, deleting other windows in |
| 825 | that column as necessary. However, display BUFFER in another |
| 826 | window if 1) BUFFER's mode derives from `magit-process-mode', or |
| 827 | 2) BUFFER's mode derives from `magit-diff-mode', provided that |
| 828 | the mode of the current buffer derives from `magit-log-mode' or |
| 829 | `magit-cherry-mode'." |
| 830 | (display-buffer |
| 831 | buffer |
| 832 | (cond ((and (or (bound-and-true-p git-commit-mode) |
| 833 | (derived-mode-p 'magit-log-mode |
| 834 | 'magit-cherry-mode |
| 835 | 'magit-reflog-mode)) |
| 836 | (with-current-buffer buffer |
| 837 | (derived-mode-p 'magit-diff-mode))) |
| 838 | nil) |
| 839 | ((with-current-buffer buffer |
| 840 | (derived-mode-p 'magit-process-mode)) |
| 841 | nil) |
| 842 | ('(magit--display-buffer-fullcolumn))))) |
| 843 | |
| 844 | (defun magit-maybe-set-dedicated () |
| 845 | "Mark the selected window as dedicated if appropriate. |
| 846 | |
| 847 | If a new window was created to display the buffer, then remember |
| 848 | that fact. That information is used by `magit-mode-quit-window', |
| 849 | to determine whether the window should be deleted when its last |
| 850 | Magit buffer is buried." |
| 851 | (let ((window (get-buffer-window (current-buffer)))) |
| 852 | (when (and (window-live-p window) |
| 853 | (not (window-prev-buffers window))) |
| 854 | (set-window-parameter window 'magit-dedicated t)))) |
| 855 | |
| 856 | ;;; Get Buffer |
| 857 | |
| 858 | (defvar-local magit--default-directory nil |
| 859 | "Value of `default-directory' when buffer is generated. |
| 860 | This exists to prevent a let-bound `default-directory' from |
| 861 | tricking `magit-get-mode-buffer' or `magit-mode-get-buffers' |
| 862 | into thinking a buffer belongs to a repo that it doesn't.") |
| 863 | (put 'magit--default-directory 'permanent-local t) |
| 864 | |
| 865 | (defun magit-mode-get-buffers () |
| 866 | (let ((topdir (magit-toplevel))) |
| 867 | (seq-filter (##with-current-buffer % |
| 868 | (and (derived-mode-p 'magit-mode) |
| 869 | (equal magit--default-directory topdir))) |
| 870 | (buffer-list)))) |
| 871 | |
| 872 | (defvar-local magit-buffer-locked-p nil) |
| 873 | (put 'magit-buffer-locked-p 'permanent-local t) |
| 874 | |
| 875 | (defun magit-get-mode-buffer (mode &optional value frame) |
| 876 | "Return buffer belonging to the current repository whose major-mode is MODE. |
| 877 | |
| 878 | If no such buffer exists then return nil. Multiple buffers with |
| 879 | the same major-mode may exist for a repository but only one can |
| 880 | exist that hasn't been locked to its value. Return that buffer |
| 881 | \(or nil if there is no such buffer) unless VALUE is non-nil, in |
| 882 | which case return the buffer that has been locked to that value. |
| 883 | |
| 884 | If FRAME is nil or omitted, then consider all buffers. Otherwise |
| 885 | only consider buffers that are displayed in some live window |
| 886 | on some frame. |
| 887 | If `all', then consider all buffers on all frames. |
| 888 | If `visible', then only consider buffers on all visible frames. |
| 889 | If `selected' or t, then only consider buffers on the selected |
| 890 | frame. |
| 891 | If a frame, then only consider buffers on that frame." |
| 892 | (let ((topdir (magit--toplevel-safe))) |
| 893 | (cl-flet* ((b (buffer) |
| 894 | (with-current-buffer buffer |
| 895 | (and (eq major-mode mode) |
| 896 | (equal magit--default-directory topdir) |
| 897 | (if value |
| 898 | (and magit-buffer-locked-p |
| 899 | (equal (magit-buffer-value) value)) |
| 900 | (not magit-buffer-locked-p)) |
| 901 | buffer))) |
| 902 | (w (window) |
| 903 | (b (window-buffer window))) |
| 904 | (f (frame) |
| 905 | (seq-some #'w (window-list frame 'no-minibuf)))) |
| 906 | (pcase-exhaustive frame |
| 907 | ('nil (seq-some #'b (buffer-list))) |
| 908 | ('all (seq-some #'f (frame-list))) |
| 909 | ('visible (seq-some #'f (visible-frame-list))) |
| 910 | ((or 'selected 't) (seq-some #'w (window-list (selected-frame)))) |
| 911 | ((guard (framep frame)) (seq-some #'w (window-list frame))))))) |
| 912 | |
| 913 | (defun magit-generate-new-buffer (mode &optional value directory) |
| 914 | (let* ((default-directory (or directory (magit--toplevel-safe))) |
| 915 | (name (funcall magit-generate-buffer-name-function mode value)) |
| 916 | (buffer (generate-new-buffer name))) |
| 917 | (with-current-buffer buffer |
| 918 | (setq magit--default-directory default-directory) |
| 919 | (setq magit-buffer-locked-p (and value t)) |
| 920 | (magit-restore-section-visibility-cache mode)) |
| 921 | (magit--maybe-uniquify-buffer-names buffer name mode) |
| 922 | buffer)) |
| 923 | |
| 924 | (defun magit-generate-buffer-name-default-function (mode &optional value) |
| 925 | "Generate buffer name for a MODE buffer in the current repository. |
| 926 | The returned name is based on `magit-buffer-name-format' and |
| 927 | takes `magit-uniquify-buffer-names' and VALUE, if non-nil, into |
| 928 | account." |
| 929 | (let ((m (substring (symbol-name mode) 0 -5)) |
| 930 | (v (and value (format "%s" (ensure-list value)))) |
| 931 | (n (if magit-uniquify-buffer-names |
| 932 | (file-name-nondirectory |
| 933 | (directory-file-name default-directory)) |
| 934 | (abbreviate-file-name default-directory)))) |
| 935 | (format-spec |
| 936 | magit-buffer-name-format |
| 937 | `((?m . ,m) |
| 938 | (?M . ,(if (eq mode 'magit-status-mode) "magit" m)) |
| 939 | (?v . ,(or v "")) |
| 940 | (?V . ,(if v (concat " " v) "")) |
| 941 | (?t . ,n) |
| 942 | (?x . ,(if magit-uniquify-buffer-names "" "*")))))) |
| 943 | |
| 944 | (defun magit--maybe-uniquify-buffer-names (buffer name mode) |
| 945 | (when magit-uniquify-buffer-names |
| 946 | (cl-pushnew mode uniquify-list-buffers-directory-modes) |
| 947 | (with-current-buffer buffer |
| 948 | (setq list-buffers-directory (abbreviate-file-name default-directory))) |
| 949 | (let ((uniquify-buffer-name-style |
| 950 | (if (memq uniquify-buffer-name-style '(nil forward)) |
| 951 | 'post-forward-angle-brackets |
| 952 | uniquify-buffer-name-style))) |
| 953 | (uniquify-rationalize-file-buffer-names |
| 954 | name (file-name-parent-directory default-directory) buffer)))) |
| 955 | |
| 956 | ;;; Buffer Lock |
| 957 | |
| 958 | (defun magit-toggle-buffer-lock () |
| 959 | "Lock the current buffer to its value or unlock it. |
| 960 | |
| 961 | Locking a buffer to its value prevents it from being reused to |
| 962 | display another value. The name of a locked buffer contains its |
| 963 | value, which allows telling it apart from other locked buffers |
| 964 | and the unlocked buffer. |
| 965 | |
| 966 | Not all Magit buffers can be locked to their values, for example |
| 967 | it wouldn't make sense to lock a status buffer. |
| 968 | |
| 969 | There can only be a single unlocked buffer using a certain |
| 970 | major-mode per repository. So when a buffer is being unlocked |
| 971 | and another unlocked buffer already exists for that mode and |
| 972 | repository, then the former buffer is instead deleted and the |
| 973 | latter is displayed in its place." |
| 974 | (interactive) |
| 975 | (cond-let |
| 976 | (magit-buffer-locked-p |
| 977 | (if-let ((unlocked (magit-get-mode-buffer major-mode))) |
| 978 | (let ((locked (current-buffer))) |
| 979 | (switch-to-buffer unlocked nil t) |
| 980 | (kill-buffer locked)) |
| 981 | (setq magit-buffer-locked-p nil) |
| 982 | (let ((name (funcall magit-generate-buffer-name-function major-mode)) |
| 983 | (buffer (current-buffer)) |
| 984 | (mode major-mode)) |
| 985 | (rename-buffer (generate-new-buffer-name name)) |
| 986 | (with-temp-buffer |
| 987 | (magit--maybe-uniquify-buffer-names buffer name mode))))) |
| 988 | ([value (magit-buffer-value)] |
| 989 | (if-let ((locked (magit-get-mode-buffer major-mode value))) |
| 990 | (let ((unlocked (current-buffer))) |
| 991 | (switch-to-buffer locked nil t) |
| 992 | (kill-buffer unlocked)) |
| 993 | (setq magit-buffer-locked-p t) |
| 994 | (let ((name (funcall magit-generate-buffer-name-function |
| 995 | major-mode value)) |
| 996 | (buffer (current-buffer)) |
| 997 | (mode major-mode)) |
| 998 | (rename-buffer (generate-new-buffer-name name)) |
| 999 | (with-temp-buffer |
| 1000 | (magit--maybe-uniquify-buffer-names buffer name mode))))) |
| 1001 | ((user-error "Buffer has no value it could be locked to")))) |
| 1002 | |
| 1003 | ;;; Bury Buffer |
| 1004 | |
| 1005 | (defun magit-mode-bury-buffer (&optional kill-buffer) |
| 1006 | "Bury or kill the current buffer. |
| 1007 | |
| 1008 | Use `magit-bury-buffer-function' to bury the buffer when called |
| 1009 | without a prefix argument or to kill it when called with a single |
| 1010 | prefix argument. |
| 1011 | |
| 1012 | With two prefix arguments, always kill the current and all other |
| 1013 | Magit buffers, associated with this repository." |
| 1014 | (interactive "P") |
| 1015 | (if (>= (prefix-numeric-value kill-buffer) 16) |
| 1016 | (mapc #'kill-buffer (magit-mode-get-buffers)) |
| 1017 | (funcall magit-bury-buffer-function kill-buffer))) |
| 1018 | |
| 1019 | (defun magit-mode-quit-window (kill-buffer) |
| 1020 | "Quit the selected window and bury its buffer. |
| 1021 | |
| 1022 | This behaves similar to `quit-window', but when the window |
| 1023 | was originally created to display a Magit buffer and the |
| 1024 | current buffer is the last remaining Magit buffer that was |
| 1025 | ever displayed in the selected window, then delete that |
| 1026 | window." |
| 1027 | (if (or (one-window-p) |
| 1028 | (seq-find (pcase-lambda (`(,buffer)) |
| 1029 | (and (not (eq buffer (current-buffer))) |
| 1030 | (buffer-live-p buffer) |
| 1031 | (or (not (window-parameter nil 'magit-dedicated)) |
| 1032 | (with-current-buffer buffer |
| 1033 | (derived-mode-p 'magit-mode |
| 1034 | 'magit-process-mode))))) |
| 1035 | (window-prev-buffers))) |
| 1036 | (quit-window kill-buffer) |
| 1037 | (let ((window (selected-window))) |
| 1038 | (quit-window kill-buffer) |
| 1039 | (when (window-live-p window) |
| 1040 | (delete-window window))))) |
| 1041 | |
| 1042 | ;;; Refresh Buffers |
| 1043 | |
| 1044 | (defvar magit-inhibit-refresh nil) |
| 1045 | |
| 1046 | (defun magit-refresh () |
| 1047 | "Refresh some buffers belonging to the current repository. |
| 1048 | |
| 1049 | Refresh the current buffer if its major mode derives from |
| 1050 | `magit-mode', and refresh the corresponding status buffer. |
| 1051 | |
| 1052 | Run hooks `magit-pre-refresh-hook', `magit-post-refresh-hook' |
| 1053 | and `magit-unwind-refresh-hook'." |
| 1054 | (interactive) |
| 1055 | (unless magit-inhibit-refresh |
| 1056 | (unwind-protect |
| 1057 | (let ((start (current-time)) |
| 1058 | (magit--refresh-cache (or magit--refresh-cache |
| 1059 | (list (cons 0 0))))) |
| 1060 | (when magit-refresh-verbose |
| 1061 | (message "Refreshing magit...")) |
| 1062 | (magit-run-hook-with-benchmark 'magit-pre-refresh-hook) |
| 1063 | (cond ((derived-mode-p 'magit-mode) |
| 1064 | (magit-refresh-buffer)) |
| 1065 | ((derived-mode-p 'tabulated-list-mode) |
| 1066 | (revert-buffer))) |
| 1067 | (when-let ((buffer (and magit-refresh-status-buffer |
| 1068 | (not (derived-mode-p 'magit-status-mode)) |
| 1069 | (magit-get-mode-buffer 'magit-status-mode)))) |
| 1070 | (with-current-buffer buffer |
| 1071 | (magit-refresh-buffer))) |
| 1072 | (magit-run-hook-with-benchmark 'magit-post-refresh-hook) |
| 1073 | (when magit-refresh-verbose |
| 1074 | (let* ((c (caar magit--refresh-cache)) |
| 1075 | (a (+ c (cdar magit--refresh-cache)))) |
| 1076 | (message "Refreshing magit...done (%.3fs, cached %s/%s (%.0f%%))" |
| 1077 | (float-time (time-since start)) |
| 1078 | c a (* (/ c (* a 1.0)) 100))))) |
| 1079 | (run-hooks 'magit-unwind-refresh-hook)))) |
| 1080 | |
| 1081 | (defun magit-refresh-all () |
| 1082 | "Refresh all buffers belonging to the current repository. |
| 1083 | |
| 1084 | Refresh all Magit buffers belonging to the current repository, |
| 1085 | and revert buffers that visit files located inside the current |
| 1086 | repository. |
| 1087 | |
| 1088 | Run hooks `magit-pre-refresh-hook' and `magit-post-refresh-hook'." |
| 1089 | (interactive) |
| 1090 | (magit-run-hook-with-benchmark 'magit-pre-refresh-hook) |
| 1091 | (dolist (buffer (magit-mode-get-buffers)) |
| 1092 | (with-current-buffer buffer (magit-refresh-buffer))) |
| 1093 | (magit-run-hook-with-benchmark 'magit-post-refresh-hook)) |
| 1094 | |
| 1095 | (defvar-local magit--refresh-start-time nil) |
| 1096 | |
| 1097 | (cl-defun magit-refresh-buffer ( &optional created |
| 1098 | &key initial-section select-section) |
| 1099 | "Refresh the current Magit buffer. |
| 1100 | The arguments are for internal use." |
| 1101 | (interactive) |
| 1102 | (when-let ((refresh (magit--refresh-buffer-function))) |
| 1103 | (let ((magit--refreshing-buffer-p t) |
| 1104 | (magit--refresh-start-time (current-time)) |
| 1105 | (magit--refresh-cache (or magit--refresh-cache (list (cons 0 0)))) |
| 1106 | (action (if created "Creating" "Refreshing"))) |
| 1107 | (when magit-refresh-verbose |
| 1108 | (message "%s buffer `%s'..." action (buffer-name))) |
| 1109 | (cond |
| 1110 | (created |
| 1111 | (funcall refresh) |
| 1112 | (cond ((and select-section (funcall select-section))) |
| 1113 | (initial-section (funcall initial-section)))) |
| 1114 | (t |
| 1115 | (deactivate-mark) |
| 1116 | (setq magit-section-pre-command-section nil) |
| 1117 | (setq magit-section-highlight-overlays nil) |
| 1118 | (setq magit-section-selection-overlays nil) |
| 1119 | (setq magit-section-highlighted-sections nil) |
| 1120 | (setq magit-section-focused-sections nil) |
| 1121 | (let ((positions (magit--refresh-buffer-get-positions))) |
| 1122 | (funcall refresh) |
| 1123 | (cond ((and select-section (funcall select-section))) |
| 1124 | ((magit--refresh-buffer-set-positions positions)))))) |
| 1125 | (let ((magit-section-cache-visibility nil)) |
| 1126 | (magit-section-show magit-root-section)) |
| 1127 | (run-hooks 'magit-refresh-buffer-hook) |
| 1128 | (magit-section-update-highlight) |
| 1129 | (when-let ((_(bound-and-true-p hi-lock-mode)) |
| 1130 | (fn (hi-lock-revert-buffer-rehighlight))) |
| 1131 | (funcall fn)) |
| 1132 | (set-buffer-modified-p nil) |
| 1133 | (push (current-buffer) magit-section--refreshed-buffers) |
| 1134 | (when magit-refresh-verbose |
| 1135 | (message "%s buffer `%s'...done (%.3fs)" action (buffer-name) |
| 1136 | (float-time (time-since magit--refresh-start-time))))))) |
| 1137 | |
| 1138 | (defun magit--refresh-buffer-function () |
| 1139 | (let ((fn (intern (format "%s-refresh-buffer" |
| 1140 | (substring (symbol-name major-mode) 0 -5))))) |
| 1141 | (and (functionp fn) |
| 1142 | (lambda () |
| 1143 | (let ((inhibit-read-only t)) |
| 1144 | (erase-buffer) |
| 1145 | (save-excursion (funcall fn))))))) |
| 1146 | |
| 1147 | (defun magit--refresh-buffer-get-positions () |
| 1148 | (or (let ((buffer (current-buffer))) |
| 1149 | (mapcan |
| 1150 | (lambda (window) |
| 1151 | (with-selected-window window |
| 1152 | (with-current-buffer buffer |
| 1153 | (and-let ((section (magit-section-at))) |
| 1154 | `((,window |
| 1155 | ,section |
| 1156 | ,@(magit-section-get-relative-position section) |
| 1157 | ,@(and-let ((ws (magit-section-at (window-start)))) |
| 1158 | (list ws |
| 1159 | (car (magit-section-get-relative-position ws)) |
| 1160 | (window-start))))))))) |
| 1161 | ;; For hunks we run `magit-section-movement-hook' (once for |
| 1162 | ;; each window displaying the buffer). The selected window |
| 1163 | ;; comes first in this list, but we want to process it last. |
| 1164 | (nreverse (get-buffer-window-list buffer nil t)))) |
| 1165 | (and$ (magit-section-at) |
| 1166 | `((nil ,$ ,@(magit-section-get-relative-position $)))))) |
| 1167 | |
| 1168 | (defun magit--refresh-buffer-set-positions (positions) |
| 1169 | (pcase-dolist |
| 1170 | (`(,window ,section ,line ,char ,ws-section ,ws-line ,window-start) |
| 1171 | positions) |
| 1172 | (if window |
| 1173 | (with-selected-window window |
| 1174 | (magit-section-goto-successor section line char) |
| 1175 | (cond-let |
| 1176 | ((derived-mode-p 'magit-log-mode)) |
| 1177 | ((or (not window-start) |
| 1178 | (> window-start (point)))) |
| 1179 | ((magit-section-equal ws-section (magit-section-at window-start)) |
| 1180 | (set-window-start window window-start t)) |
| 1181 | ([pos (save-excursion |
| 1182 | (and (magit-section-goto-successor--same |
| 1183 | ws-section ws-line 0) |
| 1184 | (point)))] |
| 1185 | (set-window-start window pos t)))) |
| 1186 | ;; We must make sure this does not call `set-window-start', |
| 1187 | ;; which the HUNK METHOD does by calling `magit-section-goto' |
| 1188 | ;; because that runs the `magit-section-goto-successor-hook' |
| 1189 | ;; and thus `magit-hunk-set-window-start'. The window does |
| 1190 | ;; not display this buffer, so the window start would be set |
| 1191 | ;; for the wrong buffer. Originally reported in #4196 and |
| 1192 | ;; fixed with 482c25a3204468a4f6c2fe12ff061666b61f5f4d. |
| 1193 | (let ((magit-section-movement-hook nil)) |
| 1194 | (magit-section-goto-successor section line char) |
| 1195 | ;; To store the point value for the selected window, it isn't |
| 1196 | ;; enough for it to be current, the window has to "display" it. |
| 1197 | ;; The effect of `goto-char', used by the above function, is not |
| 1198 | ;; preserved, and using just `set-window-point' would affect the |
| 1199 | ;; wrong buffer. |
| 1200 | (unless (eq (window-dedicated-p) t) |
| 1201 | (let ((restore (window-buffer)) |
| 1202 | (window-scroll-functions nil) |
| 1203 | (window-configuration-change-hook nil)) |
| 1204 | (unwind-protect |
| 1205 | (progn |
| 1206 | (set-window-buffer nil (current-buffer) t) |
| 1207 | (set-window-point nil (point))) |
| 1208 | (set-window-buffer nil restore t)))))))) |
| 1209 | |
| 1210 | (defun magit-revert-buffer (_ignore-auto _noconfirm) |
| 1211 | "Wrapper around `magit-refresh-buffer' suitable as `revert-buffer-function'." |
| 1212 | (magit-refresh-buffer)) |
| 1213 | |
| 1214 | (defun magit-profile-refresh-buffer () |
| 1215 | "Profile refreshing the current Magit buffer." |
| 1216 | (interactive) |
| 1217 | (require (quote elp)) |
| 1218 | (elp-reset-all) |
| 1219 | (message "Profiling Magit and Forge...") |
| 1220 | (elp-instrument-package "magit-") |
| 1221 | (elp-instrument-package "forge-") |
| 1222 | (magit-refresh-buffer) |
| 1223 | (message "Profiling Magit and Forge...done") |
| 1224 | (elp-results) |
| 1225 | (elp-reset-all)) |
| 1226 | |
| 1227 | (defun magit-toggle-profiling () |
| 1228 | "Start profiling Magit, or if in progress, stop and display the results." |
| 1229 | (interactive) |
| 1230 | (require (quote elp)) |
| 1231 | (cond ((catch 'in-progress |
| 1232 | (mapatoms (##and (get % elp-timer-info-property) |
| 1233 | (throw 'in-progress t)))) |
| 1234 | (message "Stop profiling and display results...") |
| 1235 | (elp-results) |
| 1236 | (elp-restore-all)) |
| 1237 | (t |
| 1238 | (message "Start profiling Magit and Forge...") |
| 1239 | (elp-reset-all) |
| 1240 | (elp-instrument-package "magit-") |
| 1241 | (elp-instrument-package "forge-")))) |
| 1242 | |
| 1243 | ;;; Save File-Visiting Buffers |
| 1244 | |
| 1245 | (defvar magit--disable-save-buffers nil) |
| 1246 | |
| 1247 | (defun magit-pre-command-hook () |
| 1248 | (setq magit--disable-save-buffers nil)) |
| 1249 | (add-hook 'pre-command-hook #'magit-pre-command-hook) |
| 1250 | |
| 1251 | (defvar magit-after-save-refresh-buffers nil) |
| 1252 | |
| 1253 | (defun magit-after-save-refresh-buffers () |
| 1254 | (unless magit-inhibit-refresh |
| 1255 | (dolist (buffer magit-after-save-refresh-buffers) |
| 1256 | (when (buffer-live-p buffer) |
| 1257 | (with-current-buffer buffer |
| 1258 | (magit-refresh-buffer)))) |
| 1259 | (setq magit-after-save-refresh-buffers nil) |
| 1260 | (remove-hook 'post-command-hook #'magit-after-save-refresh-buffers))) |
| 1261 | |
| 1262 | (defun magit-after-save-refresh-status () |
| 1263 | "Refresh the status buffer of the current repository. |
| 1264 | |
| 1265 | This function is intended to be added to `after-save-hook'. |
| 1266 | |
| 1267 | If the status buffer does not exist or the file being visited in |
| 1268 | the current buffer isn't inside the working tree of a repository, |
| 1269 | then do nothing. |
| 1270 | |
| 1271 | Note that refreshing a Magit buffer is done by re-creating its |
| 1272 | contents from scratch, which can be slow in large repositories. |
| 1273 | If you are not satisfied with Magit's performance, then you |
| 1274 | should obviously not add this function to that hook." |
| 1275 | (when-let ((_(not magit-inhibit-refresh)) |
| 1276 | (_(magit-inside-worktree-p t)) |
| 1277 | (buf (ignore-errors (magit-get-mode-buffer 'magit-status-mode)))) |
| 1278 | (cl-pushnew buf magit-after-save-refresh-buffers) |
| 1279 | (add-hook 'post-command-hook #'magit-after-save-refresh-buffers))) |
| 1280 | |
| 1281 | (defun magit-maybe-save-repository-buffers () |
| 1282 | "Maybe save file-visiting buffers belonging to the current repository. |
| 1283 | Do so if `magit-save-repository-buffers' is non-nil. You should |
| 1284 | not remove this from any hooks, instead set that variable to nil |
| 1285 | if you so desire." |
| 1286 | (when (and magit-save-repository-buffers |
| 1287 | (not magit--disable-save-buffers)) |
| 1288 | (setq magit--disable-save-buffers t) |
| 1289 | (let ((msg (current-message))) |
| 1290 | (magit-save-repository-buffers |
| 1291 | (eq magit-save-repository-buffers 'dontask)) |
| 1292 | (when (and msg |
| 1293 | (current-message) |
| 1294 | (not (equal msg (current-message)))) |
| 1295 | (message "%s" msg))))) |
| 1296 | |
| 1297 | (defvar-local magit-inhibit-refresh-save nil) |
| 1298 | |
| 1299 | (defvar magit-save-repository-buffers-predicate |
| 1300 | (lambda (topdir) |
| 1301 | (let ((remote (file-remote-p default-directory)) |
| 1302 | (topdirs nil) |
| 1303 | ;; If the current file is modified and resides inside |
| 1304 | ;; a repository, and a let-binding is in effect, which |
| 1305 | ;; places us in another repository, then this binding |
| 1306 | ;; is needed to prevent that file from being saved. |
| 1307 | (default-directory default-directory)) |
| 1308 | (and buffer-file-name |
| 1309 | (setq default-directory (file-name-directory buffer-file-name)) |
| 1310 | ;; Check whether the repository still exists. |
| 1311 | (file-exists-p default-directory) |
| 1312 | ;; Check whether refreshing is disabled. |
| 1313 | (not magit-inhibit-refresh-save) |
| 1314 | ;; Check whether the visited file is either on the |
| 1315 | ;; same remote as the repository, or both are on |
| 1316 | ;; the local system. |
| 1317 | (equal (file-remote-p buffer-file-name) remote) |
| 1318 | ;; Delayed checks that are more expensive for remote |
| 1319 | ;; repositories, due to the required network access. |
| 1320 | ;; |
| 1321 | ;; Check whether the file is inside the repository. |
| 1322 | (equal (or (cdr (assoc default-directory topdirs)) |
| 1323 | (let ((top (magit-rev-parse-safe "--show-toplevel"))) |
| 1324 | (push (cons default-directory top) topdirs) |
| 1325 | top)) |
| 1326 | topdir) |
| 1327 | ;; Check whether the file is actually writable. |
| 1328 | (file-writable-p buffer-file-name)))) |
| 1329 | "Predicate for `magit-save-repository-buffers'. |
| 1330 | |
| 1331 | This function is called for each buffer that might need saving with |
| 1332 | one argument, the working tree of the respective repository. If it |
| 1333 | returns non-nil, the current buffer is saved.") |
| 1334 | |
| 1335 | (defun magit-save-repository-buffers (&optional arg) |
| 1336 | "Save file-visiting buffers belonging to the current repository. |
| 1337 | After any buffer where `buffer-save-without-query' is non-nil |
| 1338 | is saved without asking, the user is asked about each modified |
| 1339 | buffer, which visits a file in the current repository. Optional |
| 1340 | argument (the prefix) non-nil means save all with no questions." |
| 1341 | (interactive "P") |
| 1342 | (when-let ((topdir (magit-rev-parse-safe "--show-toplevel"))) |
| 1343 | (let ((save-some-buffers-action-alist |
| 1344 | `((?Y ,(##with-current-buffer % |
| 1345 | (setq buffer-save-without-query t) |
| 1346 | (save-buffer)) |
| 1347 | "to save the current buffer and remember choice") |
| 1348 | (?N ,(##with-current-buffer % |
| 1349 | (setq magit-inhibit-refresh-save t)) |
| 1350 | "to skip the current buffer and remember choice") |
| 1351 | ,@save-some-buffers-action-alist)) |
| 1352 | ;; Create a single wip commit for all saved files. |
| 1353 | (magit--wip-inhibit-autosave t) |
| 1354 | (saved nil)) |
| 1355 | (unwind-protect |
| 1356 | (save-some-buffers |
| 1357 | arg |
| 1358 | (lambda () |
| 1359 | (and (funcall magit-save-repository-buffers-predicate topdir) |
| 1360 | (prog1 t |
| 1361 | (when magit-wip-mode |
| 1362 | (push (expand-file-name buffer-file-name) saved)))))) |
| 1363 | (when saved |
| 1364 | (let ((default-directory topdir)) |
| 1365 | (magit-wip-commit-worktree |
| 1366 | (magit-wip-get-ref) |
| 1367 | saved |
| 1368 | (if (cdr saved) |
| 1369 | (format "autosave %s files after save" (length saved)) |
| 1370 | (format "autosave %s after save" |
| 1371 | (file-relative-name (car saved))))))))))) |
| 1372 | |
| 1373 | ;;; Restore Window Configuration |
| 1374 | |
| 1375 | (defvar magit-inhibit-save-previous-winconf nil) |
| 1376 | |
| 1377 | (defvar-local magit-previous-window-configuration nil) |
| 1378 | (put 'magit-previous-window-configuration 'permanent-local t) |
| 1379 | |
| 1380 | (defun magit-save-window-configuration () |
| 1381 | "Save the current window configuration. |
| 1382 | |
| 1383 | Later, when the buffer is buried, it may be restored by |
| 1384 | `magit-restore-window-configuration'." |
| 1385 | (cond (magit-inhibit-save-previous-winconf |
| 1386 | (when (eq magit-inhibit-save-previous-winconf 'unset) |
| 1387 | (setq magit-previous-window-configuration nil))) |
| 1388 | ((not (get-buffer-window (current-buffer) (selected-frame))) |
| 1389 | (setq magit-previous-window-configuration |
| 1390 | (current-window-configuration))))) |
| 1391 | |
| 1392 | (defun magit-restore-window-configuration (&optional kill-buffer) |
| 1393 | "Bury or kill the current buffer and restore previous window configuration." |
| 1394 | (let ((winconf magit-previous-window-configuration) |
| 1395 | (buffer (current-buffer)) |
| 1396 | (frame (selected-frame))) |
| 1397 | (quit-window kill-buffer (selected-window)) |
| 1398 | (when (and winconf (equal frame (window-configuration-frame winconf))) |
| 1399 | (set-window-configuration winconf) |
| 1400 | (when (buffer-live-p buffer) |
| 1401 | (with-current-buffer buffer |
| 1402 | (setq magit-previous-window-configuration nil))) |
| 1403 | (set-buffer (with-selected-window (selected-window) |
| 1404 | (current-buffer)))))) |
| 1405 | |
| 1406 | ;;; Buffer History |
| 1407 | |
| 1408 | (defun magit-go-backward () |
| 1409 | "Move backward in current buffer's history." |
| 1410 | (interactive) |
| 1411 | (if help-xref-stack |
| 1412 | (help-xref-go-back (current-buffer)) |
| 1413 | (user-error "No previous entry in buffer's history"))) |
| 1414 | |
| 1415 | (defun magit-go-forward () |
| 1416 | "Move forward in current buffer's history." |
| 1417 | (interactive) |
| 1418 | (if help-xref-forward-stack |
| 1419 | (help-xref-go-forward (current-buffer)) |
| 1420 | (user-error "No next entry in buffer's history"))) |
| 1421 | |
| 1422 | (defun magit-insert-xref-buttons () |
| 1423 | "Insert xref buttons." |
| 1424 | (when (and (not magit-buffer-locked-p) |
| 1425 | (or help-xref-stack help-xref-forward-stack)) |
| 1426 | (when help-xref-stack |
| 1427 | (magit-xref-insert-button help-back-label 'magit-xref-backward)) |
| 1428 | (when help-xref-forward-stack |
| 1429 | (when help-xref-stack |
| 1430 | (insert " ")) |
| 1431 | (magit-xref-insert-button help-forward-label 'magit-xref-forward)))) |
| 1432 | |
| 1433 | (defun magit-xref-insert-button (label type) |
| 1434 | (magit-insert-section (button label) |
| 1435 | (insert-text-button label 'type type |
| 1436 | 'help-args (list (current-buffer))))) |
| 1437 | |
| 1438 | (define-button-type 'magit-xref-backward |
| 1439 | :supertype 'help-back |
| 1440 | 'mouse-face 'magit-section-highlight |
| 1441 | 'help-echo (purecopy "mouse-2, RET: go back to previous history entry")) |
| 1442 | |
| 1443 | (define-button-type 'magit-xref-forward |
| 1444 | :supertype 'help-forward |
| 1445 | 'mouse-face 'magit-section-highlight |
| 1446 | 'help-echo (purecopy "mouse-2, RET: go back to next history entry")) |
| 1447 | |
| 1448 | (defvar magit-xref-modes |
| 1449 | ;; Do not function-quote to avoid circular dependencies. |
| 1450 | '(magit-log-mode |
| 1451 | magit-reflog-mode |
| 1452 | magit-diff-mode |
| 1453 | magit-revision-mode) |
| 1454 | "List of modes for which to insert navigation buttons.") |
| 1455 | |
| 1456 | (defun magit-xref-setup (fn args) |
| 1457 | (when (memq major-mode magit-xref-modes) |
| 1458 | (when help-xref-stack-item |
| 1459 | (push (cons (point) help-xref-stack-item) help-xref-stack) |
| 1460 | (setq help-xref-forward-stack nil)) |
| 1461 | (when-let ((tail (nthcdr 30 help-xref-stack))) |
| 1462 | (setcdr tail nil)) |
| 1463 | (setq help-xref-stack-item |
| 1464 | (list 'magit-xref-restore fn default-directory args)))) |
| 1465 | |
| 1466 | (defun magit-xref-restore (fn dir args) |
| 1467 | (setq default-directory dir) |
| 1468 | (funcall fn major-mode nil args)) |
| 1469 | |
| 1470 | ;;; Repository-Local Cache |
| 1471 | |
| 1472 | (defvar magit-repository-local-cache nil |
| 1473 | "Alist mapping `magit-toplevel' paths to alists of key/value pairs.") |
| 1474 | |
| 1475 | (defun magit-repository-local-repository () |
| 1476 | "Return the key for the current repository." |
| 1477 | (or (bound-and-true-p magit--default-directory) |
| 1478 | (magit-toplevel))) |
| 1479 | |
| 1480 | (defun magit-repository-local-set (key value &optional repository) |
| 1481 | "Set the repository-local VALUE for KEY. |
| 1482 | |
| 1483 | Unless specified, REPOSITORY is the current buffer's repository. |
| 1484 | |
| 1485 | If REPOSITORY is nil (meaning there is no current repository), |
| 1486 | then the value is not cached, and we return nil." |
| 1487 | (let* ((repokey (or repository (magit-repository-local-repository))) |
| 1488 | (cache (assoc repokey magit-repository-local-cache))) |
| 1489 | ;; Don't cache values for a nil REPOSITORY, as the 'set' and 'get' |
| 1490 | ;; calls for some KEY may happen in unrelated contexts. |
| 1491 | (when repokey |
| 1492 | (if cache |
| 1493 | (let ((keyvalue (assoc key (cdr cache)))) |
| 1494 | (if keyvalue |
| 1495 | ;; Update pre-existing value for key. |
| 1496 | (setcdr keyvalue value) |
| 1497 | ;; No such key in repository-local cache. |
| 1498 | (push (cons key value) (cdr cache)))) |
| 1499 | ;; No cache for this repository. |
| 1500 | (push (cons repokey (list (cons key value))) |
| 1501 | magit-repository-local-cache))))) |
| 1502 | |
| 1503 | (defun magit-repository-local-exists-p (key &optional repository) |
| 1504 | "Non-nil when a repository-local value exists for KEY. |
| 1505 | |
| 1506 | Return a (KEY . VALUE) cons cell. |
| 1507 | |
| 1508 | The KEY is matched using `equal'. |
| 1509 | |
| 1510 | Unless specified, REPOSITORY is the current buffer's repository." |
| 1511 | (and-let ((cache (assoc (or repository |
| 1512 | (magit-repository-local-repository)) |
| 1513 | magit-repository-local-cache))) |
| 1514 | (assoc key (cdr cache)))) |
| 1515 | |
| 1516 | (defun magit-repository-local-get (key &optional default repository) |
| 1517 | "Return the repository-local value for KEY. |
| 1518 | |
| 1519 | Return DEFAULT if no value for KEY exists. |
| 1520 | |
| 1521 | The KEY is matched using `equal'. |
| 1522 | |
| 1523 | Unless specified, REPOSITORY is the current buffer's repository." |
| 1524 | (if-let ((keyvalue (magit-repository-local-exists-p key repository))) |
| 1525 | (cdr keyvalue) |
| 1526 | default)) |
| 1527 | |
| 1528 | (defun magit-repository-local-delete (key &optional repository) |
| 1529 | "Delete the repository-local value for KEY. |
| 1530 | |
| 1531 | Unless specified, REPOSITORY is the current buffer's repository. |
| 1532 | If REPOSITORY is `all', then delete the value for KEY for all |
| 1533 | repositories." |
| 1534 | (cond-let |
| 1535 | ((eq repository 'all) |
| 1536 | (dolist (cache magit-repository-local-cache) |
| 1537 | (setf cache (assoc-delete-all key cache)))) |
| 1538 | ([cache (assoc (or repository (magit-repository-local-repository)) |
| 1539 | magit-repository-local-cache)] |
| 1540 | (setf cache (assoc-delete-all key cache))))) |
| 1541 | |
| 1542 | (defmacro magit--with-repository-local-cache (key &rest body) |
| 1543 | (declare (indent 1) (debug (form body))) |
| 1544 | (let ((k (gensym))) |
| 1545 | `(let ((,k ,key)) |
| 1546 | (if-let ((kv (magit-repository-local-exists-p ,k))) |
| 1547 | (cdr kv) |
| 1548 | (let ((v ,(macroexp-progn body))) |
| 1549 | (magit-repository-local-set ,k v) |
| 1550 | v))))) |
| 1551 | |
| 1552 | (defun magit-preserve-section-visibility-cache () |
| 1553 | (when (derived-mode-p 'magit-status-mode 'magit-refs-mode) |
| 1554 | (magit-repository-local-set |
| 1555 | (cons major-mode 'magit-section-visibility-cache) |
| 1556 | magit-section-visibility-cache))) |
| 1557 | |
| 1558 | (defun magit-restore-section-visibility-cache (mode) |
| 1559 | (setq magit-section-visibility-cache |
| 1560 | (magit-repository-local-get |
| 1561 | (cons mode 'magit-section-visibility-cache)))) |
| 1562 | |
| 1563 | (defun magit-zap-caches (&optional all) |
| 1564 | "Zap caches for the current repository. |
| 1565 | |
| 1566 | Remove the repository's entry from `magit-repository-local-cache', |
| 1567 | remove the host's entry from `magit--host-git-version-cache', set |
| 1568 | `magit-section-visibility-cache' to nil for all Magit buffers of |
| 1569 | the repository, and empty the `magit--blob-cache'. |
| 1570 | |
| 1571 | With a prefix argument or if optional ALL is non-nil, discard the |
| 1572 | mentioned caches completely." |
| 1573 | (interactive) |
| 1574 | (magit--blob-cache-zap) |
| 1575 | (cond (all |
| 1576 | (setq magit-repository-local-cache nil) |
| 1577 | (setq magit--host-git-version-cache nil) |
| 1578 | (setq magit-githook-directory nil) |
| 1579 | (dolist (buffer (buffer-list)) |
| 1580 | (with-current-buffer buffer |
| 1581 | (when (derived-mode-p 'magit-mode) |
| 1582 | (setq magit-section-visibility-cache nil))))) |
| 1583 | (t |
| 1584 | (magit-with-toplevel |
| 1585 | (setq magit-repository-local-cache |
| 1586 | (cl-delete default-directory |
| 1587 | magit-repository-local-cache |
| 1588 | :key #'car :test #'equal)) |
| 1589 | (setq magit--host-git-version-cache |
| 1590 | (cl-delete (file-remote-p default-directory) |
| 1591 | magit--host-git-version-cache |
| 1592 | :key #'car :test #'equal))) |
| 1593 | (dolist (buffer (magit-mode-get-buffers)) |
| 1594 | (with-current-buffer buffer |
| 1595 | (setq magit-section-visibility-cache nil)))))) |
| 1596 | |
| 1597 | ;;; Utilities |
| 1598 | |
| 1599 | (defun magit-toggle-verbose-refresh () |
| 1600 | "Toggle whether Magit refreshes buffers verbosely. |
| 1601 | Enabling this helps figuring out which sections are bottlenecks. |
| 1602 | The additional output can be found in the *Messages* buffer." |
| 1603 | (interactive) |
| 1604 | (setq magit-refresh-verbose (not magit-refresh-verbose)) |
| 1605 | (message "%s verbose refreshing" |
| 1606 | (if magit-refresh-verbose "Enabled" "Disabled"))) |
| 1607 | |
| 1608 | (defun magit-run-hook-with-benchmark (hook) |
| 1609 | (cond |
| 1610 | ((not hook)) |
| 1611 | (magit-refresh-verbose |
| 1612 | (message "Running %s..." hook) |
| 1613 | (message "Running %s...done (%.3fs)" hook |
| 1614 | (benchmark-elapse |
| 1615 | (run-hook-wrapped |
| 1616 | hook |
| 1617 | (lambda (fn) |
| 1618 | (message " %-50s %f" fn (benchmark-elapse (funcall fn)))))))) |
| 1619 | ((run-hooks hook)))) |
| 1620 | |
| 1621 | (defun magit-file-region-line-numbers () |
| 1622 | "Return the bounds of the region as line numbers. |
| 1623 | The returned value has the form (BEGINNING-LINE END-LINE). If |
| 1624 | the region ends at the beginning of a line, do not include that |
| 1625 | line. Avoid including the line after the end of the file." |
| 1626 | (and (magit-buffer-file-name) |
| 1627 | (region-active-p) |
| 1628 | (not (= (region-beginning) (region-end) (1+ (buffer-size)))) |
| 1629 | (let ((beg (region-beginning)) |
| 1630 | (end (min (region-end) (buffer-size)))) |
| 1631 | (list (line-number-at-pos beg t) |
| 1632 | (line-number-at-pos (if (= (magit--bol-position end) end) |
| 1633 | (max beg (1- end)) |
| 1634 | end) |
| 1635 | t))))) |
| 1636 | |
| 1637 | ;;; _ |
| 1638 | (provide 'magit-mode) |
| 1639 | ;; Local Variables: |
| 1640 | ;; read-symbol-shorthands: ( |
| 1641 | ;; ("and$" . "cond-let--and$") |
| 1642 | ;; ("thread$" . "cond-let--thread$") |
| 1643 | ;; ("when$" . "cond-let--when$") |
| 1644 | ;; ("and-let*" . "cond-let--and-let*") |
| 1645 | ;; ("and-let" . "cond-let--and-let") |
| 1646 | ;; ("if-let*" . "cond-let--if-let*") |
| 1647 | ;; ("if-let" . "cond-let--if-let") |
| 1648 | ;; ("when-let*" . "cond-let--when-let*") |
| 1649 | ;; ("when-let" . "cond-let--when-let") |
| 1650 | ;; ("while-let*" . "cond-let--while-let*") |
| 1651 | ;; ("while-let" . "cond-let--while-let") |
| 1652 | ;; ("match-string" . "match-string") |
| 1653 | ;; ("match-str" . "match-string-no-properties")) |
| 1654 | ;; End: |
| 1655 | ;;; magit-mode.el ends here |