1207 lines
47 KiB
EmacsLisp
1207 lines
47 KiB
EmacsLisp
|
;;; magit-utils.el --- various utilities -*- lexical-binding: t; coding: utf-8 -*-
|
||
|
|
||
|
;; Copyright (C) 2010-2020 The Magit Project Contributors
|
||
|
;;
|
||
|
;; You should have received a copy of the AUTHORS.md file which
|
||
|
;; lists all contributors. If not, see http://magit.vc/authors.
|
||
|
|
||
|
;; Author: Jonas Bernoulli <jonas@bernoul.li>
|
||
|
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
|
||
|
|
||
|
;; Contains code from GNU Emacs https://www.gnu.org/software/emacs,
|
||
|
;; released under the GNU General Public License version 3 or later.
|
||
|
|
||
|
;; Magit is free software; you can redistribute it and/or modify it
|
||
|
;; under the terms of the GNU General Public License as published by
|
||
|
;; the Free Software Foundation; either version 3, or (at your option)
|
||
|
;; any later version.
|
||
|
;;
|
||
|
;; Magit is distributed in the hope that it will be useful, but WITHOUT
|
||
|
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||
|
;; License for more details.
|
||
|
;;
|
||
|
;; You should have received a copy of the GNU General Public License
|
||
|
;; along with Magit. If not, see http://www.gnu.org/licenses.
|
||
|
|
||
|
;;; Commentary:
|
||
|
|
||
|
;; This library defines several utility functions used by several
|
||
|
;; other libraries which cannot depend on one another (because
|
||
|
;; circular dependencies are not good). Luckily most (all) of these
|
||
|
;; functions have very little (nothing) to do with Git, so we not only
|
||
|
;; have to do this, it even makes sense.
|
||
|
|
||
|
;; Unfortunately there are also some options which are used by several
|
||
|
;; libraries which cannot depend on one another, they are defined here
|
||
|
;; too.
|
||
|
|
||
|
;;; Code:
|
||
|
|
||
|
(require 'cl-lib)
|
||
|
(require 'dash)
|
||
|
|
||
|
(eval-when-compile
|
||
|
(require 'subr-x))
|
||
|
|
||
|
(require 'crm)
|
||
|
|
||
|
(eval-when-compile (require 'ido))
|
||
|
(declare-function ido-completing-read+ "ido-completing-read+"
|
||
|
(prompt collection &optional predicate
|
||
|
require-match initial-input
|
||
|
hist def inherit-input-method))
|
||
|
(declare-function Info-get-token "info" (pos start all &optional errorstring))
|
||
|
|
||
|
(eval-when-compile (require 'vc-git))
|
||
|
(declare-function vc-git--run-command-string "vc-git" (file &rest args))
|
||
|
|
||
|
(eval-when-compile (require 'which-func))
|
||
|
(declare-function which-function "which-func" ())
|
||
|
|
||
|
(defvar magit-wip-before-change-mode)
|
||
|
|
||
|
;;; Options
|
||
|
|
||
|
(defcustom magit-completing-read-function 'magit-builtin-completing-read
|
||
|
"Function to be called when requesting input from the user.
|
||
|
|
||
|
If you have enabled `ivy-mode' or `helm-mode', then you don't
|
||
|
have to customize this option; `magit-builtin-completing-read'
|
||
|
will work just fine. However, if you use Ido completion, then
|
||
|
you do have to use `magit-ido-completing-read', because Ido is
|
||
|
less well behaved than the former, more modern alternatives.
|
||
|
|
||
|
If you would like to use Ivy or Helm completion with Magit but
|
||
|
not enable the respective modes globally, then customize this
|
||
|
option to use `ivy-completing-read' or
|
||
|
`helm--completing-read-default'. If you choose to use
|
||
|
`ivy-completing-read', note that the items may always be shown in
|
||
|
alphabetical order, depending on your version of Ivy."
|
||
|
:group 'magit-essentials
|
||
|
:type '(radio (function-item magit-builtin-completing-read)
|
||
|
(function-item magit-ido-completing-read)
|
||
|
(function-item ivy-completing-read)
|
||
|
(function-item helm--completing-read-default)
|
||
|
(function :tag "Other function")))
|
||
|
|
||
|
(defcustom magit-dwim-selection
|
||
|
'((magit-stash-apply nil t)
|
||
|
(magit-stash-branch nil t)
|
||
|
(magit-stash-branch-here nil t)
|
||
|
(magit-stash-format-patch nil t)
|
||
|
(magit-stash-drop nil ask)
|
||
|
(magit-stash-pop nil ask)
|
||
|
(forge-browse-dwim nil t)
|
||
|
(forge-browse-commit nil t)
|
||
|
(forge-browse-branch nil t)
|
||
|
(forge-browse-remote nil t)
|
||
|
(forge-browse-issue nil t)
|
||
|
(forge-browse-pullreq nil t)
|
||
|
(forge-edit-topic-title nil t)
|
||
|
(forge-edit-topic-state nil t)
|
||
|
(forge-edit-topic-milestone nil t)
|
||
|
(forge-edit-topic-labels nil t)
|
||
|
(forge-edit-topic-marks nil t)
|
||
|
(forge-edit-topic-assignees nil t)
|
||
|
(forge-edit-topic-review-requests nil t)
|
||
|
(forge-edit-topic-note nil t)
|
||
|
(forge-pull-pullreq nil t)
|
||
|
(forge-visit-issue nil t)
|
||
|
(forge-visit-pullreq nil t))
|
||
|
"When not to offer alternatives and ask for confirmation.
|
||
|
|
||
|
Many commands by default ask the user to select from a list of
|
||
|
possible candidates. They do so even when there is a thing at
|
||
|
point that they can act on, which is then offered as the default.
|
||
|
|
||
|
This option can be used to tell certain commands to use the thing
|
||
|
at point instead of asking the user to select a candidate to act
|
||
|
on, with or without confirmation.
|
||
|
|
||
|
The value has the form ((COMMAND nil|PROMPT DEFAULT)...).
|
||
|
|
||
|
- COMMAND is the command that should not prompt for a choice.
|
||
|
To have an effect, the command has to use the function
|
||
|
`magit-completing-read' or a utility function which in turn uses
|
||
|
that function.
|
||
|
|
||
|
- If the command uses `magit-completing-read' multiple times, then
|
||
|
PROMPT can be used to only affect one of these uses. PROMPT, if
|
||
|
non-nil, is a regular expression that is used to match against
|
||
|
the PROMPT argument passed to `magit-completing-read'.
|
||
|
|
||
|
- DEFAULT specifies how to use the default. If it is t, then
|
||
|
the DEFAULT argument passed to `magit-completing-read' is used
|
||
|
without confirmation. If it is `ask', then the user is given
|
||
|
a chance to abort. DEFAULT can also be nil, in which case the
|
||
|
entry has no effect."
|
||
|
:package-version '(magit . "2.12.0")
|
||
|
:group 'magit-commands
|
||
|
:type '(repeat
|
||
|
(list (symbol :tag "Command") ; It might not be fboundp yet.
|
||
|
(choice (const :tag "for all prompts" nil)
|
||
|
(regexp :tag "for prompts matching regexp"))
|
||
|
(choice (const :tag "offer other choices" nil)
|
||
|
(const :tag "require confirmation" ask)
|
||
|
(const :tag "use default without confirmation" t)))))
|
||
|
|
||
|
(defconst magit--confirm-actions
|
||
|
'((const reverse) (const discard)
|
||
|
(const rename) (const resurrect)
|
||
|
(const untrack) (const trash)
|
||
|
(const delete) (const abort-rebase)
|
||
|
(const abort-merge) (const merge-dirty)
|
||
|
(const drop-stashes) (const reset-bisect)
|
||
|
(const kill-process) (const delete-unmerged-branch)
|
||
|
(const delete-pr-branch) (const remove-modules)
|
||
|
(const stage-all-changes) (const unstage-all-changes)
|
||
|
(const safe-with-wip)))
|
||
|
|
||
|
(defcustom magit-no-confirm nil
|
||
|
"A list of symbols for actions Magit should not confirm, or t.
|
||
|
|
||
|
Many potentially dangerous commands by default ask the user for
|
||
|
confirmation. Each of the below symbols stands for an action
|
||
|
which, when invoked unintentionally or without being fully aware
|
||
|
of the consequences, could lead to tears. In many cases there
|
||
|
are several commands that perform variations of a certain action,
|
||
|
so we don't use the command names but more generic symbols.
|
||
|
|
||
|
Applying changes:
|
||
|
|
||
|
`discard' Discarding one or more changes (i.e. hunks or the
|
||
|
complete diff for a file) loses that change, obviously.
|
||
|
|
||
|
`reverse' Reverting one or more changes can usually be undone
|
||
|
by reverting the reversion.
|
||
|
|
||
|
`stage-all-changes', `unstage-all-changes' When there are both
|
||
|
staged and unstaged changes, then un-/staging everything would
|
||
|
destroy that distinction. Of course that also applies when
|
||
|
un-/staging a single change, but then less is lost and one does
|
||
|
that so often that having to confirm every time would be
|
||
|
unacceptable.
|
||
|
|
||
|
Files:
|
||
|
|
||
|
`delete' When a file that isn't yet tracked by Git is deleted
|
||
|
then it is completely lost, not just the last changes. Very
|
||
|
dangerous.
|
||
|
|
||
|
`trash' Instead of deleting a file it can also be move to the
|
||
|
system trash. Obviously much less dangerous than deleting it.
|
||
|
|
||
|
Also see option `magit-delete-by-moving-to-trash'.
|
||
|
|
||
|
`resurrect' A deleted file can easily be resurrected by
|
||
|
\"deleting\" the deletion, which is done using the same command
|
||
|
that was used to delete the same file in the first place.
|
||
|
|
||
|
`untrack' Untracking a file can be undone by tracking it again.
|
||
|
|
||
|
`rename' Renaming a file can easily be undone.
|
||
|
|
||
|
Sequences:
|
||
|
|
||
|
`reset-bisect' Aborting (known to Git as \"resetting\") a
|
||
|
bisect operation loses all information collected so far.
|
||
|
|
||
|
`abort-rebase' Aborting a rebase throws away all already
|
||
|
modified commits, but it's possible to restore those from the
|
||
|
reflog.
|
||
|
|
||
|
`abort-merge' Aborting a merge throws away all conflict
|
||
|
resolutions which has already been carried out by the user.
|
||
|
|
||
|
`merge-dirty' Merging with a dirty worktree can make it hard to
|
||
|
go back to the state before the merge was initiated.
|
||
|
|
||
|
References:
|
||
|
|
||
|
`delete-unmerged-branch' Once a branch has been deleted it can
|
||
|
only be restored using low-level recovery tools provided by
|
||
|
Git. And even then the reflog is gone. The user always has
|
||
|
to confirm the deletion of a branch by accepting the default
|
||
|
choice (or selecting another branch), but when a branch has
|
||
|
not been merged yet, also make sure the user is aware of that.
|
||
|
|
||
|
`delete-pr-remote' When deleting a branch that was created from
|
||
|
a pull-request and if no other branches still exist on that
|
||
|
remote, then `magit-branch-delete' offers to delete the remote
|
||
|
as well. This should be safe because it only happens if no
|
||
|
other refs exist in the remotes namespace, and you can recreate
|
||
|
the remote if necessary.
|
||
|
|
||
|
`drop-stashes' Dropping a stash is dangerous because Git stores
|
||
|
stashes in the reflog. Once a stash is removed, there is no
|
||
|
going back without using low-level recovery tools provided by
|
||
|
Git. When a single stash is dropped, then the user always has
|
||
|
to confirm by accepting the default (or selecting another).
|
||
|
This action only concerns the deletion of multiple stashes at
|
||
|
once.
|
||
|
|
||
|
Edit published history:
|
||
|
|
||
|
Without adding these symbols here, you will be warned before
|
||
|
editing commits that have already been pushed to one of the
|
||
|
branches listed in `magit-published-branches'.
|
||
|
|
||
|
`amend-published' Affects most commands that amend to \"HEAD\".
|
||
|
|
||
|
`rebase-published' Affects commands that perform interactive
|
||
|
rebases. This includes commands from the commit popup that
|
||
|
modify a commit other than \"HEAD\", namely the various fixup
|
||
|
and squash variants.
|
||
|
|
||
|
`edit-published' Affects the commands `magit-edit-line-commit'
|
||
|
and `magit-diff-edit-hunk-commit'. These two commands make
|
||
|
it quite easy to accidentally edit a published commit, so you
|
||
|
should think twice before configuring them not to ask for
|
||
|
confirmation.
|
||
|
|
||
|
To disable confirmation completely, add all three symbols here
|
||
|
or set `magit-published-branches' to nil.
|
||
|
|
||
|
Removing modules:
|
||
|
|
||
|
`remove-modules' When you remove the working directory of a
|
||
|
module that does not contain uncommitted changes, then that is
|
||
|
safer than doing so when there are uncommitted changes and/or
|
||
|
when you also remove the gitdir. Still, you don't want to do
|
||
|
that by accident.
|
||
|
|
||
|
`remove-dirty-modules' When you remove the working directory of
|
||
|
a module that contains uncommitted changes, then those changes
|
||
|
are gone for good. It is better to go to the module, inspect
|
||
|
these changes and only if appropriate discard them manually.
|
||
|
|
||
|
`trash-module-gitdirs' When you remove the gitdir of a module,
|
||
|
then all unpushed changes are gone for good. It is very easy
|
||
|
to forget that you have some unfinished work on an unpublished
|
||
|
feature branch or even in a stash.
|
||
|
|
||
|
Actually there are some safety precautions in place, that might
|
||
|
help you out if you make an unwise choice here, but don't count
|
||
|
on it. In case of emergency, stay calm and check the stash and
|
||
|
the `trash-directory' for traces of lost work.
|
||
|
|
||
|
Various:
|
||
|
|
||
|
`kill-process' There seldom is a reason to kill a process.
|
||
|
|
||
|
Global settings:
|
||
|
|
||
|
Instead of adding all of the above symbols to the value of this
|
||
|
option you can also set it to the atom `t', which has the same
|
||
|
effect as adding all of the above symbols. Doing that most
|
||
|
certainly is a bad idea, especially because other symbols might
|
||
|
be added in the future. So even if you don't want to be asked
|
||
|
for confirmation for any of these actions, you are still better
|
||
|
of adding all of the respective symbols individually.
|
||
|
|
||
|
When `magit-wip-before-change-mode' is enabled then these actions
|
||
|
can fairly easily be undone: `discard', `reverse',
|
||
|
`stage-all-changes', and `unstage-all-changes'. If and only if
|
||
|
this mode is enabled, then `safe-with-wip' has the same effect
|
||
|
as adding all of these symbols individually."
|
||
|
:package-version '(magit . "2.1.0")
|
||
|
:group 'magit-essentials
|
||
|
:group 'magit-commands
|
||
|
:type `(choice (const :tag "Always require confirmation" nil)
|
||
|
(const :tag "Never require confirmation" t)
|
||
|
(set :tag "Require confirmation except for"
|
||
|
;; `remove-dirty-modules' and
|
||
|
;; `trash-module-gitdirs' intentionally
|
||
|
;; omitted.
|
||
|
,@magit--confirm-actions)))
|
||
|
|
||
|
(defcustom magit-slow-confirm '(drop-stashes)
|
||
|
"Whether to ask user \"y or n\" or \"yes or no\" questions.
|
||
|
|
||
|
When this is nil, then `y-or-n-p' is used when the user has to
|
||
|
confirm a potentially destructive action. When this is t, then
|
||
|
`yes-or-no-p' is used instead. If this is a list of symbols
|
||
|
identifying actions, then `yes-or-no-p' is used for those,
|
||
|
`y-or-no-p' for all others. The list of actions is the same as
|
||
|
for `magit-no-confirm' (which see)."
|
||
|
:package-version '(magit . "2.9.0")
|
||
|
:group 'magit-miscellaneous
|
||
|
:type `(choice (const :tag "Always ask \"yes or no\" questions" t)
|
||
|
(const :tag "Always ask \"y or n\" questions" nil)
|
||
|
(set :tag "Ask \"yes or no\" questions only for"
|
||
|
,@magit--confirm-actions)))
|
||
|
|
||
|
(defcustom magit-no-message nil
|
||
|
"A list of messages Magit should not display.
|
||
|
|
||
|
Magit displays most echo area messages using `message', but a few
|
||
|
are displayed using `magit-message' instead, which takes the same
|
||
|
arguments as the former, FORMAT-STRING and ARGS. `magit-message'
|
||
|
forgoes printing a message if any member of this list is a prefix
|
||
|
of the respective FORMAT-STRING.
|
||
|
|
||
|
If Magit prints a message which causes you grief, then please
|
||
|
first investigate whether there is another option which can be
|
||
|
used to suppress it. If that is not the case, then ask the Magit
|
||
|
maintainers to start using `magit-message' instead of `message'
|
||
|
in that case. We are not proactively replacing all uses of
|
||
|
`message' with `magit-message', just in case someone *might* find
|
||
|
some of these messages useless.
|
||
|
|
||
|
Messages which can currently be suppressed using this option are:
|
||
|
* \"Turning on magit-auto-revert-mode...\""
|
||
|
:package-version '(magit . "2.8.0")
|
||
|
:group 'magit-miscellaneous
|
||
|
:type '(repeat string))
|
||
|
|
||
|
(defcustom magit-ellipsis ?…
|
||
|
"Character used to abbreviate text.
|
||
|
|
||
|
Currently this is used to abbreviate author names in the margin
|
||
|
and in process buffers to elide `magit-git-global-arguments'."
|
||
|
:package-version '(magit . "2.1.0")
|
||
|
:group 'magit-miscellaneous
|
||
|
:type 'character)
|
||
|
|
||
|
(defcustom magit-update-other-window-delay 0.2
|
||
|
"Delay before automatically updating the other window.
|
||
|
|
||
|
When moving around in certain buffers, then certain other
|
||
|
buffers, which are being displayed in another window, may
|
||
|
optionally be updated to display information about the
|
||
|
section at point.
|
||
|
|
||
|
When holding down a key to move by more than just one section,
|
||
|
then that would update that buffer for each section on the way.
|
||
|
To prevent that, updating the revision buffer is delayed, and
|
||
|
this option controls for how long. For optimal experience you
|
||
|
might have to adjust this delay and/or the keyboard repeat rate
|
||
|
and delay of your graphical environment or operating system."
|
||
|
:package-version '(magit . "2.3.0")
|
||
|
:group 'magit-miscellaneous
|
||
|
:type 'number)
|
||
|
|
||
|
(defcustom magit-view-git-manual-method 'info
|
||
|
"How links to Git documentation are followed from Magit's Info manuals.
|
||
|
|
||
|
`info' Follow the link to the node in the `gitman' Info manual
|
||
|
as usual. Unfortunately that manual is not installed by
|
||
|
default on some platforms, and when it is then the nodes
|
||
|
look worse than the actual manpages.
|
||
|
|
||
|
`man' View the respective man-page using the `man' package.
|
||
|
|
||
|
`woman' View the respective man-page using the `woman' package."
|
||
|
:package-version '(magit . "2.9.0")
|
||
|
:group 'magit-miscellaneous
|
||
|
:type '(choice (const :tag "view info manual" info)
|
||
|
(const :tag "view manpage using `man'" man)
|
||
|
(const :tag "view manpage using `woman'" woman)))
|
||
|
|
||
|
;;; User Input
|
||
|
|
||
|
(defvar helm-completion-in-region-default-sort-fn)
|
||
|
(defvar helm-crm-default-separator)
|
||
|
(defvar ivy-sort-functions-alist)
|
||
|
|
||
|
(defvar magit-completing-read--silent-default nil)
|
||
|
|
||
|
(defun magit-completing-read (prompt collection &optional
|
||
|
predicate require-match initial-input
|
||
|
hist def fallback)
|
||
|
"Read a choice in the minibuffer, or use the default choice.
|
||
|
|
||
|
This is the function that Magit commands use when they need the
|
||
|
user to select a single thing to act on. The arguments have the
|
||
|
same meaning as for `completing-read', except for FALLBACK, which
|
||
|
is unique to this function and is described below.
|
||
|
|
||
|
Instead of asking the user to choose from a list of possible
|
||
|
candidates, this function may instead just return the default
|
||
|
specified by DEF, with or without requiring user confirmation.
|
||
|
Whether that is the case depends on PROMPT, `this-command' and
|
||
|
`magit-dwim-selection'. See the documentation of the latter for
|
||
|
more information.
|
||
|
|
||
|
If it does use the default without the user even having to
|
||
|
confirm that, then `magit-completing-read--silent-default' is set
|
||
|
to t, otherwise nil.
|
||
|
|
||
|
If it does read a value in the minibuffer, then this function
|
||
|
acts similarly to `completing-read', except for the following:
|
||
|
|
||
|
- COLLECTION must be a list of choices. A function is not
|
||
|
supported.
|
||
|
|
||
|
- If REQUIRE-MATCH is nil and the user exits without a choice,
|
||
|
then nil is returned instead of an empty string.
|
||
|
|
||
|
- If REQUIRE-MATCH is non-nil and the user exits without a
|
||
|
choice, `user-error' is raised.
|
||
|
|
||
|
- FALLBACK specifies a secondary default that is only used if
|
||
|
the primary default DEF is nil. The secondary default is not
|
||
|
subject to `magit-dwim-selection' — if DEF is nil but FALLBACK
|
||
|
is not, then this function always asks the user to choose a
|
||
|
candidate, just as if both defaults were nil.
|
||
|
|
||
|
- \": \" is appended to PROMPT.
|
||
|
|
||
|
- PROMPT is modified to end with \" (default DEF|FALLBACK): \"
|
||
|
provided that DEF or FALLBACK is non-nil, that neither
|
||
|
`ivy-mode' nor `helm-mode' is enabled, and that
|
||
|
`magit-completing-read-function' is set to its default value of
|
||
|
`magit-builtin-completing-read'."
|
||
|
(setq magit-completing-read--silent-default nil)
|
||
|
(if-let ((dwim (and def
|
||
|
(nth 2 (-first (pcase-lambda (`(,cmd ,re ,_))
|
||
|
(and (eq this-command cmd)
|
||
|
(or (not re)
|
||
|
(string-match-p re prompt))))
|
||
|
magit-dwim-selection)))))
|
||
|
(if (eq dwim 'ask)
|
||
|
(if (y-or-n-p (format "%s %s? " prompt def))
|
||
|
def
|
||
|
(user-error "Abort"))
|
||
|
(setq magit-completing-read--silent-default t)
|
||
|
def)
|
||
|
(unless def
|
||
|
(setq def fallback))
|
||
|
(let ((command this-command)
|
||
|
(reply (funcall magit-completing-read-function
|
||
|
(concat prompt ": ")
|
||
|
(if (and def (not (member def collection)))
|
||
|
(cons def collection)
|
||
|
collection)
|
||
|
predicate
|
||
|
require-match initial-input hist def)))
|
||
|
(setq this-command command)
|
||
|
(if (string= reply "")
|
||
|
(if require-match
|
||
|
(user-error "Nothing selected")
|
||
|
nil)
|
||
|
reply))))
|
||
|
|
||
|
(defun magit--completion-table (collection)
|
||
|
(lambda (string pred action)
|
||
|
(if (eq action 'metadata)
|
||
|
'(metadata (display-sort-function . identity))
|
||
|
(complete-with-action action collection string pred))))
|
||
|
|
||
|
(defun magit-builtin-completing-read
|
||
|
(prompt choices &optional predicate require-match initial-input hist def)
|
||
|
"Magit wrapper for standard `completing-read' function."
|
||
|
(unless (or (bound-and-true-p helm-mode)
|
||
|
(bound-and-true-p ivy-mode))
|
||
|
(setq prompt (magit-prompt-with-default prompt def))
|
||
|
(setq choices (magit--completion-table choices)))
|
||
|
(cl-letf (((symbol-function 'completion-pcm--all-completions)
|
||
|
#'magit-completion-pcm--all-completions))
|
||
|
(let ((ivy-sort-functions-alist nil))
|
||
|
(completing-read prompt choices
|
||
|
predicate require-match
|
||
|
initial-input hist def))))
|
||
|
|
||
|
(defun magit-completing-read-multiple
|
||
|
(prompt choices &optional sep default hist keymap)
|
||
|
"Read multiple items from CHOICES, separated by SEP.
|
||
|
|
||
|
Set up the `crm' variables needed to read multiple values with
|
||
|
`read-from-minibuffer'.
|
||
|
|
||
|
SEP is a regexp matching characters that can separate choices.
|
||
|
When SEP is nil, it defaults to `crm-default-separator'.
|
||
|
DEFAULT, HIST, and KEYMAP are passed to `read-from-minibuffer'.
|
||
|
When KEYMAP is nil, it defaults to `crm-local-completion-map'.
|
||
|
|
||
|
Unlike `completing-read-multiple', the return value is not split
|
||
|
into a list."
|
||
|
(let* ((crm-separator (or sep crm-default-separator))
|
||
|
(crm-completion-table (magit--completion-table choices))
|
||
|
(choose-completion-string-functions
|
||
|
'(crm--choose-completion-string))
|
||
|
(minibuffer-completion-table #'crm--collection-fn)
|
||
|
(minibuffer-completion-confirm t)
|
||
|
(helm-completion-in-region-default-sort-fn nil)
|
||
|
(helm-crm-default-separator nil)
|
||
|
(input
|
||
|
(cl-letf (((symbol-function 'completion-pcm--all-completions)
|
||
|
#'magit-completion-pcm--all-completions))
|
||
|
(read-from-minibuffer
|
||
|
(concat prompt (and default (format " (%s)" default)) ": ")
|
||
|
nil (or keymap crm-local-completion-map)
|
||
|
nil hist default))))
|
||
|
(when (string-equal input "")
|
||
|
(or (setq input default)
|
||
|
(user-error "Nothing selected")))
|
||
|
input))
|
||
|
|
||
|
(defun magit-completing-read-multiple*
|
||
|
(prompt table &optional predicate require-match initial-input
|
||
|
hist def inherit-input-method)
|
||
|
"Read multiple strings in the minibuffer, with completion.
|
||
|
Like `completing-read-multiple' but don't mess with order of
|
||
|
TABLE. Also bind `helm-completion-in-region-default-sort-fn'
|
||
|
to nil."
|
||
|
(unwind-protect
|
||
|
(cl-letf (((symbol-function 'completion-pcm--all-completions)
|
||
|
#'magit-completion-pcm--all-completions))
|
||
|
(add-hook 'choose-completion-string-functions
|
||
|
'crm--choose-completion-string)
|
||
|
(let* ((minibuffer-completion-table #'crm--collection-fn)
|
||
|
(minibuffer-completion-predicate predicate)
|
||
|
;; see completing_read in src/minibuf.c
|
||
|
(minibuffer-completion-confirm
|
||
|
(unless (eq require-match t) require-match))
|
||
|
(crm-completion-table (magit--completion-table table))
|
||
|
(map (if require-match
|
||
|
crm-local-must-match-map
|
||
|
crm-local-completion-map))
|
||
|
(helm-completion-in-region-default-sort-fn nil)
|
||
|
;; If the user enters empty input, `read-from-minibuffer'
|
||
|
;; returns the empty string, not DEF.
|
||
|
(input (read-from-minibuffer
|
||
|
prompt initial-input map
|
||
|
nil hist def inherit-input-method)))
|
||
|
(and def (string-equal input "") (setq input def))
|
||
|
;; Remove empty strings in the list of read strings.
|
||
|
(split-string input crm-separator t)))
|
||
|
(remove-hook 'choose-completion-string-functions
|
||
|
'crm--choose-completion-string)))
|
||
|
|
||
|
(defun magit-ido-completing-read
|
||
|
(prompt choices &optional predicate require-match initial-input hist def)
|
||
|
"Ido-based `completing-read' almost-replacement.
|
||
|
|
||
|
Unfortunately `ido-completing-read' is not suitable as a
|
||
|
drop-in replacement for `completing-read', instead we use
|
||
|
`ido-completing-read+' from the third-party package by the
|
||
|
same name."
|
||
|
(if (require 'ido-completing-read+ nil t)
|
||
|
(ido-completing-read+ prompt choices predicate require-match
|
||
|
initial-input hist
|
||
|
(or def (and require-match (car choices))))
|
||
|
(display-warning 'magit "ido-completing-read+ is not installed
|
||
|
|
||
|
To use Ido completion with Magit you need to install the
|
||
|
third-party `ido-completing-read+' packages. Falling
|
||
|
back to built-in `completing-read' for now." :error)
|
||
|
(magit-builtin-completing-read prompt choices predicate require-match
|
||
|
initial-input hist def)))
|
||
|
|
||
|
(defun magit-prompt-with-default (prompt def)
|
||
|
(if (and def (> (length prompt) 2)
|
||
|
(string-equal ": " (substring prompt -2)))
|
||
|
(format "%s (default %s): " (substring prompt 0 -2) def)
|
||
|
prompt))
|
||
|
|
||
|
(defvar magit-minibuffer-local-ns-map
|
||
|
(let ((map (make-sparse-keymap)))
|
||
|
(set-keymap-parent map minibuffer-local-map)
|
||
|
(define-key map "\s" 'magit-whitespace-disallowed)
|
||
|
(define-key map "\t" 'magit-whitespace-disallowed)
|
||
|
map))
|
||
|
|
||
|
(defun magit-whitespace-disallowed ()
|
||
|
"Beep to tell the user that whitespace is not allowed."
|
||
|
(interactive)
|
||
|
(ding)
|
||
|
(message "Whitespace isn't allowed here")
|
||
|
(setq defining-kbd-macro nil)
|
||
|
(force-mode-line-update))
|
||
|
|
||
|
(defun magit-read-string (prompt &optional initial-input history default-value
|
||
|
inherit-input-method no-whitespace)
|
||
|
"Read a string from the minibuffer, prompting with string PROMPT.
|
||
|
|
||
|
This is similar to `read-string', but
|
||
|
* empty input is only allowed if DEFAULT-VALUE is non-nil in
|
||
|
which case that is returned,
|
||
|
* whitespace is not allowed and leading and trailing whitespace is
|
||
|
removed automatically if NO-WHITESPACE is non-nil,
|
||
|
* \": \" is appended to PROMPT, and
|
||
|
* an invalid DEFAULT-VALUE is silently ignored."
|
||
|
(when default-value
|
||
|
(when (consp default-value)
|
||
|
(setq default-value (car default-value)))
|
||
|
(unless (stringp default-value)
|
||
|
(setq default-value nil)))
|
||
|
(let* ((minibuffer-completion-table nil)
|
||
|
(val (read-from-minibuffer
|
||
|
(magit-prompt-with-default (concat prompt ": ") default-value)
|
||
|
initial-input (and no-whitespace magit-minibuffer-local-ns-map)
|
||
|
nil history default-value inherit-input-method))
|
||
|
(trim (lambda (regexp string)
|
||
|
(save-match-data
|
||
|
(if (string-match regexp string)
|
||
|
(replace-match "" t t string)
|
||
|
string)))))
|
||
|
(when (and (string= val "") default-value)
|
||
|
(setq val default-value))
|
||
|
(when no-whitespace
|
||
|
(setq val (funcall trim "\\`\\(?:[ \t\n\r]+\\)"
|
||
|
(funcall trim "\\(?:[ \t\n\r]+\\)\\'" val))))
|
||
|
(cond ((string= val "")
|
||
|
(user-error "Need non-empty input"))
|
||
|
((and no-whitespace (string-match-p "[\s\t\n]" val))
|
||
|
(user-error "Input contains whitespace"))
|
||
|
(t val))))
|
||
|
|
||
|
(defun magit-read-string-ns (prompt &optional initial-input history
|
||
|
default-value inherit-input-method)
|
||
|
"Call `magit-read-string' with non-nil NO-WHITESPACE."
|
||
|
(magit-read-string prompt initial-input history default-value
|
||
|
inherit-input-method t))
|
||
|
|
||
|
(defmacro magit-read-char-case (prompt verbose &rest clauses)
|
||
|
(declare (indent 2)
|
||
|
(debug (form form &rest (characterp form body))))
|
||
|
`(prog1 (pcase (read-char-choice
|
||
|
(concat ,prompt
|
||
|
,(concat (mapconcat 'cadr clauses ", ")
|
||
|
(and verbose ", or [C-g] to abort") " "))
|
||
|
',(mapcar 'car clauses))
|
||
|
,@(--map `(,(car it) ,@(cddr it)) clauses))
|
||
|
(message "")))
|
||
|
|
||
|
(defun magit-y-or-n-p (prompt &optional action)
|
||
|
"Ask user a \"y or n\" or a \"yes or no\" question using PROMPT.
|
||
|
Which kind of question is used depends on whether
|
||
|
ACTION is a member of option `magit-slow-confirm'."
|
||
|
(if (or (eq magit-slow-confirm t)
|
||
|
(and action (member action magit-slow-confirm)))
|
||
|
(yes-or-no-p prompt)
|
||
|
(y-or-n-p prompt)))
|
||
|
|
||
|
(defvar magit--no-confirm-alist
|
||
|
'((safe-with-wip magit-wip-before-change-mode
|
||
|
discard reverse stage-all-changes unstage-all-changes)))
|
||
|
|
||
|
(cl-defun magit-confirm (action &optional prompt prompt-n noabort
|
||
|
(items nil sitems))
|
||
|
(declare (indent defun))
|
||
|
(setq prompt-n (format (concat (or prompt-n prompt) "? ") (length items)))
|
||
|
(setq prompt (format (concat (or prompt (magit-confirm-make-prompt action))
|
||
|
"? ")
|
||
|
(car items)))
|
||
|
(or (cond ((and (not (eq action t))
|
||
|
(or (eq magit-no-confirm t)
|
||
|
(memq action magit-no-confirm)
|
||
|
(cl-member-if (pcase-lambda (`(,key ,var . ,sub))
|
||
|
(and (memq key magit-no-confirm)
|
||
|
(memq action sub)
|
||
|
(or (not var)
|
||
|
(and (boundp var)
|
||
|
(symbol-value var)))))
|
||
|
magit--no-confirm-alist)))
|
||
|
(or (not sitems) items))
|
||
|
((not sitems)
|
||
|
(magit-y-or-n-p prompt action))
|
||
|
((= (length items) 1)
|
||
|
(and (magit-y-or-n-p prompt action) items))
|
||
|
((> (length items) 1)
|
||
|
(and (magit-y-or-n-p (concat (mapconcat #'identity items "\n")
|
||
|
"\n\n" prompt-n)
|
||
|
action)
|
||
|
items)))
|
||
|
(if noabort nil (user-error "Abort"))))
|
||
|
|
||
|
(defun magit-confirm-files (action files &optional prompt)
|
||
|
(when files
|
||
|
(unless prompt
|
||
|
(setq prompt (magit-confirm-make-prompt action)))
|
||
|
(magit-confirm action
|
||
|
(concat prompt " %s")
|
||
|
(concat prompt " %i files")
|
||
|
nil files)))
|
||
|
|
||
|
(defun magit-confirm-make-prompt (action)
|
||
|
(let ((prompt (symbol-name action)))
|
||
|
(replace-regexp-in-string
|
||
|
"-" " " (concat (upcase (substring prompt 0 1)) (substring prompt 1)))))
|
||
|
|
||
|
(defun magit-read-number-string (prompt &optional default _history)
|
||
|
"Like `read-number' but return value is a string.
|
||
|
DEFAULT may be a number or a numeric string."
|
||
|
(number-to-string
|
||
|
(read-number prompt (if (stringp default)
|
||
|
(string-to-number default)
|
||
|
default))))
|
||
|
|
||
|
;;; Debug Utilities
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun magit-emacs-Q-command ()
|
||
|
"Show a shell command that runs an uncustomized Emacs with only Magit loaded.
|
||
|
See info node `(magit)Debugging Tools' for more information."
|
||
|
(interactive)
|
||
|
(let ((cmd (mapconcat
|
||
|
#'shell-quote-argument
|
||
|
`(,(concat invocation-directory invocation-name)
|
||
|
"-Q" "--eval" "(setq debug-on-error t)"
|
||
|
,@(cl-mapcan
|
||
|
(lambda (dir) (list "-L" dir))
|
||
|
(delete-dups
|
||
|
(cl-mapcan
|
||
|
(lambda (lib)
|
||
|
(let ((path (locate-library lib)))
|
||
|
(cond
|
||
|
(path
|
||
|
(list (file-name-directory path)))
|
||
|
((not (equal lib "libgit"))
|
||
|
(error "Cannot find mandatory dependency %s" lib)))))
|
||
|
'(;; Like `LOAD_PATH' in `default.mk'.
|
||
|
"dash"
|
||
|
"libgit"
|
||
|
"transient"
|
||
|
"with-editor"
|
||
|
;; Obviously `magit' itself is needed too.
|
||
|
"magit"
|
||
|
;; While this is part of the Magit repository,
|
||
|
;; it is distributed as a separate package.
|
||
|
"git-commit"
|
||
|
;; Even though `async' is a dependency of the
|
||
|
;; `magit' package, it is not required here.
|
||
|
))))
|
||
|
;; Avoid Emacs bug#16406 by using full path.
|
||
|
"-l" ,(file-name-sans-extension (locate-library "magit")))
|
||
|
" ")))
|
||
|
(message "Uncustomized Magit command saved to kill-ring, %s"
|
||
|
"please run it in a terminal.")
|
||
|
(kill-new cmd)))
|
||
|
|
||
|
;;; Text Utilities
|
||
|
|
||
|
(defmacro magit-bind-match-strings (varlist string &rest body)
|
||
|
"Bind variables to submatches according to VARLIST then evaluate BODY.
|
||
|
Bind the symbols in VARLIST to submatches of the current match
|
||
|
data, starting with 1 and incrementing by 1 for each symbol. If
|
||
|
the last match was against a string, then that has to be provided
|
||
|
as STRING."
|
||
|
(declare (indent 2) (debug (listp form body)))
|
||
|
(let ((s (cl-gensym "string"))
|
||
|
(i 0))
|
||
|
`(let ((,s ,string))
|
||
|
(let ,(save-match-data
|
||
|
(--map (list it (list 'match-string (cl-incf i) s)) varlist))
|
||
|
,@body))))
|
||
|
|
||
|
(defun magit-delete-line ()
|
||
|
"Delete the rest of the current line."
|
||
|
(delete-region (point) (1+ (line-end-position))))
|
||
|
|
||
|
(defun magit-delete-match (&optional num)
|
||
|
"Delete text matched by last search.
|
||
|
If optional NUM is specified, only delete that subexpression."
|
||
|
(delete-region (match-beginning (or num 0))
|
||
|
(match-end (or num 0))))
|
||
|
|
||
|
(defun magit-file-line (file)
|
||
|
"Return the first line of FILE as a string."
|
||
|
(when (file-regular-p file)
|
||
|
(with-temp-buffer
|
||
|
(insert-file-contents file)
|
||
|
(buffer-substring-no-properties (point-min)
|
||
|
(line-end-position)))))
|
||
|
|
||
|
(defun magit-file-lines (file &optional keep-empty-lines)
|
||
|
"Return a list of strings containing one element per line in FILE.
|
||
|
Unless optional argument KEEP-EMPTY-LINES is t, trim all empty lines."
|
||
|
(when (file-regular-p file)
|
||
|
(with-temp-buffer
|
||
|
(insert-file-contents file)
|
||
|
(split-string (buffer-string) "\n" (not keep-empty-lines)))))
|
||
|
|
||
|
(defun magit-set-header-line-format (string)
|
||
|
"Set the header-line using STRING.
|
||
|
Propertize STRING with the `magit-header-line'. If the `face'
|
||
|
property of any part of STRING is already set, then that takes
|
||
|
precedence. Also pad the left side of STRING so that it aligns
|
||
|
with the text area."
|
||
|
(setq header-line-format
|
||
|
(concat (propertize " " 'display '(space :align-to 0))
|
||
|
string)))
|
||
|
|
||
|
(defun magit-face-property-all (face string)
|
||
|
"Return non-nil if FACE is present in all of STRING."
|
||
|
(cl-loop for pos = 0 then (next-single-property-change
|
||
|
pos 'font-lock-face string)
|
||
|
unless pos
|
||
|
return t
|
||
|
for current = (get-text-property pos 'font-lock-face string)
|
||
|
unless (if (consp current)
|
||
|
(memq face current)
|
||
|
(eq face current))
|
||
|
return nil))
|
||
|
|
||
|
(defun magit--add-face-text-property (beg end face &optional append object)
|
||
|
"Like `add-face-text-property' but for `font-lock-face'."
|
||
|
(cl-loop for pos = (next-single-property-change
|
||
|
beg 'font-lock-face object end)
|
||
|
for current = (get-text-property beg 'font-lock-face object)
|
||
|
for newface = (if (listp current)
|
||
|
(if append
|
||
|
(append current (list face))
|
||
|
(cons face current))
|
||
|
(if append
|
||
|
(list current face)
|
||
|
(list face current)))
|
||
|
do (progn (put-text-property beg pos 'font-lock-face newface object)
|
||
|
(setq beg pos))
|
||
|
while (< beg end)))
|
||
|
|
||
|
(defun magit--propertize-face (string face)
|
||
|
(propertize string 'face face 'font-lock-face face))
|
||
|
|
||
|
(defun magit--put-face (beg end face string)
|
||
|
(put-text-property beg end 'face face string)
|
||
|
(put-text-property beg end 'font-lock-face face string))
|
||
|
|
||
|
(defun magit--format-spec (format specification)
|
||
|
"Like `format-spec' but preserve text properties in SPECIFICATION."
|
||
|
(with-temp-buffer
|
||
|
(insert format)
|
||
|
(goto-char (point-min))
|
||
|
(while (search-forward "%" nil t)
|
||
|
(cond
|
||
|
;; Quoted percent sign.
|
||
|
((eq (char-after) ?%)
|
||
|
(delete-char 1))
|
||
|
;; Valid format spec.
|
||
|
((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]\\)")
|
||
|
(let* ((num (match-string 1))
|
||
|
(spec (string-to-char (match-string 2)))
|
||
|
(val (assq spec specification)))
|
||
|
(unless val
|
||
|
(error "Invalid format character: `%%%c'" spec))
|
||
|
(setq val (cdr val))
|
||
|
;; Pad result to desired length.
|
||
|
(let ((text (format (concat "%" num "s") val)))
|
||
|
;; Insert first, to preserve text properties.
|
||
|
(if (next-property-change 0 (concat " " text))
|
||
|
;; If the inserted text has properties, then preserve those.
|
||
|
(insert text)
|
||
|
;; Otherwise preserve FORMAT's properties, like `format-spec'.
|
||
|
(insert-and-inherit text))
|
||
|
;; Delete the specifier body.
|
||
|
(delete-region (+ (match-beginning 0) (length text))
|
||
|
(+ (match-end 0) (length text)))
|
||
|
;; Delete the percent sign.
|
||
|
(delete-region (1- (match-beginning 0)) (match-beginning 0)))))
|
||
|
;; Signal an error on bogus format strings.
|
||
|
(t
|
||
|
(error "Invalid format string"))))
|
||
|
(buffer-string)))
|
||
|
|
||
|
;;; Missing from Emacs
|
||
|
|
||
|
(defun magit-kill-this-buffer ()
|
||
|
"Kill the current buffer."
|
||
|
(interactive)
|
||
|
(kill-buffer (current-buffer)))
|
||
|
|
||
|
(defun magit--buffer-string (&optional min max trim)
|
||
|
"Like `buffer-substring-no-properties' but the arguments are optional.
|
||
|
|
||
|
This combines the benefits of `buffer-string', `buffer-substring'
|
||
|
and `buffer-substring-no-properties' into one function that is
|
||
|
not as painful to use as the latter. I.e. you can write
|
||
|
(magit--buffer-string)
|
||
|
instead of
|
||
|
(buffer-substring-no-properties (point-min)
|
||
|
(point-max))
|
||
|
|
||
|
Optional MIN defaults to the value of `point-min'.
|
||
|
Optional MAX defaults to the value of `point-max'.
|
||
|
|
||
|
If optional TRIM is non-nil, then all leading and trailing
|
||
|
whitespace is remove. If it is the newline character, then
|
||
|
one trailing newline is added."
|
||
|
;; Lets write that one last time and be done with it:
|
||
|
(let ((str (buffer-substring-no-properties (or min (point-min))
|
||
|
(or max (point-max)))))
|
||
|
(if trim
|
||
|
(concat (string-trim str)
|
||
|
(and (eq trim ?\n) "\n"))
|
||
|
str)))
|
||
|
|
||
|
;;; Kludges for Emacs Bugs
|
||
|
|
||
|
(defun magit-file-accessible-directory-p (filename)
|
||
|
"Like `file-accessible-directory-p' but work around an Apple bug.
|
||
|
See http://debbugs.gnu.org/cgi/bugreport.cgi?bug=21573#17
|
||
|
and https://github.com/magit/magit/issues/2295."
|
||
|
(and (file-directory-p filename)
|
||
|
(file-accessible-directory-p filename)))
|
||
|
|
||
|
(when (version<= "25.1" emacs-version)
|
||
|
(with-eval-after-load 'vc-git
|
||
|
(defun vc-git-conflicted-files (directory)
|
||
|
"Return the list of files with conflicts in DIRECTORY."
|
||
|
(let* ((status
|
||
|
(vc-git--run-command-string directory "diff-files"
|
||
|
"--name-status"))
|
||
|
(lines (when status (split-string status "\n" 'omit-nulls)))
|
||
|
files)
|
||
|
(dolist (line lines files)
|
||
|
(when (string-match "\\([ MADRCU?!]\\)[ \t]+\\(.+\\)" line)
|
||
|
(let ((state (match-string 1 line))
|
||
|
(file (match-string 2 line)))
|
||
|
(when (equal state "U")
|
||
|
(push (expand-file-name file directory) files)))))))))
|
||
|
|
||
|
(when (< emacs-major-version 27)
|
||
|
(defun vc-git--call@bug21559 (fn buffer command &rest args)
|
||
|
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
|
||
|
(let ((process-environment process-environment))
|
||
|
(when revert-buffer-in-progress-p
|
||
|
(push "GIT_OPTIONAL_LOCKS=0" process-environment))
|
||
|
(apply fn buffer command args)))
|
||
|
(advice-add 'vc-git--call :around 'vc-git--call@bug21559)
|
||
|
|
||
|
(defun vc-git-command@bug21559
|
||
|
(fn buffer okstatus file-or-list &rest flags)
|
||
|
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
|
||
|
(let ((process-environment process-environment))
|
||
|
(when revert-buffer-in-progress-p
|
||
|
(push "GIT_OPTIONAL_LOCKS=0" process-environment))
|
||
|
(apply fn buffer okstatus file-or-list flags)))
|
||
|
(advice-add 'vc-git-command :around 'vc-git-command@bug21559)
|
||
|
|
||
|
(defun auto-revert-handler@bug21559 (fn)
|
||
|
"Backport https://debbugs.gnu.org/cgi/bugreport.cgi?bug=21559."
|
||
|
(let ((revert-buffer-in-progress-p t))
|
||
|
(funcall fn)))
|
||
|
(advice-add 'auto-revert-handler :around 'auto-revert-handler@bug21559)
|
||
|
)
|
||
|
|
||
|
;; `completion-pcm--all-completions' reverses the completion list. To
|
||
|
;; preserve the order of our pre-sorted completions, we'll temporarily
|
||
|
;; override it with the function below. bug#24676
|
||
|
(defun magit-completion-pcm--all-completions (prefix pattern table pred)
|
||
|
(if (completion-pcm--pattern-trivial-p pattern)
|
||
|
(all-completions (concat prefix (car pattern)) table pred)
|
||
|
(let* ((regex (completion-pcm--pattern->regex pattern))
|
||
|
(case-fold-search completion-ignore-case)
|
||
|
(completion-regexp-list (cons regex completion-regexp-list))
|
||
|
(compl (all-completions
|
||
|
(concat prefix
|
||
|
(if (stringp (car pattern)) (car pattern) ""))
|
||
|
table pred)))
|
||
|
(if (not (functionp table))
|
||
|
compl
|
||
|
(let ((poss ()))
|
||
|
(dolist (c compl)
|
||
|
(when (string-match-p regex c) (push c poss)))
|
||
|
;; This `nreverse' call is the only code change made to the
|
||
|
;; `completion-pcm--all-completions' that shipped with Emacs 25.1.
|
||
|
(nreverse poss))))))
|
||
|
|
||
|
(defun magit-which-function ()
|
||
|
"Return current function name based on point.
|
||
|
|
||
|
This is a simple wrapper around `which-function', that resets
|
||
|
Imenu's potentially outdated and therefore unreliable cache by
|
||
|
setting `imenu--index-alist' to nil before calling that function."
|
||
|
(setq imenu--index-alist nil)
|
||
|
(which-function))
|
||
|
|
||
|
;;; Kludges for Custom
|
||
|
|
||
|
(defun magit-custom-initialize-reset (symbol exp)
|
||
|
"Initialize SYMBOL based on EXP.
|
||
|
Set the symbol, using `set-default' (unlike
|
||
|
`custom-initialize-reset' which uses the `:set' function if any.)
|
||
|
The value is either the symbol's current value
|
||
|
(as obtained using the `:get' function), if any,
|
||
|
or the value in the symbol's `saved-value' property if any,
|
||
|
or (last of all) the value of EXP."
|
||
|
(set-default-toplevel-value
|
||
|
symbol
|
||
|
(condition-case nil
|
||
|
(let ((def (default-toplevel-value symbol))
|
||
|
(getter (get symbol 'custom-get)))
|
||
|
(if getter (funcall getter symbol) def))
|
||
|
(error
|
||
|
(eval (let ((sv (get symbol 'saved-value)))
|
||
|
(if sv (car sv) exp)))))))
|
||
|
|
||
|
(defun magit-hook-custom-get (symbol)
|
||
|
(if (symbol-file symbol 'defvar)
|
||
|
(default-toplevel-value symbol)
|
||
|
;;
|
||
|
;; Called by `custom-initialize-reset' on behalf of `symbol's
|
||
|
;; `defcustom', which is being evaluated for the first time to
|
||
|
;; set the initial value, but there's already a default value,
|
||
|
;; which most likely was established by one or more `add-hook'
|
||
|
;; calls.
|
||
|
;;
|
||
|
;; We combine the `standard-value' and the current value, while
|
||
|
;; preserving the order established by `:options', and return
|
||
|
;; the result of that to be used as the "initial" default value.
|
||
|
;;
|
||
|
(let ((standard (eval (car (get symbol 'standard-value))))
|
||
|
(current (default-toplevel-value symbol))
|
||
|
(value nil))
|
||
|
(dolist (fn (get symbol 'custom-options))
|
||
|
(when (or (memq fn standard)
|
||
|
(memq fn current))
|
||
|
(push fn value)))
|
||
|
(dolist (fn current)
|
||
|
(unless (memq fn value)
|
||
|
(push fn value)))
|
||
|
(nreverse value))))
|
||
|
|
||
|
;;; Kludges for Info Manuals
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun Info-follow-nearest-node--magit-gitman (fn &optional fork)
|
||
|
(let ((node (Info-get-token
|
||
|
(point) "\\*note[ \n\t]+"
|
||
|
"\\*note[ \n\t]+\\([^:]*\\):\\(:\\|[ \n\t]*(\\)?")))
|
||
|
(if (and node (string-match "^(gitman)\\(.+\\)" node))
|
||
|
(pcase magit-view-git-manual-method
|
||
|
(`info (funcall fn fork))
|
||
|
(`man (require 'man)
|
||
|
(man (match-string 1 node)))
|
||
|
(`woman (require 'woman)
|
||
|
(woman (match-string 1 node)))
|
||
|
(_
|
||
|
(user-error "Invalid value for `magit-view-git-manual-method'")))
|
||
|
(funcall fn fork))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(advice-add 'Info-follow-nearest-node :around
|
||
|
'Info-follow-nearest-node--magit-gitman)
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun org-man-export--magit-gitman (fn link description format)
|
||
|
(if (and (eq format 'texinfo)
|
||
|
(string-match-p "\\`git" link))
|
||
|
(replace-regexp-in-string "%s" link "
|
||
|
@ifinfo
|
||
|
@ref{%s,,,gitman,}.
|
||
|
@end ifinfo
|
||
|
@ifhtml
|
||
|
@html
|
||
|
the <a href=\"http://git-scm.com/docs/%s\">%s(1)</a> manpage.
|
||
|
@end html
|
||
|
@end ifhtml
|
||
|
@iftex
|
||
|
the %s(1) manpage.
|
||
|
@end iftex
|
||
|
")
|
||
|
(funcall fn link description format)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(advice-add 'org-man-export :around
|
||
|
'org-man-export--magit-gitman)
|
||
|
|
||
|
;;; Kludges for Package Managers
|
||
|
|
||
|
(defun magit--straight-chase-links (filename)
|
||
|
"Chase links in FILENAME until a name that is not a link.
|
||
|
|
||
|
This is the same as `file-chase-links', except that it also
|
||
|
handles fake symlinks that are created by the package manager
|
||
|
straight.el on Windows.
|
||
|
|
||
|
See <https://github.com/raxod502/straight.el/issues/520>."
|
||
|
(when (and (bound-and-true-p straight-symlink-emulation-mode)
|
||
|
(fboundp 'straight-chase-emulated-symlink))
|
||
|
(when-let ((target (straight-chase-emulated-symlink filename)))
|
||
|
(unless (eq target 'broken)
|
||
|
(setq filename target))))
|
||
|
(file-chase-links filename))
|
||
|
|
||
|
;;; Bitmaps
|
||
|
|
||
|
(when (fboundp 'define-fringe-bitmap)
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmap+
|
||
|
[#b00000000
|
||
|
#b00011000
|
||
|
#b00011000
|
||
|
#b01111110
|
||
|
#b01111110
|
||
|
#b00011000
|
||
|
#b00011000
|
||
|
#b00000000])
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmap-
|
||
|
[#b00000000
|
||
|
#b00000000
|
||
|
#b00000000
|
||
|
#b01111110
|
||
|
#b01111110
|
||
|
#b00000000
|
||
|
#b00000000
|
||
|
#b00000000])
|
||
|
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmap>
|
||
|
[#b01100000
|
||
|
#b00110000
|
||
|
#b00011000
|
||
|
#b00001100
|
||
|
#b00011000
|
||
|
#b00110000
|
||
|
#b01100000
|
||
|
#b00000000])
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmapv
|
||
|
[#b00000000
|
||
|
#b10000010
|
||
|
#b11000110
|
||
|
#b01101100
|
||
|
#b00111000
|
||
|
#b00010000
|
||
|
#b00000000
|
||
|
#b00000000])
|
||
|
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmap-bold>
|
||
|
[#b11100000
|
||
|
#b01110000
|
||
|
#b00111000
|
||
|
#b00011100
|
||
|
#b00011100
|
||
|
#b00111000
|
||
|
#b01110000
|
||
|
#b11100000])
|
||
|
(define-fringe-bitmap 'magit-fringe-bitmap-boldv
|
||
|
[#b10000001
|
||
|
#b11000011
|
||
|
#b11100111
|
||
|
#b01111110
|
||
|
#b00111100
|
||
|
#b00011000
|
||
|
#b00000000
|
||
|
#b00000000])
|
||
|
)
|
||
|
|
||
|
;;; Miscellaneous
|
||
|
|
||
|
(defun magit-message (format-string &rest args)
|
||
|
"Display a message at the bottom of the screen, or not.
|
||
|
Like `message', except that if the users configured option
|
||
|
`magit-no-message' to prevent the message corresponding to
|
||
|
FORMAT-STRING to be displayed, then don't."
|
||
|
(unless (--first (string-prefix-p it format-string) magit-no-message)
|
||
|
(apply #'message format-string args)))
|
||
|
|
||
|
(defun magit-msg (format-string &rest args)
|
||
|
"Display a message at the bottom of the screen, but don't log it.
|
||
|
Like `message', except that `message-log-max' is bound to nil."
|
||
|
(let ((message-log-max nil))
|
||
|
(apply #'message format-string args)))
|
||
|
|
||
|
(defmacro magit--with-temp-position (buf pos &rest body)
|
||
|
(declare (indent 2))
|
||
|
`(with-current-buffer ,buf
|
||
|
(save-excursion
|
||
|
(save-restriction
|
||
|
(widen)
|
||
|
(goto-char (or ,pos 1))
|
||
|
,@body))))
|
||
|
|
||
|
;;; _
|
||
|
(provide 'magit-utils)
|
||
|
;;; magit-utils.el ends here
|