mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 21067e7cbe6d7a0f65ff5c317a96b5c337b0b3d8
commit | author | age
5cb5f7 1 ;;; magit-popup.el --- Define prefix-infix-suffix command combos  -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2010-2018  The Magit Project Contributors
4 ;;
5 ;; You should have received a copy of the AUTHORS.md file which
6 ;; lists all contributors.  If not, see http://magit.vc/authors.
7
8 ;; This library was inspired by and replaces library `magit-key-mode',
9 ;; which was written by Phil Jackson <phil@shellarchive.co.uk> and is
10 ;; distributed under the GNU General Public License version 3 or later.
11
12 ;; Author: Jonas Bernoulli <jonas@bernoul.li>
13 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
14
15 ;; Package-Requires: ((emacs "24.4") (async "1.9.2") (dash "2.13.0"))
16 ;; Keywords: bindings
17 ;; Homepage: https://github.com/magit/magit-popup
18
19 ;; Magit-Popup is free software; you can redistribute it and/or modify
20 ;; it under the terms of the GNU General Public License as published by
21 ;; the Free Software Foundation; either version 3, or (at your option)
22 ;; any later version.
23 ;;
24 ;; Magit-Popup is distributed in the hope that it will be useful,
25 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
26 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 ;; GNU General Public License for more details.
28 ;;
29 ;; You should have received a copy of the GNU General Public License
30 ;; along with Magit-Popup.  If not, see http://www.gnu.org/licenses.
31
32 ;;; Commentary:
33
34 ;; This package implements a generic interface for toggling switches
35 ;; and setting options and then invoking an Emacs command which does
36 ;; something with these arguments.  The prototypical use is for the
37 ;; command to call an external process, passing on the arguments as
38 ;; command line arguments.  But this is only one of many possible
39 ;; uses (though the one this library is optimized for).
40
41 ;; With the Emacs concept of "prefix arguments" in mind this could be
42 ;; described as "infix arguments with feedback in a buffer".
43
44 ;; Commands that set the prefix argument for the subsequent command do
45 ;; not limit what that next command could be.  But entering a command
46 ;; console popup does limit the selection to the commands defined for
47 ;; that popup, and so we use the term "infix" instead of "prefix".
48
49 ;;; Code:
50
51 (require 'button)
52 (require 'cl-lib)
53 (require 'dash)
54 (require 'format-spec)
55 (eval-when-compile (require 'subr-x))
56
57 (and (require 'async-bytecomp nil t)
58      (cl-intersection '(all magit)
59                       (bound-and-true-p async-bytecomp-allowed-packages))
60      (fboundp 'async-bytecomp-package-mode)
61      (async-bytecomp-package-mode 1))
62
63 (declare-function info 'info)
64 (declare-function Man-find-section 'man)
65 (declare-function Man-next-section 'man)
66
67 ;; For branch actions.
68 (declare-function magit-branch-set-face 'magit-git)
69
70 ;;; Settings
71 ;;;; Custom Groups
72
73 (defgroup magit-popup nil
74   "Infix arguments with a popup as feedback."
75   :link '(info-link "(magit-popup)")
76   :group 'bindings)
77
78 (defgroup magit-popup-faces nil
79   "Faces used by Magit-Popup."
80   :group 'magit-popup)
81
82 ;;;; Custom Options
83
84 (defcustom magit-popup-display-buffer-action '((display-buffer-below-selected))
85   "The action used to display a popup buffer.
86
87 Popup buffers are displayed using `display-buffer' with the value
88 of this option as ACTION argument.  You can also set this to nil
89 and instead add an entry to `display-buffer-alist'."
90   :package-version '(magit-popup . "2.4.0")
91   :group 'magit-popup
92   :type 'sexp)
93
94 (defcustom magit-popup-manpage-package
95   (if (memq system-type '(windows-nt ms-dos)) 'woman 'man)
96   "The package used to display manpages.
97 One of `man' or `woman'."
98   :group 'magit-popup
99   :type '(choice (const man) (const woman)))
100
101 (defcustom magit-popup-show-help-echo t
102   "Show usage information in the echo area."
103   :group 'magit-popup
104   :type 'boolean)
105
106 (defcustom magit-popup-show-common-commands nil
107   "Whether to initially show section with commands common to all popups.
108 This section can also be toggled temporarily using \
109 \\<magit-popup-mode-map>\\[magit-popup-toggle-show-common-commands]."
110   :package-version '(magit-popup . "2.9.0")
111   :group 'magit-popup
112   :type 'boolean)
113
114 (defcustom magit-popup-use-prefix-argument 'default
115   "Control how prefix arguments affect infix argument popups.
116
117 This option controls the effect that the use of a prefix argument
118 before entering a popup has.
119
120 `default'  With a prefix argument directly invoke the popup's
121            default action (an Emacs command), instead of bringing
122            up the popup.
123
124 `popup'    With a prefix argument bring up the popup, otherwise
125            directly invoke the popup's default action.
126
127 `nil'      Ignore prefix arguments."
128   :group 'magit-popup
129   :type '(choice
130           (const :tag "Call default action instead of showing popup" default)
131           (const :tag "Show popup instead of calling default action" popup)
132           (const :tag "Ignore prefix argument" nil)))
133
134 ;;;; Custom Faces
135
136 (defface magit-popup-heading
137   '((t :inherit font-lock-keyword-face))
138   "Face for key mode header lines."
139   :group 'magit-popup-faces)
140
141 (defface magit-popup-key
142   '((t :inherit font-lock-builtin-face))
143   "Face for key mode buttons."
144   :group 'magit-popup-faces)
145
146 (defface magit-popup-argument
147   '((t :inherit font-lock-warning-face))
148   "Face used to display enabled arguments in popups."
149   :group 'magit-popup-faces)
150
151 (defface magit-popup-disabled-argument
152   '((t :inherit shadow))
153   "Face used to display disabled arguments in popups."
154   :group 'magit-popup-faces)
155
156 (defface magit-popup-option-value
157   '((t :inherit font-lock-string-face))
158   "Face used to display option values in popups."
159   :group 'magit-popup-faces)
160
161 ;;;; Keymap
162
163 (defvar magit-popup-mode-map
164   (let ((map (make-sparse-keymap)))
165     (define-key map [remap self-insert-command] 'magit-invoke-popup-action)
166     (define-key map (kbd "- <t>")               'magit-invoke-popup-switch)
167     (define-key map (kbd "= <t>")               'magit-invoke-popup-option)
168     (define-key map (kbd "C-g")     'magit-popup-quit)
169     (define-key map (kbd "?")       'magit-popup-help)
170     (define-key map (kbd "C-h k")   'magit-popup-help)
171     (define-key map (kbd "C-h i")   'magit-popup-info)
172     (define-key map (kbd "C-t")     'magit-popup-toggle-show-common-commands)
173     (define-key map (kbd "C-c C-c") 'magit-popup-set-default-arguments)
174     (define-key map (kbd "C-x C-s") 'magit-popup-save-default-arguments)
175     (cond ((featurep 'jkl)
176            (define-key map (kbd "C-p") 'universal-argument)
177            (define-key map [return]    'push-button)
178            (define-key map (kbd "C-i") 'backward-button)
179            (define-key map (kbd "C-k") 'forward-button))
180           (t
181            (define-key map (kbd "C-m") 'push-button)
182            (define-key map (kbd "DEL") 'backward-button)
183            (define-key map (kbd "C-p") 'backward-button)
184            (define-key map (kbd "C-i") 'forward-button)
185            (define-key map (kbd "C-n") 'forward-button)))
186     map)
187   "Keymap for `magit-popup-mode'.
188
189 \\<magit-popup-mode-map>\
190 This keymap contains bindings common to all popups.  A section
191 listing these commands can be shown or hidden using \
192 \\[magit-popup-toggle-show-common-commands].
193
194 The prefix used to toggle any switch can be changed by binding
195 another key to `magit-invoke-popup-switch'.  Likewise binding
196 another key to `magit-invoke-popup-option' changes the prefixed
197 used to set any option.  The two prefixes have to be different.
198 If you change these bindings, you should also change the `prefix'
199 property of the button types `magit-popup-switch-button' and
200 `magit-popup-option-button'.
201
202 If you change any other binding, then you might have to also edit
203 `magit-popup-common-commands' for things to align correctly in
204 the section listing these commands.
205
206 Never bind an alphabetic character in this keymap or you might
207 make it impossible to invoke certain actions.")
208
209 (defvar magit-popup-common-commands
210   '(("Set defaults"          magit-popup-set-default-arguments)
211     ("View popup manual"     magit-popup-info)
212     ("Toggle this section"   magit-popup-toggle-show-common-commands)
213     ("Save defaults"         magit-popup-save-default-arguments)
214     ("    Popup help prefix" magit-popup-help)
215     ("Abort"                 magit-popup-quit)))
216
217 ;;;; Buttons
218
219 (define-button-type 'magit-popup-button
220   'face nil
221   'action (lambda (button)
222             (funcall (button-get button 'function)
223                      (button-get button 'event))))
224
225 (define-button-type 'magit-popup-switch-button
226   'supertype 'magit-popup-button
227   'function  'magit-invoke-popup-switch
228   'property  :switches
229   'heading   "Switches\n"
230   'formatter 'magit-popup-format-argument-button
231   'format    " %k %d (%a)"
232   'prefix    ?-
233   'maxcols   1)
234
235 (define-button-type 'magit-popup-option-button
236   'supertype 'magit-popup-button
237   'function  'magit-invoke-popup-option
238   'property  :options
239   'heading   "Options\n"
240   'formatter 'magit-popup-format-argument-button
241   'format    " %k %d (%a%v)"
242   'prefix    ?=
243   'maxcols   1)
244
245 (define-button-type 'magit-popup-variable-button
246   'supertype 'magit-popup-button
247   'function  'magit-invoke-popup-action
248   'property  :variables
249   'heading   "Variables\n"
250   'formatter 'magit-popup-format-variable-button
251   'format    " %k %d"
252   'prefix    nil
253   'maxcols   1)
254
255 (define-button-type 'magit-popup-action-button
256   'supertype 'magit-popup-button
257   'function  'magit-invoke-popup-action
258   'property  :actions
259   'heading   "Actions\n"
260   'formatter 'magit-popup-format-action-button
261   'format    " %k %d"
262   'prefix    nil
263   'maxcols   :max-action-columns)
264
265 (define-button-type 'magit-popup-command-button
266   'supertype 'magit-popup-action-button
267   'formatter 'magit-popup-format-command-button
268   'action    (lambda (button)
269                (let ((command (button-get button 'function)))
270                  (unless (eq command 'push-button)
271                    (call-interactively command)))))
272
273 (define-button-type 'magit-popup-internal-command-button
274   'supertype 'magit-popup-command-button
275   'heading   "Common Commands\n"
276   'maxcols   3)
277
278 ;;; Events
279
280 (defvar-local magit-this-popup nil
281   "The popup which is currently active.
282 This is intended for internal use only.
283 Don't confuse this with `magit-current-popup'.")
284
285 (defvar-local magit-this-popup-events nil
286   "The events known to the active popup.
287 This is intended for internal use only.
288 Don't confuse this with `magit-current-popup-args'.")
289
290 (defvar-local magit-previous-popup nil)
291
292 (defvar-local magit-pre-popup-buffer nil
293   "The buffer that was current before invoking the active popup.")
294
295 (defun magit-popup-get (prop)
296   "While a popup is active, get the value of PROP."
297   (if (memq prop '(:switches :options :variables :actions))
298       (plist-get magit-this-popup-events prop)
299     (plist-get (symbol-value magit-this-popup) prop)))
300
301 (defun magit-popup-put (prop val)
302   "While a popup is active, set the value of PROP to VAL."
303   (if (memq prop '(:switches :options :variables :actions))
304       (setq magit-this-popup-events
305             (plist-put magit-this-popup-events prop val))
306     (error "Property %s isn't supported" prop)))
307
308 (defvar magit-current-popup nil
309   "The popup from which this editing command was invoked.
310
311 Use this inside the `interactive' form of a popup aware command
312 to determine whether it was invoked from a popup and if so from
313 which popup.  If the current command was invoked without the use
314 of a popup, then this is nil.")
315
316 (defvar magit-current-popup-action nil
317   "The popup action now being executed.")
318
319 (defvar magit-current-popup-args nil
320   "The value of the popup arguments for this editing command.
321
322 If the current command was invoked from a popup, then this is
323 a list of strings of all the set switches and options.  This
324 includes arguments which are set by default not only those
325 explicitly set during this invocation.
326
327 When the value is nil, then that can be because no argument is
328 set, or because the current command wasn't invoked from a popup;
329 consult `magit-current-popup' to tell the difference.
330
331 Generally it is better to use `NAME-arguments', which is created
332 by `magit-define-popup', instead of this variable or the function
333 by the same name, because `NAME-argument' uses the default value
334 for the arguments when the editing command is invoked directly
335 instead of from a popup.  When the command is bound in several
336 popups that might not be feasible though.")
337
338 (defun magit-current-popup-args (&rest filter)
339   "Return the value of the popup arguments for this editing command.
340
341 The value is the same as that of the variable by the same name
342 \(which see), except that FILTER is applied.  FILTER is a list
343 of regexps; only arguments that match one of them are returned.
344 The first element of FILTER may also be `:not' in which case
345 only arguments that don't match any of the regexps are returned,
346 or `:only' which doesn't change the behaviour."
347   (let ((-compare-fn (lambda (a b) (magit-popup-arg-match b a))))
348     (-filter (if (eq (car filter) :not)
349                  (lambda (arg) (not (-contains-p (cdr filter) arg)))
350                (when (eq (car filter) :only)
351                  (pop filter))
352                (lambda (arg) (-contains-p filter arg)))
353              magit-current-popup-args)))
354
355 (defvar magit-current-pre-popup-buffer nil
356   "The buffer that was current before invoking the active popup.
357 This is bound when invoking an action or variable.")
358
359 (defmacro magit-with-pre-popup-buffer (&rest body)
360   "Execute the forms in BODY in the buffer that current before the popup.
361 If `magit-current-pre-popup-buffer' is non-nil use that, else if
362 `magit-pre-popup-buffer' is non-nil use that, otherwise (when no
363 popup is involved) execute the forms in the current buffer."
364   (declare (indent 0))
365   `(--if-let (or magit-current-pre-popup-buffer magit-pre-popup-buffer)
366        (with-current-buffer it ,@body)
367      ,@body))
368
369 (defun magit-popup-arg-match (pattern string)
370   (if (or (string-match-p "=$" pattern)
371           (string-match-p "^-[A-Z]$" pattern))
372       (string-match (format "^%s\\(.*\\)$" pattern) string)
373     (string-equal string pattern)))
374
375 (cl-defstruct magit-popup-event key dsc arg fun use val)
376
377 (defun magit-popup-event-keydsc (ev)
378   (let ((key (magit-popup-event-key ev)))
379     (key-description (if (vectorp key) key (vector key)))))
380
381 (defun magit-popup-lookup (event type)
382   (--first (equal (magit-popup-event-key it) event)
383            (-filter 'magit-popup-event-p (magit-popup-get type))))
384
385 (defun magit-popup-get-args ()
386   (--mapcat (when (and (magit-popup-event-p it)
387                        (magit-popup-event-use it))
388               (list (format "%s%s"
389                             (magit-popup-event-arg it)
390                             (or (magit-popup-event-val it) ""))))
391             (append (magit-popup-get :switches)
392                     (magit-popup-get :options))))
393
394 (defmacro magit-popup-convert-events (def form)
395   (declare (indent 1) (debug (form form)))
396   `(--map (if (or (null it) (stringp it) (functionp it)) it ,form) ,def))
397
398 (defun magit-popup-convert-switches (val def)
399   (magit-popup-convert-events def
400     (let ((a (nth 2 it)))
401       (make-magit-popup-event
402        :key (car it) :dsc (cadr it) :arg a
403        :use (and (member a val) t)
404        ;; For arguments implemented in lisp, this function's
405        ;; doc-string is used by `magit-popup-help'.  That is
406        ;; the only thing it is used for.
407        :fun (and (string-prefix-p "\+\+" a) (nth 3 it))))))
408
409 (defun magit-popup-convert-options (val def)
410   (magit-popup-convert-events def
411     (let* ((a (nth 2 it))
412            (r (format "^%s\\(.*\\)" a))
413            (v (--first (string-match r it) val)))
414       (make-magit-popup-event
415        :key (car it)  :dsc (cadr it) :arg a
416        :use (and v t) :val (and v (match-string 1 v))
417        :fun (or (nth 3 it) 'read-from-minibuffer)))))
418
419 (defun magit-popup-convert-variables (_val def)
420   (magit-popup-convert-events def
421     (make-magit-popup-event
422      :key (car it) :dsc (cadr it) :fun (nth 2 it) :arg (nth 3 it))))
423
424 (defun magit-popup-convert-actions (_val def)
425   (magit-popup-convert-events def
426     (make-magit-popup-event
427      :key (car it) :dsc (cadr it) :fun (nth 2 it))))
428
429 ;;; Define
430
431 (defmacro magit-define-popup (name doc &rest args)
432   "Define a popup command named NAME.
433
434 NAME should begin with the package prefix and by convention end
435 with `-popup'.  That name is used for the actual command as well
436 as for a variable used internally.  DOC is used as the doc-string
437 of that command.
438
439 Also define an option and a function named `SHORTNAME-arguments',
440 where SHORTNAME is NAME with the trailing `-popup' removed.  The
441 name of this option and this function can be overwritten using
442 the optional argument OPTION, but that is rarely advisable. As a
443 special case if OPTION is specified but nil, do not define this
444 option and this function at all.
445
446 The option `SHORTNAME-arguments' holds the default value for the
447 popup arguments.  It can be customized from within the popup or
448 using the Custom interface.
449
450 The function `SHORTNAME-arguments' is a wrapper around the
451 variable `magit-current-popup-args', both of which are intended
452 to be used inside the `interactive' form of commands commonly
453 invoked from the popup `NAME'.  When such a command is invoked
454 from that popup, then the function `SHORTNAME-arguments' returns
455 the value of the variable `magit-current-popup-args'; however
456 when the command is invoked directly, then it returns the default
457 value of the variable `SHORTNAME-arguments'.
458
459 Optional argument GROUP specifies the Custom group into which the
460 option is placed.  If omitted, then the option is placed into some
461 group the same way it is done when directly using `defcustom' and
462 omitting the group, except when NAME begins with \"magit-\", in
463 which case the group `magit-git-arguments' is used.
464
465 Optional argument MODE is deprecated, instead use the keyword
466 arguments `:setup-function' and/or `:refresh-function'.  If MODE
467 is non-nil, then it specifies the mode used by the popup buffer,
468 instead of the default, which is `magit-popup-mode'.
469
470 The remaining arguments should have the form
471
472     [KEYWORD VALUE]...
473
474 The following keywords are meaningful (and by convention are
475 usually specified in that order):
476
477 `:actions'
478   The actions which can be invoked from the popup.  VALUE is a
479   list whose members have the form (KEY DESC COMMAND), see
480   `magit-define-popup-action' for details.
481
482   Actions are regular Emacs commands, which usually have an
483   `interactive' form setup to consume the values of the popup
484   `:switches' and `:options' when invoked from the corresponding
485   popup, else when invoked as the default action or directly
486   without using the popup, the default value of the variable
487   `SHORTNAME-arguments'.  This is usually done by calling the
488   function `SHORTNAME-arguments'.
489
490   Members of VALUE may also be strings and functions, assuming
491   the first member is a string or function.  In that case the
492   members are split into sections and these special elements are
493   used as headings.  If such an element is a function then it is
494   called with no arguments and must return either a string, which
495   is used as the heading, or nil, in which case the section is
496   not inserted.
497
498   Members of VALUE may also be nil.  This should only be used
499   together with `:max-action-columns' and allows having gaps in
500   the action grid, which can help arranging actions sensibly.
501
502 `:default-action'
503   The default action of the popup which is used directly instead
504   of displaying the popup buffer, when the popup is invoked with
505   a prefix argument.  Also see `magit-popup-use-prefix-argument'
506   and `:use-prefix', which can be used to inverse the meaning of
507   the prefix argument.
508
509 `:use-prefix'
510   Controls when to display the popup buffer and when to invoke
511   the default action (if any) directly.  This overrides the
512   global default set using `magit-popup-use-prefix-argument'.
513   The value, if specified, should be one of `default' or `popup',
514   or a function that is called with no arguments and returns one
515   of these symbols.
516
517 `:max-action-columns'
518   The maximum number of actions to display on a single line, a
519   number or a function that returns a number and takes the name
520   of the section currently being inserted as argument.  If there
521   isn't enough room to display as many columns as specified here,
522   then fewer are used.
523
524 `:switches'
525   The popup arguments which can be toggled on and off.  VALUE
526   is a list whose members have the form (KEY DESC SWITCH), see
527   `magit-define-popup-switch' for details.
528
529   Members of VALUE may also be strings and functions, assuming
530   the first member is a string or function.  In that case the
531   members are split into sections and these special elements are
532   used as headings.  If such an element is a function then it is
533   called with no arguments and must return either a string, which
534   is used as the heading, or nil, in which case the section is
535   not inserted.
536
537 `:options'
538   The popup arguments which take a value, as in \"--opt=OPTVAL\".
539   VALUE is a list whose members have the form (KEY DESC OPTION
540   READER), see `magit-define-popup-option' for details.
541
542   Members of VALUE may also be strings and functions, assuming
543   the first member is a string or function.  In that case the
544   members are split into sections and these special elements are
545   used as headings.  If such an element is a function then it is
546   called with no arguments and must return either a string, which
547   is used as the heading, or nil, in which case the section is
548   not inserted.
549
550 `:default-arguments'
551   The default arguments, a list of switches (which are then
552   enabled by default) and options with there default values, as
553   in \"--OPT=OPTVAL\".
554
555 `:variables'
556
557   Variables which can be set from the popup.  VALUE is a list
558   whose members have the form (KEY DESC COMMAND FORMATTER), see
559   `magit-define-popup-variable' for details.
560
561   Members of VALUE may also be strings and functions, assuming
562   the first member is a string or function.  In that case the
563   members are split into sections and these special elements are
564   used as headings.  If such an element is a function then it is
565   called with no arguments and must return either a string, which
566   is used as the heading, or nil, in which case the section is
567   not inserted.
568
569   Members of VALUE may also be actions as described above for
570   `:actions'.
571
572   VALUE may also be a function that returns a list as describe
573   above.
574
575 `:sequence-predicate'
576   When this function returns non-nil, then the popup uses
577   `:sequence-actions' instead of `:actions', and does not show
578   the `:switches' and `:options'.
579
580 `:sequence-actions'
581   The actions which can be invoked from the popup, when
582   `:sequence-predicate' returns non-nil.
583
584 `:setup-function'
585   When this function is specified, then it is used instead of
586   `magit-popup-default-setup'.
587
588 `:refresh-function'
589   When this function is specified, then it is used instead of
590   calling `magit-popup-insert-section' three times with symbols
591   `magit-popup-switch-button', `magit-popup-option-button', and
592   finally `magit-popup-action-button' as argument.
593
594 `:man-page'
595   The name of the manpage to be displayed when the user requests
596   help for a switch or argument.
597
598 \(fn NAME DOC [GROUP [MODE [OPTION]]] :KEYWORD VALUE...)"
599   (declare (indent defun) (doc-string 2))
600   (let* ((str  (symbol-name name))
601          (grp  (if (keywordp (car args))
602                    (and (string-prefix-p "magit-" str) ''magit-git-arguments)
603                  (pop args)))
604          (mode (and (not (keywordp (car args))) (pop args)))
605          (opt  (if (keywordp (car args))
606                    (intern (concat (if (string-suffix-p "-popup" str)
607                                        (substring str 0 -6)
608                                      str)
609                                    "-arguments"))
610                  (eval (pop args)))))
611     `(progn
612        (defun ,name (&optional arg) ,doc
613          (interactive "P")
614          (magit-invoke-popup ',name ,mode arg))
615        (defvar ,name
616          (list :variable ',opt ,@args))
617        (magit-define-popup-keys-deferred ',name)
618        ,@(when opt
619            `((defcustom ,opt (plist-get ,name :default-arguments)
620                ""
621                ,@(and grp (list :group grp))
622                :type '(repeat (string :tag "Argument")))
623              (defun ,opt ()
624                (if (eq magit-current-popup ',name)
625                    magit-current-popup-args
626                  ,opt))
627              (put ',opt 'definition-name ',name))))))
628
629 (defun magit-define-popup-switch (popup key desc switch
630                                         &optional enable at prepend)
631   "In POPUP, define KEY as SWITCH.
632
633 POPUP is a popup command defined using `magit-define-popup'.
634 SWITCH is a string representing an argument that takes no value.
635 KEY is a character representing the second event in the sequence
636 of keystrokes used to toggle the argument.  (The first event, the
637 prefix, is shared among all switches, defaults to -, and can be
638 changed in `magit-popup-mode-keymap').
639
640 DESC is a string describing the purpose of the argument, it is
641 displayed in the popup.
642
643 If optional ENABLE is non-nil, then the switch is on by default.
644
645 SWITCH is inserted after all other switches already defined for
646 POPUP, unless optional PREPEND is non-nil, in which case it is
647 placed first.  If optional AT is non-nil, then it should be the
648 KEY of another switch already defined for POPUP, the argument
649 is then placed before or after AT, depending on PREPEND."
650   (declare (indent defun))
651   (magit-define-popup-key popup :switches key
652     (list desc switch enable) at prepend))
653
654 (defun magit-define-popup-option (popup key desc option
655                                         &optional reader value at prepend)
656   "In POPUP, define KEY as OPTION.
657
658 POPUP is a popup command defined using `magit-define-popup'.
659 OPTION is a string representing an argument that takes a value.
660 KEY is a character representing the second event in the sequence
661 of keystrokes used to set the argument's value.  (The first
662 event, the prefix, is shared among all options, defaults to =,
663 and can be changed in `magit-popup-mode-keymap').
664
665 DESC is a string describing the purpose of the argument, it is
666 displayed in the popup.
667
668 If optional VALUE is non-nil then the option is on by default,
669 and VALUE is its default value.
670
671 READER is used to read a value from the user when the option is
672 invoked and does not currently have a value.  (When the option
673 has a value, then invoking the option causes it to be unset.)
674 This function must take two arguments but may choose to ignore
675 them.  The first argument is the name of the option (with \": \"
676 appended, unless it ends with \"=\") and can be used as the
677 prompt.  The second argument is nil or the value that was in
678 effect before the option was unset, which may be suitable as
679 initial completion input.  If no reader is specified, then
680 `read-from-minibuffer' is used.
681
682 OPTION is inserted after all other options already defined for
683 POPUP, unless optional PREPEND is non-nil, in which case it is
684 placed first.  If optional AT is non-nil, then it should be the
685 KEY of another option already defined for POPUP, the argument
686 is then placed before or after AT, depending on PREPEND."
687   (declare (indent defun))
688   (magit-define-popup-key popup :options key
689     (list desc option reader value) at prepend))
690
691 (defun magit-define-popup-variable (popup key desc command formatter
692                                           &optional at prepend)
693   "In POPUP, define KEY as COMMAND.
694
695 POPUP is a popup command defined using `magit-define-popup'.
696 COMMAND is a command which calls `magit-popup-set-variable'.
697 FORMATTER is a function which calls `magit-popup-format-variable'.
698 These two functions have to be called with the same arguments.
699
700 KEY is a character representing the event used interactively call
701 the COMMAND.
702
703 DESC is the variable or a representation thereof.  It's not
704 actually used for anything.
705
706 COMMAND is inserted after all other commands already defined for
707 POPUP, unless optional PREPEND is non-nil, in which case it is
708 placed first.  If optional AT is non-nil, then it should be the
709 KEY of another command already defined for POPUP, the command
710 is then placed before or after AT, depending on PREPEND."
711   (declare (indent defun))
712   (magit-define-popup-key popup :variables key
713     (list desc command formatter) at prepend))
714
715 (defun magit-define-popup-action (popup key desc command
716                                         &optional at prepend)
717   "In POPUP, define KEY as COMMAND.
718
719 POPUP is a popup command defined using `magit-define-popup'.
720 COMMAND can be any command but should usually consume the popup
721 arguments in its `interactive' form.
722 KEY is a character representing the event used invoke the action,
723 i.e. to interactively call the COMMAND.
724
725 DESC is a string describing the purpose of the action, it is
726 displayed in the popup.
727
728 COMMAND is inserted after all other commands already defined for
729 POPUP, unless optional PREPEND is non-nil, in which case it is
730 placed first.  If optional AT is non-nil, then it should be the
731 KEY of another command already defined for POPUP, the command
732 is then placed before or after AT, depending on PREPEND."
733   (declare (indent defun))
734   (magit-define-popup-key popup :actions key
735     (list desc command) at prepend))
736
737 (defun magit-define-popup-sequence-action
738     (popup key desc command &optional at prepend)
739   "Like `magit-define-popup-action' but for `:sequence-action'."
740   (declare (indent defun))
741   (magit-define-popup-key popup :sequence-actions key
742     (list desc command) at prepend))
743
744 (defconst magit-popup-type-plural-alist
745   '((:switch . :switches)
746     (:option . :options)
747     (:variable . :variables)
748     (:action . :actions)
749     (:sequence-action . :sequence-actions)))
750
751 (defun magit-popup-pluralize-type (type)
752   (or (cdr (assq type magit-popup-type-plural-alist))
753       type))
754
755 (defun magit-define-popup-key
756     (popup type key def &optional at prepend)
757   "In POPUP, define KEY as an action, switch, or option.
758 It's better to use one of the specialized functions
759   `magit-define-popup-action',
760   `magit-define-popup-sequence-action',
761   `magit-define-popup-switch',
762   `magit-define-popup-option', or
763   `magit-define-popup-variable'."
764   (declare (indent defun))
765   (setq type (magit-popup-pluralize-type type))
766   (if (memq type '(:switches :options :variables :actions :sequence-actions))
767       (if (boundp popup)
768           (let* ((plist (symbol-value popup))
769                  (value (plist-get plist type))
770                  (elt   (assoc key value)))
771             (if elt
772                 (setcdr elt def)
773               (setq elt (cons key def)))
774             (if at
775                 (when (setq at (cl-member at value :key 'car-safe :test 'equal))
776                   (setq value (cl-delete key value :key 'car-safe :test 'equal))
777                   (if prepend
778                       (progn (push (car at) (cdr at))
779                              (setcar at elt))
780                     (push elt (cdr at))))
781               (setq value (cl-delete key value :key 'car-safe :test 'equal)))
782             (unless (assoc key value)
783               (setq value (if prepend
784                               (cons elt value)
785                             (append value (list elt)))))
786             (set popup (plist-put plist type value)))
787         (push (list type key def at prepend)
788               (get popup 'magit-popup-deferred)))
789     (error "Unknown popup event type: %s" type)))
790
791 (defun magit-define-popup-keys-deferred (popup)
792   (dolist (args (get popup 'magit-popup-deferred))
793     (condition-case err
794         (apply #'magit-define-popup-key popup args)
795       ((debug error)
796        (display-warning 'magit (error-message-string err) :error))))
797   (put popup 'magit-popup-deferred nil))
798
799 (defun magit-change-popup-key (popup type from to)
800   "In POPUP, bind TO to what FROM was bound to.
801 TYPE is one of `:action', `:sequence-action', `:switch', or
802 `:option'.  Bind TO and unbind FROM, both are characters."
803   (--if-let (assoc from (plist-get (symbol-value popup)
804                                    (magit-popup-pluralize-type type)))
805       (setcar it to)
806     (message "magit-change-popup-key: FROM key %c is unbound" from)))
807
808 (defun magit-remove-popup-key (popup type key)
809   "In POPUP, remove KEY's binding of TYPE.
810 POPUP is a popup command defined using `magit-define-popup'.
811 TYPE is one of `:action', `:sequence-action', `:switch', or
812 `:option'.  KEY is the character which is to be unbound."
813   (setq type (magit-popup-pluralize-type type))
814   (let* ((plist (symbol-value popup))
815          (alist (plist-get plist type))
816          (value (assoc key alist)))
817     (set popup (plist-put plist type (delete value alist)))))
818
819 ;;; Invoke
820
821 (defvar-local magit-popup-previous-winconf nil)
822
823 (defun magit-invoke-popup (popup mode arg)
824   (let* ((def     (symbol-value popup))
825          (val     (symbol-value (plist-get def :variable)))
826          (default (plist-get def :default-action))
827          (local   (plist-get def :use-prefix))
828          (local   (if (functionp local)
829                       (funcall local)
830                     local))
831          (use-prefix (or local magit-popup-use-prefix-argument)))
832     (cond
833      ((or (and (eq use-prefix 'default) arg)
834           (and (eq use-prefix 'popup) (not arg)))
835       (if default
836           (let ((magit-current-popup (list popup 'default))
837                 (magit-current-popup-args
838                  (let ((magit-this-popup popup)
839                        (magit-this-popup-events nil))
840                    (magit-popup-default-setup val def)
841                    (magit-popup-get-args))))
842             (when (and arg (listp arg))
843               (setq current-prefix-arg (and (not (= (car arg) 4))
844                                             (list (/ (car arg) 4)))))
845             (call-interactively default))
846         (message "%s has no default action; showing popup instead." popup)
847         (magit-popup-mode-setup popup mode)))
848      ((memq use-prefix '(default popup nil))
849       (magit-popup-mode-setup popup mode)
850       (when magit-popup-show-help-echo
851         (message
852          (format
853           "[%s] show common commands, [%s] describe events, [%s] show manual"
854           (propertize "C-t"   'face 'magit-popup-key)
855           (propertize "?"     'face 'magit-popup-key)
856           (propertize "C-h i" 'face 'magit-popup-key)))))
857      (local
858       (error "Invalid :use-prefix popup property value: %s" use-prefix))
859      (t
860       (error "Invalid magit-popup-use-prefix-argument value: %s" use-prefix)))))
861
862 (defun magit-invoke-popup-switch (event)
863   (interactive (list last-command-event))
864   (--if-let (magit-popup-lookup event :switches)
865       (progn
866         (setf (magit-popup-event-use it)
867               (not (magit-popup-event-use it)))
868         (magit-refresh-popup-buffer))
869     (user-error "%c isn't bound to any switch" event)))
870
871 (defun magit-invoke-popup-option (event)
872   (interactive (list last-command-event))
873   (--if-let (magit-popup-lookup event :options)
874       (progn
875         (if (magit-popup-event-use it)
876             (setf (magit-popup-event-use it) nil)
877           (let* ((arg (magit-popup-event-arg it))
878                  (val (funcall
879                        (magit-popup-event-fun it)
880                        (concat arg (unless (string-match-p "=$" arg) ": "))
881                        (magit-popup-event-val it))))
882             (setf (magit-popup-event-use it) t)
883             (setf (magit-popup-event-val it) val)))
884         (magit-refresh-popup-buffer))
885     (user-error "%c isn't bound to any option" event)))
886
887 (defun magit-invoke-popup-action (event)
888   (interactive (list last-command-event))
889   (let ((action   (magit-popup-lookup event :actions))
890         (variable (magit-popup-lookup event :variables)))
891     (when (and variable (not (magit-popup-event-arg variable)))
892       (setq action variable)
893       (setq variable nil))
894     (cond ((or action variable)
895            (let* ((magit-current-popup magit-this-popup)
896                   (magit-current-popup-args (magit-popup-get-args))
897                   (magit-current-pre-popup-buffer magit-pre-popup-buffer)
898                   (command (magit-popup-event-fun (or action variable)))
899                   (magit-current-popup-action command))
900              (when action
901                (magit-popup-quit))
902              (setq this-command command)
903              (call-interactively command)
904              (unless action
905                (magit-refresh-popup-buffer))))
906           ((eq event ?q)
907            (magit-popup-quit)
908            (when magit-previous-popup
909              (magit-popup-mode-setup magit-previous-popup nil)))
910           (t
911            (user-error "%c isn't bound to any action" event)))))
912
913 (defun magit-popup-quit ()
914   "Quit the current popup command without invoking an action."
915   (interactive)
916   (let ((winconf magit-popup-previous-winconf))
917     (if (derived-mode-p 'magit-popup-mode)
918         (kill-buffer)
919       (magit-popup-help-mode -1)
920       (kill-local-variable 'magit-popup-previous-winconf))
921     (when winconf
922       (set-window-configuration winconf))))
923
924 (defun magit-popup-read-number (prompt &optional default)
925   "Like `read-number' but DEFAULT may be a numeric string."
926   (read-number prompt (if (stringp default)
927                           (string-to-number default)
928                         default)))
929
930 ;;; Save
931
932 (defun magit-popup-set-default-arguments (arg)
933   "Set default value for the arguments for the current popup.
934 Then close the popup without invoking an action; unless a prefix
935 argument is used in which case the popup remains open.
936
937 For a popup named `NAME-popup' that usually means setting the
938 value of the custom option `NAME-arguments'."
939   (interactive "P")
940   (-if-let (var (magit-popup-get :variable))
941       (progn (customize-set-variable var (magit-popup-get-args))
942              (unless arg (magit-popup-quit)))
943     (user-error "Nothing to set")))
944
945 (defun magit-popup-save-default-arguments (arg)
946   "Save default value for the arguments for the current popup.
947 Then close the popup without invoking an action; unless a prefix
948 argument is used in which case the popup remains open.
949
950 For a popup named `NAME-popup' that usually means saving the
951 value of the custom option `NAME-arguments'."
952   (interactive "P")
953   (-if-let (var (magit-popup-get :variable))
954       (progn (customize-save-variable var (magit-popup-get-args))
955              (unless arg (magit-popup-quit)))
956     (user-error "Nothing to save")))
957
958 ;;; Help
959
960 (defun magit-popup-toggle-show-common-commands ()
961   "Show or hide an additional section with common commands.
962 The commands listed in this section are common to all popups
963 and are defined in `magit-popup-mode-map' (which see)."
964   (interactive)
965   (setq magit-popup-show-common-commands
966         (not magit-popup-show-common-commands))
967   (magit-refresh-popup-buffer)
968   (fit-window-to-buffer))
969
970 (defun magit-popup-help ()
971   "Show help for the argument or action at point."
972   (interactive)
973   (let* ((man (magit-popup-get :man-page))
974          (key (read-key-sequence
975                (concat "Describe key" (and man " (? for manpage)") ": ")))
976          (int (aref key (1- (length key))))
977          (def (or (lookup-key (current-local-map)  key t)
978                   (lookup-key (current-global-map) key))))
979     (pcase def
980       (`magit-invoke-popup-switch
981        (--if-let (magit-popup-lookup int :switches)
982            (if (and (string-prefix-p "++" (magit-popup-event-arg it))
983                     (magit-popup-event-fun it))
984                (magit-popup-describe-function (magit-popup-event-fun it))
985              (magit-popup-manpage man it))
986          (user-error "%c isn't bound to any switch" int)))
987       (`magit-invoke-popup-option
988        (--if-let (magit-popup-lookup int :options)
989            (if (and (string-prefix-p "++" (magit-popup-event-arg it))
990                     (magit-popup-event-fun it))
991                (magit-popup-describe-function (magit-popup-event-fun it))
992              (magit-popup-manpage man it))
993          (user-error "%c isn't bound to any option" int)))
994       (`magit-popup-help
995        (magit-popup-manpage man nil))
996       ((or `self-insert-command
997            `magit-invoke-popup-action)
998        (setq def (or (magit-popup-lookup int :actions)
999                      (magit-popup-lookup int :variables)))
1000        (if def
1001            (magit-popup-describe-function (magit-popup-event-fun def))
1002          (ding)
1003          (message nil)))
1004       (`nil (ding)
1005             (message nil))
1006       (_    (magit-popup-describe-function def)))))
1007
1008 (defun magit-popup-manpage (topic arg)
1009   (unless topic
1010     (user-error "No man page associated with %s"
1011                 (magit-popup-get :man-page)))
1012   (when arg
1013     (setq arg (magit-popup-event-arg arg))
1014     (when (string-prefix-p "--" arg)
1015       ;; handle '--' option and the '--[no-]' shorthand
1016       (setq arg (cond ((string= "-- " arg)
1017                        "\\(?:\\[--\\] \\)?<[^[:space:]]+>\\.\\.\\.")
1018                       ((string-prefix-p "--no-" arg)
1019                        (concat "--"
1020                                "\\[?no-\\]?"
1021                                (substring arg 5)))
1022                       (t
1023                        (concat "--"
1024                                "\\(?:\\[no-\\]\\)?"
1025                                (substring arg 2)))))))
1026   (let ((winconf (current-window-configuration)) buffer)
1027     (pcase magit-popup-manpage-package
1028       (`woman (delete-other-windows)
1029               (split-window-below)
1030               (with-no-warnings ; display-buffer-function is obsolete
1031                 (let ((display-buffer-alist nil)
1032                       (display-buffer-function nil)
1033                       (display-buffer-overriding-action nil))
1034                   (woman topic)))
1035               (setq buffer (current-buffer)))
1036       (`man   (cl-letf (((symbol-function #'fboundp) (lambda (_) nil)))
1037                 (setq buffer (man topic)))
1038               (delete-other-windows)
1039               (split-window-below)
1040               (set-window-buffer (selected-window) buffer)))
1041     (with-current-buffer buffer
1042       (setq magit-popup-previous-winconf winconf)
1043       (magit-popup-help-mode)
1044       (fit-window-to-buffer (next-window))
1045       (if (and arg
1046                (Man-find-section "OPTIONS")
1047                (let ((case-fold-search nil)
1048                      ;; This matches preceding/proceeding options.
1049                      ;; Options such as '-a', '-S[<keyid>]', and
1050                      ;; '--grep=<pattern>' are matched by this regex
1051                      ;; without the shy group. The '. ' in the shy
1052                      ;; group is for options such as '-m
1053                      ;; parent-number', and the '-[^[:space:]]+ ' is
1054                      ;; for options such as '--mainline parent-number'
1055                      (others "-\\(?:. \\|-[^[:space:]]+ \\)?[^[:space:]]+"))
1056                  (re-search-forward
1057                   ;; should start with whitespace, and may have any
1058                   ;; number of options before/after
1059                   (format "^[\t\s]+\\(?:%s, \\)*?\\(?1:%s\\)%s\\(?:, %s\\)*$"
1060                           others
1061                           ;; options don't necessarily end in an '='
1062                           ;; (e.g., '--gpg-sign[=<keyid>]')
1063                           (string-remove-suffix "=" arg)
1064                           ;; Simple options don't end in an '='.
1065                           ;; Splitting this into 2 cases should make
1066                           ;; getting false positives less likely.
1067                           (if (string-suffix-p "=" arg)
1068                               ;; [^[:space:]]*[^.[:space:]] matches
1069                               ;; the option value, which is usually
1070                               ;; after the option name and either '='
1071                               ;; or '[='. The value can't end in a
1072                               ;; period, as that means it's being used
1073                               ;; at the end of a sentence. The space
1074                               ;; is for options such as '--mainline
1075                               ;; parent-number'.
1076                               "\\(?: \\|\\[?=\\)[^[:space:]]*[^.[:space:]]"
1077                             ;; Either this doesn't match anything
1078                             ;; (e.g., '-a'), or the option is followed
1079                             ;; by a value delimited by a '[', '<', or
1080                             ;; ':'. A space might appear before this
1081                             ;; value, as in '-f <file>'. The space
1082                             ;; alternative is for options such as '-m
1083                             ;; parent-number'.
1084                             "\\(?:\\(?: \\| ?[\\[<:]\\)[^[:space:]]*[^.[:space:]]\\)?")
1085                           others)
1086                   nil
1087                   t)))
1088           (goto-char (match-beginning 1))
1089         (goto-char (point-min))))))
1090
1091 (defun magit-popup-describe-function (function)
1092   (let ((winconf (current-window-configuration)))
1093     (delete-other-windows)
1094     (split-window-below)
1095     (other-window 1)
1096     (with-no-warnings ; display-buffer-function is obsolete
1097       (let ((display-buffer-alist '(("" display-buffer-use-some-window)))
1098             (display-buffer-function nil)
1099             (display-buffer-overriding-action nil)
1100             (help-window-select nil))
1101         (describe-function function)))
1102     (fit-window-to-buffer)
1103     (other-window 1)
1104     (setq magit-popup-previous-winconf winconf)
1105     (magit-popup-help-mode)))
1106
1107 (defun magit-popup-info ()
1108   "Show the popup manual."
1109   (interactive)
1110   (let ((winconf (current-window-configuration)))
1111     (delete-other-windows)
1112     (split-window-below)
1113     (info "(magit-popup.info)Usage")
1114     (magit-popup-help-mode)
1115     (setq magit-popup-previous-winconf winconf))
1116   (magit-popup-help-mode)
1117   (fit-window-to-buffer (next-window)))
1118
1119 (define-minor-mode magit-popup-help-mode
1120   "Auxiliary minor mode used to restore previous window configuration.
1121 When some sort of help buffer is created from within a popup,
1122 then this minor mode is turned on in that buffer, so that when
1123 the user quits it, the previous window configuration is also
1124 restored."
1125   :keymap '(([remap Man-quit]    . magit-popup-quit)
1126             ([remap Info-exit]   . magit-popup-quit)
1127             ([remap quit-window] . magit-popup-quit)))
1128
1129 ;;; Modes
1130
1131 (define-derived-mode magit-popup-mode fundamental-mode "MagitPopup"
1132   "Major mode for infix argument popups."
1133   :mode 'magit-popup
1134   (setq truncate-lines t)
1135   (setq buffer-read-only t)
1136   (setq-local scroll-margin 0)
1137   (setq-local magit-popup-show-common-commands magit-popup-show-common-commands)
1138   (hack-dir-local-variables-non-file-buffer))
1139
1140 (put 'magit-popup-mode 'mode-class 'special)
1141
1142 (defun magit-popup-default-setup (val def)
1143   (if (--when-let (magit-popup-get :sequence-predicate)
1144         (funcall it))
1145       (magit-popup-put :actions (magit-popup-convert-actions
1146                                  val (magit-popup-get :sequence-actions)))
1147     (let ((vars (plist-get def :variables)))
1148       (when (functionp vars)
1149         (setq vars (funcall vars)))
1150       (when vars
1151         (magit-popup-put :variables (magit-popup-convert-variables val vars))))
1152     (magit-popup-put :switches (magit-popup-convert-switches
1153                                 val (plist-get def :switches)))
1154     (magit-popup-put :options  (magit-popup-convert-options
1155                                 val (plist-get def :options)))
1156     (magit-popup-put :actions  (magit-popup-convert-actions
1157                                 val (plist-get def :actions)))))
1158
1159 (defun magit-popup-mode-setup (popup mode)
1160   (setq magit-previous-popup magit-current-popup)
1161   (let ((val (symbol-value (plist-get (symbol-value popup) :variable)))
1162         (def (symbol-value popup))
1163         (buf (current-buffer)))
1164     (magit-popup-mode-display-buffer (get-buffer-create
1165                                       (format "*%s*" popup))
1166                                      (or mode 'magit-popup-mode))
1167     (setq magit-this-popup popup)
1168     (setq magit-pre-popup-buffer buf)
1169     (if (bound-and-true-p magit-popup-setup-hook) ; obsolete
1170         (run-hook-with-args 'magit-popup-setup-hook val def)
1171       (funcall (or (magit-popup-get :setup-function)
1172                    'magit-popup-default-setup)
1173                val def)))
1174   (magit-refresh-popup-buffer)
1175   (fit-window-to-buffer nil nil (line-number-at-pos (point-max))))
1176
1177 (defun magit-popup-mode-display-buffer (buffer mode)
1178   (let ((winconf (current-window-configuration)))
1179     (select-window (display-buffer buffer magit-popup-display-buffer-action))
1180     (funcall mode)
1181     (setq magit-popup-previous-winconf winconf)))
1182
1183 (defvar magit-refresh-popup-buffer-hook nil
1184   "Hook run by `magit-refresh-popup-buffer'.
1185
1186 The hook is run right after inserting the representation of the
1187 popup events but before optionally inserting the representation
1188 of events shared by all popups and before point is adjusted.")
1189
1190 (defun magit-refresh-popup-buffer ()
1191   (let* ((inhibit-read-only t)
1192          (button (button-at (point)))
1193          (prefix (and button (button-get button 'prefix)))
1194          (event  (and button (button-get button 'event))))
1195     (erase-buffer)
1196     (save-excursion
1197       (--if-let (magit-popup-get :refresh-function)
1198           (funcall it)
1199         (magit-popup-insert-section 'magit-popup-variable-button)
1200         (magit-popup-insert-section 'magit-popup-switch-button)
1201         (magit-popup-insert-section 'magit-popup-option-button)
1202         (magit-popup-insert-section 'magit-popup-action-button))
1203       (run-hooks 'magit-refresh-popup-buffer-hook)
1204       (when magit-popup-show-common-commands
1205         (magit-popup-insert-command-section
1206          'magit-popup-internal-command-button
1207          magit-popup-common-commands)))
1208     (set-buffer-modified-p nil)
1209     (when event
1210       (while (and (ignore-errors (forward-button 1))
1211                   (let ((b (button-at (point))))
1212                     (or (not (equal (button-get b 'prefix) prefix))
1213                         (not (equal (button-get b 'event)  event)))))))))
1214
1215 ;;; Draw
1216
1217 (defvar magit-popup-min-padding 3
1218   "Minimal amount of whitespace between columns in popup buffers.")
1219
1220 (defun magit-popup-insert-section (type &optional spec heading)
1221   (if (not spec)
1222       (progn (setq spec (magit-popup-get (button-type-get type 'property)))
1223              (when spec
1224                (if (or (stringp (car spec))
1225                        (functionp (car spec)))
1226                    (--each (--partition-by-header
1227                             (or (stringp it) (functionp it))
1228                             spec)
1229                      (magit-popup-insert-section type (cdr it) (car it)))
1230                  (magit-popup-insert-section type spec))))
1231     (let* ((formatter (button-type-get type 'formatter))
1232            (items (mapcar (lambda (ev)
1233                             (and ev (or (funcall formatter type ev) '(""))))
1234                           (or spec (magit-popup-get
1235                                     (button-type-get type 'property)))))
1236            (maxcols (button-type-get type 'maxcols))
1237            (pred (magit-popup-get :sequence-predicate)))
1238       (when items
1239         (if (functionp heading)
1240             (when (setq heading (funcall heading))
1241               (insert heading ?\n))
1242           (unless heading
1243             (setq heading (button-type-get type 'heading)))
1244           (insert (propertize heading 'face 'magit-popup-heading))
1245           (unless (string-match "\n$" heading)
1246             (insert "\n")))
1247         (if (and pred (funcall pred))
1248             (setq maxcols nil)
1249           (cl-typecase maxcols
1250             (keyword (setq maxcols (magit-popup-get maxcols)))
1251             (symbol  (setq maxcols (symbol-value maxcols)))))
1252         (when (functionp maxcols)
1253           (setq maxcols (funcall maxcols heading)))
1254         (when heading
1255           (let ((colwidth
1256                  (+ (apply 'max (mapcar (lambda (e) (length (car e))) items))
1257                     magit-popup-min-padding)))
1258             (dolist (item items)
1259               (unless (bolp)
1260                 (let ((padding (- colwidth (% (current-column) colwidth))))
1261                   (if (and (< (+ (current-column) padding colwidth)
1262                               (window-width))
1263                            (< (ceiling (/ (current-column) (* colwidth 1.0)))
1264                               (or maxcols 1000)))
1265                       (insert (make-string padding ?\s))
1266                     (insert "\n"))))
1267               (unless (equal item '(""))
1268                 (if item
1269                     (apply 'insert-button item)
1270                   (insert ?\s)))))
1271           (insert (if (= (char-before) ?\n) "\n" "\n\n")))))))
1272
1273 (defun magit-popup-format-argument-button (type ev)
1274   (list (format-spec
1275          (button-type-get type 'format)
1276          `((?k . ,(propertize (concat
1277                                (--when-let (button-type-get type 'prefix)
1278                                  (char-to-string it))
1279                                (magit-popup-event-keydsc ev))
1280                               'face 'magit-popup-key))
1281            (?d . ,(magit-popup-event-dsc ev))
1282            (?a . ,(propertize (magit-popup-event-arg ev)
1283                               'face (if (magit-popup-event-use ev)
1284                                         'magit-popup-argument
1285                                       'magit-popup-disabled-argument)))
1286            (?v . ,(let ((val (magit-popup-event-val ev)))
1287                     (if (and (magit-popup-event-use ev)
1288                              (not (equal val "")))
1289                         (propertize (format "\"%s\"" val)
1290                                     'face 'magit-popup-option-value)
1291                       "")))))
1292         'type type 'event (magit-popup-event-key ev)))
1293
1294 (defun magit-popup-format-variable-button (type ev)
1295   (if (not (magit-popup-event-arg ev))
1296       (magit-popup-format-action-button 'magit-popup-action-button ev)
1297     (list (format-spec
1298            (button-type-get type 'format)
1299            `((?k . ,(propertize (magit-popup-event-keydsc ev)
1300                                 'face 'magit-popup-key))
1301              (?d . ,(funcall (magit-popup-event-arg ev)))))
1302           'type type 'event (magit-popup-event-key ev))))
1303
1304 (defun magit-popup-format-action-button (type ev)
1305   (let* ((cmd (magit-popup-event-fun ev))
1306          (dsc (magit-popup-event-dsc ev))
1307          (fun (and (functionp dsc) dsc)))
1308     (unless (and disabled-command-function
1309                  (symbolp cmd)
1310                  (get cmd 'disabled))
1311       (when fun
1312         (setq dsc
1313               (-when-let (branch (funcall fun))
1314                 (if (text-property-not-all 0 (length branch) 'face nil branch)
1315                     branch
1316                   (magit-branch-set-face branch)))))
1317       (when dsc
1318         (list (format-spec
1319                (button-type-get type 'format)
1320                `((?k . ,(propertize (magit-popup-event-keydsc ev)
1321                                     'face 'magit-popup-key))
1322                  (?d . ,dsc)
1323                  (?D . ,(if (and (not fun)
1324                                  (eq cmd (magit-popup-get :default-action)))
1325                             (propertize dsc 'face 'bold)
1326                           dsc))))
1327               'type type 'event (magit-popup-event-key ev))))))
1328
1329 (defun magit-popup-insert-command-section (type spec)
1330   (magit-popup-insert-section
1331    type (mapcar (lambda (elt)
1332                   (list (car (where-is-internal (cadr elt)
1333                                                 (current-local-map)))
1334                         (car elt)))
1335                 spec)))
1336
1337 (defun magit-popup-format-command-button (type elt)
1338   (nconc (magit-popup-format-action-button
1339           type (make-magit-popup-event :key (car  elt)
1340                                        :dsc (cadr elt)))
1341          (list 'function (lookup-key (current-local-map) (car elt)))))
1342
1343 ;;; Utilities
1344
1345 (defun magit-popup-import-file-args (args files)
1346   (if files
1347       (cons (concat "-- " (mapconcat #'identity files ",")) args)
1348     args))
1349
1350 (defun magit-popup-export-file-args (args)
1351   (let ((files (--first (string-prefix-p "-- " it) args)))
1352     (when files
1353       (setq args  (remove files args))
1354       (setq files (split-string (substring files 3) ",")))
1355     (list args files)))
1356
1357 (defconst magit-popup-font-lock-keywords
1358   (eval-when-compile
1359     `((,(concat "(\\(magit-define-popup\\)\\_>"
1360                 "[ \t'\(]*"
1361                 "\\(\\(?:\\sw\\|\\s_\\)+\\)?")
1362        (1 'font-lock-keyword-face)
1363        (2 'font-lock-function-name-face nil t)))))
1364
1365 (font-lock-add-keywords 'emacs-lisp-mode magit-popup-font-lock-keywords)
1366
1367 ;;; _
1368 (provide 'magit-popup)
1369 ;; Local Variables:
1370 ;; indent-tabs-mode: nil
1371 ;; End:
1372 ;;; magit-popup.el ends here