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 |