fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 22h ago
1655 lines · 64.3 KB Raw Blame History
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