diff --git a/lisp/emulation/viper-cmd.el b/lisp/emulation/viper-cmd.el index ee96d8efac6..2a37c383f81 100644 --- a/lisp/emulation/viper-cmd.el +++ b/lisp/emulation/viper-cmd.el @@ -574,7 +574,7 @@ ((memq state '(insert-state replace-state)) viper-minibuffer-insert-face)))) - (if (viper-is-in-minibuffer) + (if (and (viper-is-in-minibuffer) viper-enable-minibuffer-faces) (viper-set-minibuffer-overlay)) ) diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el index af8ac4278f1..5dfd10d6e4c 100644 --- a/lisp/eshell/em-cmpl.el +++ b/lisp/eshell/em-cmpl.el @@ -317,8 +317,7 @@ to writing a completion function." (eshell--pcomplete-insert-tab)) (let ((end (point-marker)) (begin (save-excursion (beginning-of-line) (point))) - (posns (list t)) - args delim) + args posns delim) (when (and pcomplete-allow-modifications (memq this-command '(pcomplete-expand pcomplete-expand-and-complete))) @@ -330,21 +329,25 @@ to writing a completion function." (catch 'eshell-incomplete (ignore (setq args (eshell-parse-arguments begin end))))) - (cond ((memq (car delim) '(?\{ ?\<)) + (cond ((member (car delim) '("{" "${" "$<")) (setq begin (1+ (cadr delim)) args (eshell-parse-arguments begin end))) - ((eq (car delim) ?\() + ((member (car delim) '("$'" "$\"")) + ;; Add the (incomplete) argument to our arguments, and + ;; note its position. + (setq args (append (nth 2 delim) (list (car delim)))) + (push (- (nth 1 delim) 2) posns)) + ((member (car delim) '("(" "$(")) (throw 'pcompleted (elisp-completion-at-point))) (t (eshell--pcomplete-insert-tab)))) (when (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) new-start) (while a diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el index 0d02b64b084..eb679b80cb5 100644 --- a/lisp/eshell/em-dirs.el +++ b/lisp/eshell/em-dirs.el @@ -281,15 +281,32 @@ Thus, this does not include the current directory.") (let ((arg (pcomplete-actual-arg))) (when (string-match "\\`~[a-z]*\\'" arg) (setq pcomplete-stub (substring arg 1) - pcomplete-last-completion-raw t) - (throw 'pcomplete-completions - (progn - (eshell-read-user-names) - (pcomplete-uniquify-list - (mapcar - (lambda (user) - (file-name-as-directory (cdr user))) - eshell-user-names))))))) + pcomplete-last-completion-raw t) + (eshell-read-user-names) + (let ((names (pcomplete-uniquify-list + (mapcar (lambda (user) + (file-name-as-directory (cdr user))) + eshell-user-names)))) + (throw 'pcomplete-completions + ;; Provide a programmed completion table. This works + ;; just like completing over the list of names, except + ;; it always returns the completed string for + ;; `try-completion', never `t'. That's because this is + ;; only completing a directory name, and so the + ;; completion isn't actually finished yet. + (lambda (string pred action) + (pcase action + ('nil ; try-completion + (let ((result (try-completion string names pred))) + (if (eq result t) string result))) + ('t ; all-completions + (all-completions string names pred)) + ('lambda ; test-completion + (test-completion string names pred)) + ('metadata + '(metadata (category . file))) + (`(boundaries . ,suffix) + `(boundaries 0 . ,(string-search "/" suffix)))))))))) (defun eshell/pwd (&rest _args) "Change output from `pwd' to be cleaner." diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el index c7360fb246e..8a2ba13b2ad 100644 --- a/lisp/eshell/em-glob.el +++ b/lisp/eshell/em-glob.el @@ -171,7 +171,7 @@ interpretation." (end (eshell-find-delimiter delim (if (eq delim ?\[) ?\] ?\))))) (if (not end) - (throw 'eshell-incomplete delim) + (throw 'eshell-incomplete (char-to-string delim)) (if (and (eshell-using-module 'eshell-pred) (eshell-arg-delimiter (1+ end))) (ignore (goto-char here)) diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el index 14fa27aba06..2ccca092b86 100644 --- a/lisp/eshell/em-pred.el +++ b/lisp/eshell/em-pred.el @@ -293,7 +293,7 @@ This function is specially for adding onto `eshell-parse-argument-hook'." (forward-char) (let ((end (eshell-find-delimiter ?\( ?\)))) (if (not end) - (throw 'eshell-incomplete ?\() + (throw 'eshell-incomplete "(") (when (eshell-arg-delimiter (1+ end)) (save-restriction (narrow-to-region (point) end) diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el index 6c882471aee..cb0b2e0938c 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -421,7 +421,7 @@ backslash is in a quoted string, the backslash and the character after are both returned." (when (eq (char-after) ?\\) (when (eshell-looking-at-backslash-return (point)) - (throw 'eshell-incomplete ?\\)) + (throw 'eshell-incomplete "\\")) (forward-char 2) ; Move one char past the backslash. (let ((special-chars (if eshell-current-quoted eshell-special-chars-inside-quoting @@ -447,7 +447,7 @@ after are both returned." (if (eq (char-after) ?\') (let ((end (eshell-find-delimiter ?\' ?\'))) (if (not end) - (throw 'eshell-incomplete ?\') + (throw 'eshell-incomplete "'") (let ((string (buffer-substring-no-properties (1+ (point)) end))) (goto-char (1+ end)) (while (string-match "''" string) @@ -460,7 +460,7 @@ after are both returned." (let* ((end (eshell-find-delimiter ?\" ?\" nil nil t)) (eshell-current-quoted t)) (if (not end) - (throw 'eshell-incomplete ?\") + (throw 'eshell-incomplete "\"") (prog1 (save-restriction (forward-char) @@ -514,7 +514,7 @@ If the form has no `type', the syntax is parsed as if `type' were t)) ;; buffer-p is non-nil by default. (end (eshell-find-delimiter ?\< ?\>))) (when (not end) - (throw 'eshell-incomplete ?\<)) + (throw 'eshell-incomplete "#<")) (if (eshell-arg-delimiter (1+ end)) (prog1 (list (if buffer-p 'get-buffer-create 'get-process) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index efc46f10c96..d609711402a 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -681,7 +681,7 @@ This means an exit code of 0." (not (eq (char-after (1+ (point))) ?\})))) (let ((end (eshell-find-delimiter ?\{ ?\}))) (if (not end) - (throw 'eshell-incomplete ?\{) + (throw 'eshell-incomplete "{") (when (eshell-arg-delimiter (1+ end)) (prog1 `(eshell-as-subcommand @@ -698,7 +698,7 @@ This means an exit code of 0." (condition-case nil (read (current-buffer)) (end-of-file - (throw 'eshell-incomplete ?\())))) + (throw 'eshell-incomplete "("))))) (if (eshell-arg-delimiter) `(eshell-command-to-value (eshell-lisp-command (quote ,obj))) diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el index b3cde472713..0c381dbb86a 100644 --- a/lisp/eshell/esh-mode.el +++ b/lisp/eshell/esh-mode.el @@ -580,7 +580,7 @@ will return the parsed command." (setq command (eshell-parse-command (cons beg end) args t))))) (ignore - (message "Expecting completion of delimiter %c ..." + (message "Expecting completion of delimiter %s ..." (if (listp delim) (car delim) delim))) diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index 60aab92b33e..0031324b537 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -434,9 +434,14 @@ the values of nil for each." (defun eshell-envvar-names (&optional environment) "Return a list of currently visible environment variable names." - (mapcar (lambda (x) - (substring x 0 (string-search "=" x))) - (or environment process-environment))) + (delete-dups + (append + ;; Real environment variables + (mapcar (lambda (x) + (substring x 0 (string-search "=" x))) + (or environment process-environment)) + ;; Eshell variable aliases + (mapcar #'car eshell-variable-aliases-list)))) (defun eshell-environment-variables () "Return a `process-environment', fully updated. @@ -503,7 +508,7 @@ Possible variable references are: ((eq (char-after) ?{) (let ((end (eshell-find-delimiter ?\{ ?\}))) (if (not end) - (throw 'eshell-incomplete ?\{) + (throw 'eshell-incomplete "${") (forward-char) (prog1 `(eshell-apply-indices @@ -527,7 +532,7 @@ Possible variable references are: ((eq (char-after) ?\<) (let ((end (eshell-find-delimiter ?\< ?\>))) (if (not end) - (throw 'eshell-incomplete ?\<) + (throw 'eshell-incomplete "$<") (let* ((temp (make-temp-file temporary-file-directory)) (cmd (concat (buffer-substring (1+ (point)) end) " > " temp))) @@ -560,15 +565,19 @@ Possible variable references are: (current-buffer))))) indices ,eshell-current-quoted) (end-of-file - (throw 'eshell-incomplete ?\()))) + (throw 'eshell-incomplete "$(")))) ((looking-at (rx-to-string `(or "'" ,(if eshell-current-quoted "\\\"" "\"")))) (eshell-with-temp-command (or (eshell-unescape-inner-double-quote (point-max)) (cons (point) (point-max))) - (let ((name (if (eq (char-after) ?\') - (eshell-parse-literal-quote) - (eshell-parse-double-quote)))) + (let (name) + (when-let ((delim + (catch 'eshell-incomplete + (ignore (setq name (if (eq (char-after) ?\') + (eshell-parse-literal-quote) + (eshell-parse-double-quote))))))) + (throw 'eshell-incomplete (concat "$" delim))) (when name `(eshell-get-variable ,(eval name) indices ,eshell-current-quoted))))) ((assoc (char-to-string (char-after)) @@ -597,7 +606,7 @@ For example, \"[0 1][2]\" becomes: (while (eq (char-after) ?\[) (let ((end (eshell-find-delimiter ?\[ ?\]))) (if (not end) - (throw 'eshell-incomplete ?\[) + (throw 'eshell-incomplete "[") (forward-char) (eshell-with-temp-command (or (eshell-unescape-inner-double-quote end) (cons (point) end)) @@ -816,33 +825,40 @@ START and END." (let ((arg (pcomplete-actual-arg))) (when (string-match (rx "$" (? (or "#" "@")) - (? (group (regexp eshell-variable-name-regexp))) - string-end) + (? (or (group-n 1 (regexp eshell-variable-name-regexp) + string-end) + (seq (group-n 2 (or "'" "\"")) + (group-n 1 (+ anychar)))))) arg) (setq pcomplete-stub (substring arg (match-beginning 1))) + (let ((delimiter (match-string 2 arg))) + ;; When finished with completion, insert the trailing + ;; delimiter, if any, and add a trailing slash if the variable + ;; refers to a directory. + (add-function + :before-until (var pcomplete-exit-function) + (lambda (variable status) + (when (eq status 'finished) + (when delimiter + (if (looking-at (regexp-quote delimiter)) + (goto-char (match-end 0)) + (insert delimiter))) + (let ((non-essential t) + (value (eshell-get-variable variable))) + (when (and (stringp value) (file-directory-p value)) + (insert "/") + ;; Tell Pcomplete not to insert its own termination + ;; string. + t)))))) (throw 'pcomplete-completions (eshell-variables-list))))) (defun eshell-variables-list () "Generate list of applicable variables." - (let ((argname pcomplete-stub) - completions) - (dolist (alias eshell-variable-aliases-list) - (if (string-match (concat "^" argname) (car alias)) - (setq completions (cons (car alias) completions)))) + (let ((argname pcomplete-stub)) (sort - (append - (mapcar - (lambda (varname) - (let ((value (eshell-get-variable varname))) - (if (and value - (stringp value) - (file-directory-p value)) - (concat varname "/") - varname))) - (eshell-envvar-names (eshell-environment-variables))) - (all-completions argname obarray 'boundp) - completions) - 'string-lessp))) + (append (eshell-envvar-names) + (all-completions argname obarray #'boundp)) + #'string-lessp))) (defun eshell-complete-variable-assignment () "If there is a variable assignment, allow completion of entries." diff --git a/lisp/pcomplete.el b/lisp/pcomplete.el index 1ca7a213361..36f68f1af57 100644 --- a/lisp/pcomplete.el +++ b/lisp/pcomplete.el @@ -362,6 +362,32 @@ modified to be an empty string, or the desired separation string." ;;; User Functions: +(defun pcomplete-default-exit-function (_s status) + "The default exit function to use in `pcomplete-completions-at-point'. +This just adds `pcomplete-termination-string' after the +completion if STATUS is `finished'." + (unless (zerop (length pcomplete-termination-string)) + (when (eq status 'finished) + (if (looking-at + (regexp-quote pcomplete-termination-string)) + (goto-char (match-end 0)) + (insert pcomplete-termination-string))))) + +(defvar pcomplete-exit-function #'pcomplete-default-exit-function + "The exit function to call in `pcomplete-completions-at-point'. + +This variable is let-bound in `pcomplete-completions-at-point', +so you can modify or advise it in order to adjust the behavior +for a specific completion. For example, you might do the +following in a `pcomplete-try-first-hook' function to insert a +trailing slash after a completion: + + (add-function + :before (var pcomplete-exit-function) + (lambda (_ status) + (when (eq status \\='finished) + (insert \"/\"))))") + ;;; Alternative front-end using the standard completion facilities. ;; The way pcomplete-parse-arguments and pcomplete-stub work only @@ -406,6 +432,7 @@ Same as `pcomplete' but using the standard completion UI." (if pcomplete-allow-modifications buffer-read-only t)) pcomplete-seen pcomplete-norm-func pcomplete-args pcomplete-last pcomplete-index + (pcomplete-exit-function pcomplete-exit-function) (pcomplete-autolist pcomplete-autolist) (pcomplete-suffix-list pcomplete-suffix-list) ;; Apparently the vars above are global vars modified by @@ -494,16 +521,7 @@ Same as `pcomplete' but using the standard completion UI." (get-text-property 0 'pcomplete-help cand))) :predicate pred :exit-function - ;; If completion is finished, add a terminating space. - ;; We used to also do this if STATUS is `sole', but - ;; that does not work right when completion cycling. - (unless (zerop (length pcomplete-termination-string)) - (lambda (_s status) - (when (eq status 'finished) - (if (looking-at - (regexp-quote pcomplete-termination-string)) - (goto-char (match-end 0)) - (insert pcomplete-termination-string))))))))))) + pcomplete-exit-function)))))) ;; I don't think such commands are usable before first setting up buffer-local ;; variables to parse args, so there's no point autoloading it. diff --git a/test/lisp/eshell/em-cmpl-tests.el b/test/lisp/eshell/em-cmpl-tests.el index 12a156fbb38..ecab7332822 100644 --- a/test/lisp/eshell/em-cmpl-tests.el +++ b/test/lisp/eshell/em-cmpl-tests.el @@ -183,6 +183,31 @@ See ." (should (equal (eshell-insert-and-complete "echo $system-nam") "echo $system-name ")))) +(ert-deftest em-cmpl-test/quoted-variable-ref-completion () + "Test completion of variable references like \"$'var'\". +See ." + (with-temp-eshell + (should (equal (eshell-insert-and-complete "echo $'system-nam") + "echo $'system-name' "))) + (with-temp-eshell + (should (equal (eshell-insert-and-complete "echo $\"system-nam") + "echo $\"system-name\" ")))) + +(ert-deftest em-cmpl-test/variable-ref-completion/directory () + "Test completion of variable references that expand to directories. +See ." + (with-temp-eshell + (should (equal (eshell-insert-and-complete "echo $PW") + "echo $PWD/"))) + (with-temp-eshell + (let ((minibuffer-message-timeout 0) + (inhibit-message t)) + (should (equal (eshell-insert-and-complete "echo $PWD") + "echo $PWD/")))) + (with-temp-eshell + (should (equal (eshell-insert-and-complete "echo $'PW") + "echo $'PWD'/")))) + (ert-deftest em-cmpl-test/variable-assign-completion () "Test completion of variable assignments like \"var=value\". See ." @@ -193,15 +218,14 @@ See ." "VAR=file.txt "))))) (ert-deftest em-cmpl-test/user-ref-completion () - "Test completeion of user references like \"~user\". + "Test completion of user references like \"~user\". See ." (unwind-protect (with-temp-eshell (cl-letf (((symbol-function 'eshell-read-user-names) (lambda () (setq eshell-user-names '((1234 . "user")))))) - ;; FIXME: Should this really add a space at the end? (should (equal (eshell-insert-and-complete "echo ~us") - "echo ~user/ ")))) + "echo ~user/")))) ;; Clear the cached user names we set above. (setq eshell-user-names nil)))