From mboxrd@z Thu Jan 1 00:00:00 1970 From: Alex Kost Subject: [PATCH 2/2] emacs: Add shell completions for "guix" command. Date: Sat, 06 Jun 2015 23:44:49 +0300 Message-ID: <87mw0czhvi.fsf@gmail.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:55843) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z1Kxd-0000rc-S7 for guix-devel@gnu.org; Sat, 06 Jun 2015 16:45:00 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z1KxX-000778-Fw for guix-devel@gnu.org; Sat, 06 Jun 2015 16:44:57 -0400 Received: from mail-la0-x231.google.com ([2a00:1450:4010:c03::231]:33448) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z1KxW-00076w-UV for guix-devel@gnu.org; Sat, 06 Jun 2015 16:44:51 -0400 Received: by labpy14 with SMTP id py14so73711478lab.0 for ; Sat, 06 Jun 2015 13:44:50 -0700 (PDT) Received: from leviafan ([217.107.192.168]) by mx.google.com with ESMTPSA id az6sm2814195lab.24.2015.06.06.13.44.48 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 06 Jun 2015 13:44:49 -0700 (PDT) List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org To: guix-devel@gnu.org --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Oof! There is a plenty of guix commands, actions, options, =E2=80=A6 So after this patch, it would be more convenient to use =E2=80=98guix=E2=80= =99 in "M-x shell". It should complete almost everything except suboptions of subcommands, I mean those =E2=80=98import=E2=80=99 modules (e.g., it will not complete =E2=80=98--no-test-dependencies=E2=80=99 option for =E2=80=98guix import ha= ckage=E2=80=99 command). Completing may lag a bit from time to time. It happens because shell commands (like =E2=80=98guix ... --help=E2=80=99) are called when necessary= to find options, packages, etc. But the found entries are "memoized" so next time the same entries will be completed much faster. Many thanks to Ludovic for the great idea of this package! --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0002-emacs-Add-shell-completions-for-guix-command.patch Content-Transfer-Encoding: quoted-printable >From 9ed30b97519afad84c58f1a7166f11a5e22ecda1 Mon Sep 17 00:00:00 2001 From: Alex Kost Date: Sat, 6 Jun 2015 22:19:51 +0300 Subject: [PATCH 2/2] emacs: Add shell completions for "guix" command. * emacs/guix-pcomplete.el: New file. * emacs.am (ELFILES): Add it. * doc/emacs.texi (Emacs Completions): New node. --- doc/emacs.texi | 32 ++++ emacs.am | 1 + emacs/guix-pcomplete.el | 394 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 427 insertions(+) create mode 100644 emacs/guix-pcomplete.el diff --git a/doc/emacs.texi b/doc/emacs.texi index 6c1b255..00cd5c1 100644 --- a/doc/emacs.texi +++ b/doc/emacs.texi @@ -19,6 +19,7 @@ guix package}). Specifically, ``guix.el'' makes it easy = to: * Usage: Emacs Usage. Using the interface. * Configuration: Emacs Configuration. Configuring the interface. * Prettify Mode: Emacs Prettify. Abbreviating @file{/gnu/store/@dots{}} fi= le names. +* Completions: Emacs Completions. Completing @command{guix} shell co= mmand. @end menu =20 @node Emacs Initial Setup @@ -489,3 +490,34 @@ mode hooks (@pxref{Hooks,,, emacs, The GNU Emacs Manua= l}), for example: (add-hook 'shell-mode-hook 'guix-prettify-mode) (add-hook 'dired-mode-hook 'guix-prettify-mode) @end example + + +@node Emacs Completions +@subsection Shell Completions + +Another feature that becomes available after configuring Emacs interface +(@pxref{Emacs Initial Setup}) is completing of @command{guix} +subcommands, options, packages and other things in @code{shell} +(@pxref{Interactive Shell,,, emacs, The GNU Emacs Manual}) and +@code{eshell} (@pxref{Top,,, eshell, Eshell: The Emacs Shell}). + +It works the same way as other completions do. Just press @key{TAB} +when your intuition tells you. + +And here are some examples, where pressing @key{TAB} may complete +something: + +@itemize @w{} + +@item @code{guix pa}@key{TAB} +@item @code{guix package -}@key{TAB} +@item @code{guix package --}@key{TAB} +@item @code{guix package -i gei}@key{TAB} +@item @code{guix build -L/tm}@key{TAB} +@item @code{guix build --sy}@key{TAB} +@item @code{guix build --system=3Di}@key{TAB} +@item @code{guix system rec}@key{TAB} +@item @code{guix lint --checkers=3Dsy}@key{TAB} +@item @code{guix lint --checkers=3Dsynopsis,des}@key{TAB} + +@end itemize diff --git a/emacs.am b/emacs.am index a43168e..372b33e 100644 --- a/emacs.am +++ b/emacs.am @@ -26,6 +26,7 @@ ELFILES =3D \ emacs/guix-info.el \ emacs/guix-list.el \ emacs/guix-messages.el \ + emacs/guix-pcomplete.el \ emacs/guix-prettify.el \ emacs/guix-utils.el \ emacs/guix.el diff --git a/emacs/guix-pcomplete.el b/emacs/guix-pcomplete.el new file mode 100644 index 0000000..5249bde --- /dev/null +++ b/emacs/guix-pcomplete.el @@ -0,0 +1,394 @@ +;;; guix-pcomplete.el --- Functions for completing guix commands -*- lexi= cal-binding: t -*- + +;; Copyright =C2=A9 2015 Alex Kost + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This file provides completions for "guix" command that may be used in +;; `shell', `eshell' and wherever `pcomplete' works. + +;;; Code: + +(require 'pcomplete) +(require 'pcmpl-unix) +(require 'cl-lib) +(require 'guix-utils) + + +;;; Regexps for parsing various "guix ..." outputs + +(defvar guix-pcomplete-parse-package-regexp + (rx bol (group (one-or-more (not blank)))) + "Regexp used to find names of the packages.") + +(defvar guix-pcomplete-parse-command-regexp + (rx bol + (or " " + " - '") ; for "guix system" actions + (group wordchar (one-or-more (or wordchar "-")))) + "Regexp used to find guix commands. +'Command' means any option not prefixed with '-'. For example, +guix subcommand, system action, importer, etc.") + +(defvar guix-pcomplete-parse-long-option-regexp + (rx (or " " ", ") + (group "--" (one-or-more (or wordchar "-")) + (zero-or-one "=3D"))) + "Regexp used to find long options.") + +(defvar guix-pcomplete-parse-short-option-regexp + (rx bol (one-or-more blank) + "-" (group (not (any "- ")))) + "Regexp used to find short options.") + +(defvar guix-pcomplete-parse-linter-regexp + (rx bol "- " (group (one-or-more (or wordchar "-")))) + "Regexp used to find 'lint' checkers.") + +(defvar guix-pcomplete-parse-regexp-group 1 + "Parenthesized expression of regexps used to find commands and +options.") + + +;;; Non-receivable completions + +(defvar guix-pcomplete-systems + '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux") + "List of supported systems.") + +(defvar guix-pcomplete-hash-formats + '("nix-base32" "base32" "base16" "hex" "hexadecimal") + "List of supported hash formats.") + +(defvar guix-pcomplete-refresh-subsets + '("core" "non-core") + "List of supported 'refresh' subsets.") + +(defvar guix-pcomplete-key-policies + '("interactive" "always" "never") + "List of supported key download policies.") + + +;;; Interacting with guix + +(defcustom guix-pcomplete-guix-program (executable-find "guix") + "Name of the 'guix' program. +It is used to find guix commands, options, packages, etc." + :type 'file + :group 'pcomplete + :group 'guix) + +(defun guix-pcomplete-run-guix (&rest args) + "Run `guix-pcomplete-guix-program' with ARGS. +Insert the output to the current buffer." + (apply #'call-process + guix-pcomplete-guix-program nil t nil args)) + +(defun guix-pcomplete-run-guix-and-search (regexp &optional group + &rest args) + "Run `guix-pcomplete-guix-program' with ARGS and search for matches. +Return a list of strings matching REGEXP. +GROUP specifies a parenthesized expression used in REGEXP." + (with-temp-buffer + (apply #'guix-pcomplete-run-guix args) + (goto-char (point-min)) + (let (result) + (while (re-search-forward regexp nil t) + (push (match-string-no-properties group) result)) + (nreverse result)))) + +(defmacro guix-pcomplete-define-options-finder (name docstring regexp + &optional filter) + "Define function NAME to receive guix options and commands. + +The defined function takes an optional COMMAND argument. This +function will run 'guix COMMAND --help' (or 'guix --help' if +COMMAND is nil) using `guix-pcomplete-run-guix-and-search' and +return its result. + +If FILTER is specified, it should be a function. The result is +passed to this FILTER as argument and the result value of this +function call is returned." + (declare (doc-string 2) (indent 1)) + `(guix-memoized-defun ,name (&optional command) + ,docstring + (let* ((args '("--help")) + (args (if command (cons command args) args)) + (res (apply #'guix-pcomplete-run-guix-and-search + ,regexp guix-pcomplete-parse-regexp-group args))) + ,(if filter + `(funcall ,filter res) + 'res)))) + +(guix-pcomplete-define-options-finder guix-pcomplete-commands + "If COMMAND is nil, return a list of available guix commands. +If COMMAND is non-nil (it should be a string), return available +subcommands, actions, etc. for this guix COMMAND." + guix-pcomplete-parse-command-regexp) + +(guix-pcomplete-define-options-finder guix-pcomplete-long-options + "Return a list of available long options for guix COMMAND." + guix-pcomplete-parse-long-option-regexp) + +(guix-pcomplete-define-options-finder guix-pcomplete-short-options + "Return a string with available short options for guix COMMAND." + guix-pcomplete-parse-short-option-regexp + (lambda (list) + (mapconcat #'identity list ""))) + +(guix-memoized-defun guix-pcomplete-all-packages () + "Return a list of all available Guix packages." + (guix-pcomplete-run-guix-and-search + guix-pcomplete-parse-package-regexp + guix-pcomplete-parse-regexp-group + "package" "--list-available")) + +(guix-memoized-defun guix-pcomplete-installed-packages (&optional profile) + "Return a list of Guix packages installed in PROFILE." + (let* ((args (and profile + (list (concat "--profile=3D" profile)))) + (args (append '("package" "--list-installed") args))) + (apply #'guix-pcomplete-run-guix-and-search + guix-pcomplete-parse-package-regexp + guix-pcomplete-parse-regexp-group + args))) + +(guix-memoized-defun guix-pcomplete-lint-checkers () + "Return a list of all available lint checkers." + (guix-pcomplete-run-guix-and-search + guix-pcomplete-parse-linter-regexp + guix-pcomplete-parse-regexp-group + "lint" "--list-checkers")) + + +;;; Completing + +(defvar guix-pcomplete-option-regexp (rx string-start "-") + "Regexp to match an option.") + +(defvar guix-pcomplete-long-option-regexp (rx string-start "--") + "Regexp to match a long option.") + +(defvar guix-pcomplete-long-option-with-arg-regexp + (rx string-start + (group "--" (one-or-more any)) "=3D" + (group (zero-or-more any))) + "Regexp to match a long option with its argument. +The first parenthesized group defines the option and the second +group - the argument.") + +(defvar guix-pcomplete-short-option-with-arg-regexp + (rx string-start + (group "-" (not (any "-"))) + (group (zero-or-more any))) + "Regexp to match a short option with its argument. +The first parenthesized group defines the option and the second +group - the argument.") + +(defun guix-pcomplete-match-option () + "Return non-nil, if the current argument is an option." + (pcomplete-match guix-pcomplete-option-regexp 0)) + +(defun guix-pcomplete-match-long-option () + "Return non-nil, if the current argument is a long option." + (pcomplete-match guix-pcomplete-long-option-regexp 0)) + +(defun guix-pcomplete-match-long-option-with-arg () + "Return non-nil, if the current argument is a long option with value." + (pcomplete-match guix-pcomplete-long-option-with-arg-regexp 0)) + +(defun guix-pcomplete-match-short-option-with-arg () + "Return non-nil, if the current argument is a short option with value." + (pcomplete-match guix-pcomplete-short-option-with-arg-regexp 0)) + +(defun guix-pcomplete-long-option-arg (option args) + "Return a long OPTION's argument from a list of arguments ARGS." + (let* ((re (concat "\\`" option "=3D\\(.*\\)")) + (args (cl-member-if (lambda (arg) + (string-match re arg)) + args)) + (cur (car args))) + (when cur + (match-string-no-properties 1 cur)))) + +(defun guix-pcomplete-short-option-arg (option args) + "Return a short OPTION's argument from a list of arguments ARGS." + (let* ((re (concat "\\`" option "\\(.*\\)")) + (args (cl-member-if (lambda (arg) + (string-match re arg)) + args)) + (cur (car args))) + (when cur + (let ((arg (match-string-no-properties 1 cur))) + (if (string=3D "" arg) + (cadr args) ; take the next arg + arg))))) + +(defun guix-pcomplete-complete-comma-args (entries) + "Complete comma separated arguments using ENTRIES." + (let ((index pcomplete-index)) + (while (=3D index pcomplete-index) + (let* ((args (if (or (guix-pcomplete-match-long-option-with-arg) + (guix-pcomplete-match-short-option-with-arg)) + (pcomplete-match-string 2 0) + (pcomplete-arg 0))) + (input (if (string-match ".*,\\(.*\\)" args) + (match-string-no-properties 1 args) + args))) + (pcomplete-here* entries input))))) + +(defun guix-pcomplete-complete-command-arg (command) + "Complete argument for guix COMMAND." + (cond + ((member command + '("archive" "build" "environment" "lint" "refresh")) + (while t + (pcomplete-here (guix-pcomplete-all-packages)))) + (t (pcomplete-here* (pcomplete-entries))))) + +(defun guix-pcomplete-complete-option-arg (command option &optional input) + "Complete argument for COMMAND's OPTION. +INPUT is the current partially completed string." + (cl-flet ((option? (short long) + (or (string=3D option short) + (string=3D option long))) + (command? (&rest commands) + (member command commands)) + (complete (entries) + (pcomplete-here entries input nil t)) + (complete* (entries) + (pcomplete-here* entries input t))) + (cond + ((option? "-L" "--load-path") + (complete* (pcomplete-dirs))) + ((string=3D "--key-download" option) + (complete* guix-pcomplete-key-policies)) + + ((command? "package") + (cond + ;; For '--install[=3D]' and '--remove[=3D]', try to complete a pack= age + ;; name (INPUT) after the "=3D" sign, and then the rest packages + ;; separated with spaces. + ((option? "-i" "--install") + (complete (guix-pcomplete-all-packages)) + (while (not (guix-pcomplete-match-option)) + (pcomplete-here (guix-pcomplete-all-packages)))) + ((option? "-r" "--remove") + (let* ((profile (or (guix-pcomplete-short-option-arg + "-p" pcomplete-args) + (guix-pcomplete-long-option-arg + "--profile" pcomplete-args))) + (profile (and profile (expand-file-name profile)))) + (complete (guix-pcomplete-installed-packages profile)) + (while (not (guix-pcomplete-match-option)) + (pcomplete-here (guix-pcomplete-installed-packages profile))))) + ((string=3D "--show" option) + (complete (guix-pcomplete-all-packages))) + ((option? "-p" "--profile") + (complete* (pcomplete-dirs))) + ((option? "-m" "--manifest") + (complete* (pcomplete-entries))))) + + ((and (command? "archive" "build") + (option? "-s" "--system")) + (complete* guix-pcomplete-systems)) + + ((and (command? "build") + (option? "-r" "--root")) + (complete* (pcomplete-entries))) + + ((and (command? "environment") + (option? "-l" "--load")) + (complete* (pcomplete-entries))) + + ((and (command? "hash" "download") + (option? "-f" "--format")) + (complete* guix-pcomplete-hash-formats)) + + ((and (command? "lint") + (option? "-c" "--checkers")) + (guix-pcomplete-complete-comma-args + (guix-pcomplete-lint-checkers))) + + ((and (command? "publish") + (option? "-u" "--user")) + (complete* (pcmpl-unix-user-names))) + + ((and (command? "refresh") + (option? "-s" "--select")) + (complete* guix-pcomplete-refresh-subsets))))) + +(defun guix-pcomplete-complete-options (command) + "Complete options (with their arguments) for guix COMMAND." + (while (guix-pcomplete-match-option) + (let ((index pcomplete-index)) + (if (guix-pcomplete-match-long-option) + + ;; Long options. + (if (guix-pcomplete-match-long-option-with-arg) + (let ((option (pcomplete-match-string 1 0)) + (arg (pcomplete-match-string 2 0))) + (guix-pcomplete-complete-option-arg + command option arg)) + + (pcomplete-here* (guix-pcomplete-long-options command)) + ;; We support '--opt arg' style (along with '--opt=3Darg'), + ;; because 'guix package --install/--remove' may be used this + ;; way. So try to complete an argument after the option has + ;; been completed. + (unless (guix-pcomplete-match-option) + (guix-pcomplete-complete-option-arg + command (pcomplete-arg 0 -1)))) + + ;; Short options. + (let ((arg (pcomplete-arg 0))) + (if (> (length arg) 2) + ;; Support specifying an argument after a short option witho= ut + ;; spaces (for example, '-L/tmp/foo'). + (guix-pcomplete-complete-option-arg + command + (substring-no-properties arg 0 2) + (substring-no-properties arg 2)) + (pcomplete-opt (guix-pcomplete-short-options command)) + (guix-pcomplete-complete-option-arg + command (pcomplete-arg 0 -1))))) + + ;; If there were no completions, move to the next argument and get + ;; out if the last argument is achieved. + (when (=3D index pcomplete-index) + (if (=3D pcomplete-index pcomplete-last) + (throw 'pcompleted nil) + (pcomplete-next-arg)))))) + +;;;###autoload +(defun pcomplete/guix () + "Completion for `guix'." + (let ((commands (guix-pcomplete-commands))) + (pcomplete-here* (cons "--help" commands)) + (let ((command (pcomplete-arg 'first 1))) + (when (member command commands) + (guix-pcomplete-complete-options command) + (let ((subcommands (guix-pcomplete-commands command))) + (when subcommands + (pcomplete-here* subcommands))) + (guix-pcomplete-complete-options command) + (guix-pcomplete-complete-command-arg command))))) + +(provide 'guix-pcomplete) + +;;; guix-pcomplete.el ends here --=20 2.2.1 --=-=-=--