summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Kost <alezost@gmail.com>2015-06-06 22:19:51 +0300
committerAlex Kost <alezost@gmail.com>2015-06-08 11:46:05 +0300
commit00a8dae2a6e74a0d7dd8a0b82c3dc08d3daf2a31 (patch)
tree52f2970febcc684a157c723a1ff1024d5ba4499b
parent132e74fec99eaf0febb62269ca38bacebeaa0882 (diff)
emacs: Add shell completions for "guix" command.
Suggested by Ludovic Courtès <ludo@gnu.org>. * emacs/guix-pcomplete.el: New file. * emacs.am (ELFILES): Add it. * doc/emacs.texi (Emacs Completions): New node.
-rw-r--r--doc/emacs.texi32
-rw-r--r--emacs.am1
-rw-r--r--emacs/guix-pcomplete.el392
3 files changed, 425 insertions, 0 deletions
diff --git a/doc/emacs.texi b/doc/emacs.texi
index 6c1b255af08..00cd5c1396f 100644
--- a/doc/emacs.texi
+++ b/doc/emacs.texi
@@ -19,6 +19,7 @@ guix package}). Specifically, ``guix.el'' makes it easy to:
19* Usage: Emacs Usage. Using the interface. 19* Usage: Emacs Usage. Using the interface.
20* Configuration: Emacs Configuration. Configuring the interface. 20* Configuration: Emacs Configuration. Configuring the interface.
21* Prettify Mode: Emacs Prettify. Abbreviating @file{/gnu/store/@dots{}} file names. 21* Prettify Mode: Emacs Prettify. Abbreviating @file{/gnu/store/@dots{}} file names.
22* Completions: Emacs Completions. Completing @command{guix} shell command.
22@end menu 23@end menu
23 24
24@node Emacs Initial Setup 25@node Emacs Initial Setup
@@ -489,3 +490,34 @@ mode hooks (@pxref{Hooks,,, emacs, The GNU Emacs Manual}), for example:
489(add-hook 'shell-mode-hook 'guix-prettify-mode) 490(add-hook 'shell-mode-hook 'guix-prettify-mode)
490(add-hook 'dired-mode-hook 'guix-prettify-mode) 491(add-hook 'dired-mode-hook 'guix-prettify-mode)
491@end example 492@end example
493
494
495@node Emacs Completions
496@subsection Shell Completions
497
498Another feature that becomes available after configuring Emacs interface
499(@pxref{Emacs Initial Setup}) is completing of @command{guix}
500subcommands, options, packages and other things in @code{shell}
501(@pxref{Interactive Shell,,, emacs, The GNU Emacs Manual}) and
502@code{eshell} (@pxref{Top,,, eshell, Eshell: The Emacs Shell}).
503
504It works the same way as other completions do. Just press @key{TAB}
505when your intuition tells you.
506
507And here are some examples, where pressing @key{TAB} may complete
508something:
509
510@itemize @w{}
511
512@item @code{guix pa}@key{TAB}
513@item @code{guix package -}@key{TAB}
514@item @code{guix package --}@key{TAB}
515@item @code{guix package -i gei}@key{TAB}
516@item @code{guix build -L/tm}@key{TAB}
517@item @code{guix build --sy}@key{TAB}
518@item @code{guix build --system=i}@key{TAB}
519@item @code{guix system rec}@key{TAB}
520@item @code{guix lint --checkers=sy}@key{TAB}
521@item @code{guix lint --checkers=synopsis,des}@key{TAB}
522
523@end itemize
diff --git a/emacs.am b/emacs.am
index a43168e2cff..372b33e8226 100644
--- a/emacs.am
+++ b/emacs.am
@@ -26,6 +26,7 @@ ELFILES = \
26 emacs/guix-info.el \ 26 emacs/guix-info.el \
27 emacs/guix-list.el \ 27 emacs/guix-list.el \
28 emacs/guix-messages.el \ 28 emacs/guix-messages.el \
29 emacs/guix-pcomplete.el \
29 emacs/guix-prettify.el \ 30 emacs/guix-prettify.el \
30 emacs/guix-utils.el \ 31 emacs/guix-utils.el \
31 emacs/guix.el 32 emacs/guix.el
diff --git a/emacs/guix-pcomplete.el b/emacs/guix-pcomplete.el
new file mode 100644
index 00000000000..fa71dd5e211
--- /dev/null
+++ b/emacs/guix-pcomplete.el
@@ -0,0 +1,392 @@
1;;; guix-pcomplete.el --- Functions for completing guix commands -*- lexical-binding: t -*-
2
3;; Copyright © 2015 Alex Kost <alezost@gmail.com>
4
5;; This file is part of GNU Guix.
6
7;; GNU Guix is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Guix is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; This file provides completions for "guix" command that may be used in
23;; `shell', `eshell' and wherever `pcomplete' works.
24
25;;; Code:
26
27(require 'pcomplete)
28(require 'pcmpl-unix)
29(require 'cl-lib)
30(require 'guix-utils)
31
32
33;;; Regexps for parsing various "guix ..." outputs
34
35(defvar guix-pcomplete-parse-package-regexp
36 (rx bol (group (one-or-more (not blank))))
37 "Regexp used to find names of the packages.")
38
39(defvar guix-pcomplete-parse-command-regexp
40 (rx bol " "
41 (group wordchar (one-or-more (or wordchar "-"))))
42 "Regexp used to find guix commands.
43'Command' means any option not prefixed with '-'. For example,
44guix subcommand, system action, importer, etc.")
45
46(defvar guix-pcomplete-parse-long-option-regexp
47 (rx (or " " ", ")
48 (group "--" (one-or-more (or wordchar "-"))
49 (zero-or-one "=")))
50 "Regexp used to find long options.")
51
52(defvar guix-pcomplete-parse-short-option-regexp
53 (rx bol (one-or-more blank)
54 "-" (group (not (any "- "))))
55 "Regexp used to find short options.")
56
57(defvar guix-pcomplete-parse-linter-regexp
58 (rx bol "- " (group (one-or-more (or wordchar "-"))))
59 "Regexp used to find 'lint' checkers.")
60
61(defvar guix-pcomplete-parse-regexp-group 1
62 "Parenthesized expression of regexps used to find commands and
63options.")
64
65
66;;; Non-receivable completions
67
68(defvar guix-pcomplete-systems
69 '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux")
70 "List of supported systems.")
71
72(defvar guix-pcomplete-hash-formats
73 '("nix-base32" "base32" "base16" "hex" "hexadecimal")
74 "List of supported hash formats.")
75
76(defvar guix-pcomplete-refresh-subsets
77 '("core" "non-core")
78 "List of supported 'refresh' subsets.")
79
80(defvar guix-pcomplete-key-policies
81 '("interactive" "always" "never")
82 "List of supported key download policies.")
83
84
85;;; Interacting with guix
86
87(defcustom guix-pcomplete-guix-program (executable-find "guix")
88 "Name of the 'guix' program.
89It is used to find guix commands, options, packages, etc."
90 :type 'file
91 :group 'pcomplete
92 :group 'guix)
93
94(defun guix-pcomplete-run-guix (&rest args)
95 "Run `guix-pcomplete-guix-program' with ARGS.
96Insert the output to the current buffer."
97 (apply #'call-process
98 guix-pcomplete-guix-program nil t nil args))
99
100(defun guix-pcomplete-run-guix-and-search (regexp &optional group
101 &rest args)
102 "Run `guix-pcomplete-guix-program' with ARGS and search for matches.
103Return a list of strings matching REGEXP.
104GROUP specifies a parenthesized expression used in REGEXP."
105 (with-temp-buffer
106 (apply #'guix-pcomplete-run-guix args)
107 (goto-char (point-min))
108 (let (result)
109 (while (re-search-forward regexp nil t)
110 (push (match-string-no-properties group) result))
111 (nreverse result))))
112
113(defmacro guix-pcomplete-define-options-finder (name docstring regexp
114 &optional filter)
115 "Define function NAME to receive guix options and commands.
116
117The defined function takes an optional COMMAND argument. This
118function will run 'guix COMMAND --help' (or 'guix --help' if
119COMMAND is nil) using `guix-pcomplete-run-guix-and-search' and
120return its result.
121
122If FILTER is specified, it should be a function. The result is
123passed to this FILTER as argument and the result value of this
124function call is returned."
125 (declare (doc-string 2) (indent 1))
126 `(guix-memoized-defun ,name (&optional command)
127 ,docstring
128 (let* ((args '("--help"))
129 (args (if command (cons command args) args))
130 (res (apply #'guix-pcomplete-run-guix-and-search
131 ,regexp guix-pcomplete-parse-regexp-group args)))
132 ,(if filter
133 `(funcall ,filter res)
134 'res))))
135
136(guix-pcomplete-define-options-finder guix-pcomplete-commands
137 "If COMMAND is nil, return a list of available guix commands.
138If COMMAND is non-nil (it should be a string), return available
139subcommands, actions, etc. for this guix COMMAND."
140 guix-pcomplete-parse-command-regexp)
141
142(guix-pcomplete-define-options-finder guix-pcomplete-long-options
143 "Return a list of available long options for guix COMMAND."
144 guix-pcomplete-parse-long-option-regexp)
145
146(guix-pcomplete-define-options-finder guix-pcomplete-short-options
147 "Return a string with available short options for guix COMMAND."
148 guix-pcomplete-parse-short-option-regexp
149 (lambda (list)
150 (mapconcat #'identity list "")))
151
152(guix-memoized-defun guix-pcomplete-all-packages ()
153 "Return a list of all available Guix packages."
154 (guix-pcomplete-run-guix-and-search
155 guix-pcomplete-parse-package-regexp
156 guix-pcomplete-parse-regexp-group
157 "package" "--list-available"))
158
159(guix-memoized-defun guix-pcomplete-installed-packages (&optional profile)
160 "Return a list of Guix packages installed in PROFILE."
161 (let* ((args (and profile
162 (list (concat "--profile=" profile))))
163 (args (append '("package" "--list-installed") args)))
164 (apply #'guix-pcomplete-run-guix-and-search
165 guix-pcomplete-parse-package-regexp
166 guix-pcomplete-parse-regexp-group
167 args)))
168
169(guix-memoized-defun guix-pcomplete-lint-checkers ()
170 "Return a list of all available lint checkers."
171 (guix-pcomplete-run-guix-and-search
172 guix-pcomplete-parse-linter-regexp
173 guix-pcomplete-parse-regexp-group
174 "lint" "--list-checkers"))
175
176
177;;; Completing
178
179(defvar guix-pcomplete-option-regexp (rx string-start "-")
180 "Regexp to match an option.")
181
182(defvar guix-pcomplete-long-option-regexp (rx string-start "--")
183 "Regexp to match a long option.")
184
185(defvar guix-pcomplete-long-option-with-arg-regexp
186 (rx string-start
187 (group "--" (one-or-more any)) "="
188 (group (zero-or-more any)))
189 "Regexp to match a long option with its argument.
190The first parenthesized group defines the option and the second
191group - the argument.")
192
193(defvar guix-pcomplete-short-option-with-arg-regexp
194 (rx string-start
195 (group "-" (not (any "-")))
196 (group (zero-or-more any)))
197 "Regexp to match a short option with its argument.
198The first parenthesized group defines the option and the second
199group - the argument.")
200
201(defun guix-pcomplete-match-option ()
202 "Return non-nil, if the current argument is an option."
203 (pcomplete-match guix-pcomplete-option-regexp 0))
204
205(defun guix-pcomplete-match-long-option ()
206 "Return non-nil, if the current argument is a long option."
207 (pcomplete-match guix-pcomplete-long-option-regexp 0))
208
209(defun guix-pcomplete-match-long-option-with-arg ()
210 "Return non-nil, if the current argument is a long option with value."
211 (pcomplete-match guix-pcomplete-long-option-with-arg-regexp 0))
212
213(defun guix-pcomplete-match-short-option-with-arg ()
214 "Return non-nil, if the current argument is a short option with value."
215 (pcomplete-match guix-pcomplete-short-option-with-arg-regexp 0))
216
217(defun guix-pcomplete-long-option-arg (option args)
218 "Return a long OPTION's argument from a list of arguments ARGS."
219 (let* ((re (concat "\\`" option "=\\(.*\\)"))
220 (args (cl-member-if (lambda (arg)
221 (string-match re arg))
222 args))
223 (cur (car args)))
224 (when cur
225 (match-string-no-properties 1 cur))))
226
227(defun guix-pcomplete-short-option-arg (option args)
228 "Return a short OPTION's argument from a list of arguments ARGS."
229 (let* ((re (concat "\\`" option "\\(.*\\)"))
230 (args (cl-member-if (lambda (arg)
231 (string-match re arg))
232 args))
233 (cur (car args)))
234 (when cur
235 (let ((arg (match-string-no-properties 1 cur)))
236 (if (string= "" arg)
237 (cadr args) ; take the next arg
238 arg)))))
239
240(defun guix-pcomplete-complete-comma-args (entries)
241 "Complete comma separated arguments using ENTRIES."
242 (let ((index pcomplete-index))
243 (while (= index pcomplete-index)
244 (let* ((args (if (or (guix-pcomplete-match-long-option-with-arg)
245 (guix-pcomplete-match-short-option-with-arg))
246 (pcomplete-match-string 2 0)
247 (pcomplete-arg 0)))
248 (input (if (string-match ".*,\\(.*\\)" args)
249 (match-string-no-properties 1 args)
250 args)))
251 (pcomplete-here* entries input)))))
252
253(defun guix-pcomplete-complete-command-arg (command)
254 "Complete argument for guix COMMAND."
255 (cond
256 ((member command
257 '("archive" "build" "environment" "lint" "refresh"))
258 (while t
259 (pcomplete-here (guix-pcomplete-all-packages))))
260 (t (pcomplete-here* (pcomplete-entries)))))
261
262(defun guix-pcomplete-complete-option-arg (command option &optional input)
263 "Complete argument for COMMAND's OPTION.
264INPUT is the current partially completed string."
265 (cl-flet ((option? (short long)
266 (or (string= option short)
267 (string= option long)))
268 (command? (&rest commands)
269 (member command commands))
270 (complete (entries)
271 (pcomplete-here entries input nil t))
272 (complete* (entries)
273 (pcomplete-here* entries input t)))
274 (cond
275 ((option? "-L" "--load-path")
276 (complete* (pcomplete-dirs)))
277 ((string= "--key-download" option)
278 (complete* guix-pcomplete-key-policies))
279
280 ((command? "package")
281 (cond
282 ;; For '--install[=]' and '--remove[=]', try to complete a package
283 ;; name (INPUT) after the "=" sign, and then the rest packages
284 ;; separated with spaces.
285 ((option? "-i" "--install")
286 (complete (guix-pcomplete-all-packages))
287 (while (not (guix-pcomplete-match-option))
288 (pcomplete-here (guix-pcomplete-all-packages))))
289 ((option? "-r" "--remove")
290 (let* ((profile (or (guix-pcomplete-short-option-arg
291 "-p" pcomplete-args)
292 (guix-pcomplete-long-option-arg
293 "--profile" pcomplete-args)))
294 (profile (and profile (expand-file-name profile))))
295 (complete (guix-pcomplete-installed-packages profile))
296 (while (not (guix-pcomplete-match-option))
297 (pcomplete-here (guix-pcomplete-installed-packages profile)))))
298 ((string= "--show" option)
299 (complete (guix-pcomplete-all-packages)))
300 ((option? "-p" "--profile")
301 (complete* (pcomplete-dirs)))
302 ((option? "-m" "--manifest")
303 (complete* (pcomplete-entries)))))
304
305 ((and (command? "archive" "build")
306 (option? "-s" "--system"))
307 (complete* guix-pcomplete-systems))
308
309 ((and (command? "build")
310 (option? "-r" "--root"))
311 (complete* (pcomplete-entries)))
312
313 ((and (command? "environment")
314 (option? "-l" "--load"))
315 (complete* (pcomplete-entries)))
316
317 ((and (command? "hash" "download")
318 (option? "-f" "--format"))
319 (complete* guix-pcomplete-hash-formats))
320
321 ((and (command? "lint")
322 (option? "-c" "--checkers"))
323 (guix-pcomplete-complete-comma-args
324 (guix-pcomplete-lint-checkers)))
325
326 ((and (command? "publish")
327 (option? "-u" "--user"))
328 (complete* (pcmpl-unix-user-names)))
329
330 ((and (command? "refresh")
331 (option? "-s" "--select"))
332 (complete* guix-pcomplete-refresh-subsets)))))
333
334(defun guix-pcomplete-complete-options (command)
335 "Complete options (with their arguments) for guix COMMAND."
336 (while (guix-pcomplete-match-option)
337 (let ((index pcomplete-index))
338 (if (guix-pcomplete-match-long-option)
339
340 ;; Long options.
341 (if (guix-pcomplete-match-long-option-with-arg)
342 (let ((option (pcomplete-match-string 1 0))
343 (arg (pcomplete-match-string 2 0)))
344 (guix-pcomplete-complete-option-arg
345 command option arg))
346
347 (pcomplete-here* (guix-pcomplete-long-options command))
348 ;; We support '--opt arg' style (along with '--opt=arg'),
349 ;; because 'guix package --install/--remove' may be used this
350 ;; way. So try to complete an argument after the option has
351 ;; been completed.
352 (unless (guix-pcomplete-match-option)
353 (guix-pcomplete-complete-option-arg
354 command (pcomplete-arg 0 -1))))
355
356 ;; Short options.
357 (let ((arg (pcomplete-arg 0)))
358 (if (> (length arg) 2)
359 ;; Support specifying an argument after a short option without
360 ;; spaces (for example, '-L/tmp/foo').
361 (guix-pcomplete-complete-option-arg
362 command
363 (substring-no-properties arg 0 2)
364 (substring-no-properties arg 2))
365 (pcomplete-opt (guix-pcomplete-short-options command))
366 (guix-pcomplete-complete-option-arg
367 command (pcomplete-arg 0 -1)))))
368
369 ;; If there were no completions, move to the next argument and get
370 ;; out if the last argument is achieved.
371 (when (= index pcomplete-index)
372 (if (= pcomplete-index pcomplete-last)
373 (throw 'pcompleted nil)
374 (pcomplete-next-arg))))))
375
376;;;###autoload
377(defun pcomplete/guix ()
378 "Completion for `guix'."
379 (let ((commands (guix-pcomplete-commands)))
380 (pcomplete-here* (cons "--help" commands))
381 (let ((command (pcomplete-arg 'first 1)))
382 (when (member command commands)
383 (guix-pcomplete-complete-options command)
384 (let ((subcommands (guix-pcomplete-commands command)))
385 (when subcommands
386 (pcomplete-here* subcommands)))
387 (guix-pcomplete-complete-options command)
388 (guix-pcomplete-complete-command-arg command)))))
389
390(provide 'guix-pcomplete)
391
392;;; guix-pcomplete.el ends here