diff options
Diffstat (limited to 'lisp/eshell/em-cmpl.el')
-rw-r--r-- | lisp/eshell/em-cmpl.el | 173 |
1 files changed, 122 insertions, 51 deletions
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el index 0bdedab12ff..201beb5071d 100644 --- a/lisp/eshell/em-cmpl.el +++ b/lisp/eshell/em-cmpl.el @@ -74,9 +74,7 @@ (require 'esh-util) (require 'em-dirs) -(eval-when-compile - (require 'cl-lib) - (require 'eshell)) +(eval-when-compile (require 'cl-lib)) ;;;###autoload (progn @@ -150,6 +148,10 @@ to writing a completion function." (eshell-cmpl--custom-variable-docstring 'pcomplete-dir-ignore) :type (get 'pcomplete-dir-ignore 'custom-type)) +(defcustom eshell-cmpl-remote-file-ignore nil + (eshell-cmpl--custom-variable-docstring 'pcomplete-remote-file-ignore) + :type (get 'pcomplete-remote-file-ignore 'custom-type)) + (defcustom eshell-cmpl-ignore-case (eshell-under-windows-p) (eshell-cmpl--custom-variable-docstring 'completion-ignore-case) :type (get 'completion-ignore-case 'custom-type)) @@ -250,6 +252,8 @@ to writing a completion function." eshell-cmpl-file-ignore) (setq-local pcomplete-dir-ignore eshell-cmpl-dir-ignore) + (setq-local pcomplete-remote-file-ignore + eshell-cmpl-remote-file-ignore) (setq-local completion-ignore-case eshell-cmpl-ignore-case) (setq-local pcomplete-autolist @@ -306,15 +310,44 @@ to writing a completion function." (insert-and-inherit "\t") (throw 'pcompleted t))) +(defun eshell-complete--eval-argument-form (arg) + "Evaluate a single Eshell argument form ARG for the purposes of completion." + (condition-case err + (let* (;; Don't allow running commands; they could have + ;; arbitrary side effects, which we don't want when we're + ;; just performing completions! + (eshell-allow-commands) + ;; Handle errors ourselves so that we can properly catch + ;; `eshell-commands-forbidden'. + (eshell-handle-errors) + (result (eshell-do-eval `(eshell-commands ,arg) t))) + (cl-assert (eq (car result) 'quote)) + (cadr result)) + (eshell-commands-forbidden + (propertize "\0" 'eshell-argument-stub + (intern (format "%s-command" (cadr err))))) + (error + (lwarn 'eshell :error + "Failed to evaluate argument form during completion: %S" arg) + (propertize "\0" 'eshell-argument-stub 'error)))) + +;; Code stolen from `eshell-plain-command'. +(defun eshell-external-command-p (command) + "Whether an external command shall be called." + (let* ((esym (eshell-find-alias-function command)) + (sym (or esym (intern-soft command)))) + (not (and sym (fboundp sym) + (or esym eshell-prefer-lisp-functions + (not (eshell-search-path command))))))) + (defun eshell-complete-parse-arguments () "Parse the command line arguments for `pcomplete-argument'." (when (and eshell-no-completion-during-jobs - (eshell-interactive-process-p)) + eshell-foreground-command) (eshell--pcomplete-insert-tab)) (let ((end (point-marker)) - (begin (save-excursion (eshell-bol) (point))) - (posns (list t)) - args delim) + (begin (save-excursion (beginning-of-line) (point))) + args posns delim incomplete-arg) (when (and pcomplete-allow-modifications (memq this-command '(pcomplete-expand pcomplete-expand-and-complete))) @@ -322,59 +355,97 @@ to writing a completion function." (if (= begin end) (end-of-line)) (setq end (point-marker))) - (if (setq delim - (catch 'eshell-incomplete - (ignore - (setq args (eshell-parse-arguments begin end))))) - (cond ((memq (car delim) '(?\{ ?\<)) - (setq begin (1+ (cadr delim)) - args (eshell-parse-arguments begin end))) - ((eq (car delim) ?\() - (throw 'pcompleted (elisp-completion-at-point))) - (t - (eshell--pcomplete-insert-tab)))) - (when (get-text-property (1- end) 'comment) + ;; Don't expand globs when parsing arguments; we want to pass any + ;; globs to Pcomplete unaltered. + (declare-function eshell-parse-glob-chars "em-glob" ()) + (let ((eshell-parse-argument-hook (remq #'eshell-parse-glob-chars + eshell-parse-argument-hook))) + (if (setq delim + (catch 'eshell-incomplete + (ignore + (setq args (eshell-parse-arguments begin end))))) + (cond ((member (car delim) '("{" "${" "$<")) + (setq begin (1+ (cadr delim)) + args (eshell-parse-arguments begin end))) + ((member (car delim) '("$'" "$\"" "#<")) + ;; Add the (incomplete) argument to our arguments, and + ;; note its position. + (setq args (append (nth 2 delim) (list (car delim))) + incomplete-arg t) + (push (- (nth 1 delim) 2) posns)) + ((member (car delim) '("(" "$(")) + (throw 'pcompleted (elisp-completion-at-point))) + (t + (eshell--pcomplete-insert-tab))))) + (when (and (< begin end) + (get-text-property (1- end) 'comment)) (eshell--pcomplete-insert-tab)) - (let ((pos begin)) - (while (< pos end) - (if (get-text-property pos 'arg-begin) - (nconc posns (list pos))) - (setq pos (1+ pos)))) - (setq posns (cdr posns)) + (let ((pos (1- end))) + (while (>= pos begin) + (when (get-text-property pos 'arg-begin) + (push pos posns)) + (setq pos (1- pos)))) (cl-assert (= (length args) (length posns))) - (let ((a args) - (i 0) - l) + (let ((a args) (i 0) new-start) (while a - (if (and (consp (car a)) - (eq (caar a) 'eshell-operator)) - (setq l i)) - (setq a (cdr a) i (1+ i))) - (and l - (setq args (nthcdr (1+ l) args) - posns (nthcdr (1+ l) posns)))) + ;; If there's an unreplaced `eshell-operator' sigil, consider + ;; the token after it the new start of our arguments. + (when (and (consp (car a)) + (eq (caar a) 'eshell-operator)) + (setq new-start i)) + (setq a (cdr a) + i (1+ i))) + (when new-start + (setq args (nthcdr (1+ new-start) args) + posns (nthcdr (1+ new-start) posns)))) (cl-assert (= (length args) (length posns))) - (when (and args (eq (char-syntax (char-before end)) ? ) + (when (and args (not incomplete-arg) + (eq (char-syntax (char-before end)) ? ) (not (eq (char-before (1- end)) ?\\))) (nconc args (list "")) (nconc posns (list (point)))) + ;; Evaluate and expand Eshell forms. + (let (evaled-args evaled-posns) + (cl-mapc + (lambda (arg posn) + (pcase arg + (`(eshell-splice-args ,val) + (dolist (subarg (eshell-complete--eval-argument-form val)) + (push subarg evaled-args) + (push posn evaled-posns))) + ((pred listp) + (push (eshell-complete--eval-argument-form arg) evaled-args) + (push posn evaled-posns)) + (_ + (push arg evaled-args) + (push posn evaled-posns)))) + args posns) + (setq args (nreverse evaled-args) + posns (nreverse evaled-posns))) + ;; Determine, whether remote file names shall be completed. They + ;; shouldn't for external commands, or when in a pipe. Respect + ;; also `eshell-cmpl-remote-file-ignore', which could be set by + ;; the user. + (setq-local pcomplete-remote-file-ignore + (or eshell-cmpl-remote-file-ignore + eshell-in-pipeline-p ; does not work + (eshell-external-command-p (car args)))) + ;; Convert arguments to forms that Pcomplete can understand. (cons (mapcar (lambda (arg) - (let ((val - (if (listp arg) - (let ((result - (eshell-do-eval - (list 'eshell-commands arg) t))) - (cl-assert (eq (car result) 'quote)) - (cadr result)) - arg))) - (cond ((numberp val) - (setq val (number-to-string val))) - ;; expand .../ etc that only eshell understands to - ;; standard ../../ - ((and (stringp val)) (string-match "\\.\\.\\.+/" val) - (setq val (eshell-expand-multiple-dots val)))) - (or val ""))) + (pcase arg + ;; Expand ".../" etc that only Eshell understands to + ;; the standard "../../". + ((rx ".." (+ ".") "/") + (propertize (eshell-expand-multiple-dots arg) + 'pcomplete-arg-value arg)) + ((pred stringp) + arg) + ('nil + (propertize "" 'pcomplete-arg-value arg)) + (_ + (propertize (eshell-stringify arg) + 'pcomplete-arg-value arg)))) args) posns))) |