diff options
| -rw-r--r-- | lisp/eshell/em-cmpl.el | 60 | ||||
| -rw-r--r-- | lisp/eshell/esh-cmd.el | 15 | ||||
| -rw-r--r-- | test/lisp/eshell/em-cmpl-tests.el | 86 |
3 files changed, 131 insertions, 30 deletions
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el index b65652019d4..732bbb3f1fa 100644 --- a/lisp/eshell/em-cmpl.el +++ b/lisp/eshell/em-cmpl.el | |||
| @@ -306,9 +306,24 @@ to writing a completion function." | |||
| 306 | 306 | ||
| 307 | (defun eshell-complete--eval-argument-form (arg) | 307 | (defun eshell-complete--eval-argument-form (arg) |
| 308 | "Evaluate a single Eshell argument form ARG for the purposes of completion." | 308 | "Evaluate a single Eshell argument form ARG for the purposes of completion." |
| 309 | (let ((result (eshell-do-eval `(eshell-commands ,arg) t))) | 309 | (condition-case err |
| 310 | (cl-assert (eq (car result) 'quote)) | 310 | (let* (;; Don't allow running commands; they could have |
| 311 | (cadr result))) | 311 | ;; arbitrary side effects, which we don't want when we're |
| 312 | ;; just performing completions! | ||
| 313 | (eshell-allow-commands) | ||
| 314 | ;; Handle errors ourselves so that we can properly catch | ||
| 315 | ;; `eshell-commands-forbidden'. | ||
| 316 | (eshell-handle-errors) | ||
| 317 | (result (eshell-do-eval `(eshell-commands ,arg) t))) | ||
| 318 | (cl-assert (eq (car result) 'quote)) | ||
| 319 | (cadr result)) | ||
| 320 | (eshell-commands-forbidden | ||
| 321 | (propertize "\0" 'eshell-argument-stub | ||
| 322 | (intern (format "%s-command" (cadr err))))) | ||
| 323 | (error | ||
| 324 | (lwarn 'eshell :error | ||
| 325 | "Failed to evaluate argument form during completion: %S" arg) | ||
| 326 | (propertize "\0" 'eshell-argument-stub 'error)))) | ||
| 312 | 327 | ||
| 313 | (defun eshell-complete-parse-arguments () | 328 | (defun eshell-complete-parse-arguments () |
| 314 | "Parse the command line arguments for `pcomplete-argument'." | 329 | "Parse the command line arguments for `pcomplete-argument'." |
| @@ -325,23 +340,28 @@ to writing a completion function." | |||
| 325 | (if (= begin end) | 340 | (if (= begin end) |
| 326 | (end-of-line)) | 341 | (end-of-line)) |
| 327 | (setq end (point-marker))) | 342 | (setq end (point-marker))) |
| 328 | (if (setq delim | 343 | ;; Don't expand globs when parsing arguments; we want to pass any |
| 329 | (catch 'eshell-incomplete | 344 | ;; globs to Pcomplete unaltered. |
| 330 | (ignore | 345 | (declare-function eshell-parse-glob-chars "em-glob" ()) |
| 331 | (setq args (eshell-parse-arguments begin end))))) | 346 | (let ((eshell-parse-argument-hook (remq #'eshell-parse-glob-chars |
| 332 | (cond ((member (car delim) '("{" "${" "$<")) | 347 | eshell-parse-argument-hook))) |
| 333 | (setq begin (1+ (cadr delim)) | 348 | (if (setq delim |
| 334 | args (eshell-parse-arguments begin end))) | 349 | (catch 'eshell-incomplete |
| 335 | ((member (car delim) '("$'" "$\"" "#<")) | 350 | (ignore |
| 336 | ;; Add the (incomplete) argument to our arguments, and | 351 | (setq args (eshell-parse-arguments begin end))))) |
| 337 | ;; note its position. | 352 | (cond ((member (car delim) '("{" "${" "$<")) |
| 338 | (setq args (append (nth 2 delim) (list (car delim))) | 353 | (setq begin (1+ (cadr delim)) |
| 339 | incomplete-arg t) | 354 | args (eshell-parse-arguments begin end))) |
| 340 | (push (- (nth 1 delim) 2) posns)) | 355 | ((member (car delim) '("$'" "$\"" "#<")) |
| 341 | ((member (car delim) '("(" "$(")) | 356 | ;; Add the (incomplete) argument to our arguments, and |
| 342 | (throw 'pcompleted (elisp-completion-at-point))) | 357 | ;; note its position. |
| 343 | (t | 358 | (setq args (append (nth 2 delim) (list (car delim))) |
| 344 | (eshell--pcomplete-insert-tab)))) | 359 | incomplete-arg t) |
| 360 | (push (- (nth 1 delim) 2) posns)) | ||
| 361 | ((member (car delim) '("(" "$(")) | ||
| 362 | (throw 'pcompleted (elisp-completion-at-point))) | ||
| 363 | (t | ||
| 364 | (eshell--pcomplete-insert-tab))))) | ||
| 345 | (when (get-text-property (1- end) 'comment) | 365 | (when (get-text-property (1- end) 'comment) |
| 346 | (eshell--pcomplete-insert-tab)) | 366 | (eshell--pcomplete-insert-tab)) |
| 347 | (let ((pos (1- end))) | 367 | (let ((pos (1- end))) |
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 1a458290dfe..d5237ee1f04 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el | |||
| @@ -293,6 +293,17 @@ CDR are the same process. | |||
| 293 | 293 | ||
| 294 | When the process in the CDR completes, resume command evaluation.") | 294 | When the process in the CDR completes, resume command evaluation.") |
| 295 | 295 | ||
| 296 | (defvar eshell-allow-commands t | ||
| 297 | "If non-nil, allow evaluating command forms (including Lisp forms). | ||
| 298 | If you want to forbid command forms, you can let-bind this to a | ||
| 299 | non-nil value before calling `eshell-do-eval'. Then, any command | ||
| 300 | forms will signal `eshell-commands-forbidden'. This is useful | ||
| 301 | if, for example, you want to evaluate simple expressions like | ||
| 302 | variable expansions, but not fully-evaluate the command. See | ||
| 303 | also `eshell-complete-parse-arguments'.") | ||
| 304 | |||
| 305 | (define-error 'eshell-commands-forbidden "Commands forbidden") | ||
| 306 | |||
| 296 | ;;; Functions: | 307 | ;;; Functions: |
| 297 | 308 | ||
| 298 | (defsubst eshell-interactive-process-p () | 309 | (defsubst eshell-interactive-process-p () |
| @@ -1328,6 +1339,8 @@ have been replaced by constants." | |||
| 1328 | (defun eshell-named-command (command &optional args) | 1339 | (defun eshell-named-command (command &optional args) |
| 1329 | "Insert output from a plain COMMAND, using ARGS. | 1340 | "Insert output from a plain COMMAND, using ARGS. |
| 1330 | COMMAND may result in an alias being executed, or a plain command." | 1341 | COMMAND may result in an alias being executed, or a plain command." |
| 1342 | (unless eshell-allow-commands | ||
| 1343 | (signal 'eshell-commands-forbidden '(named))) | ||
| 1331 | (setq eshell-last-arguments args | 1344 | (setq eshell-last-arguments args |
| 1332 | eshell-last-command-name (eshell-stringify command)) | 1345 | eshell-last-command-name (eshell-stringify command)) |
| 1333 | (run-hook-with-args 'eshell-prepare-command-hook) | 1346 | (run-hook-with-args 'eshell-prepare-command-hook) |
| @@ -1465,6 +1478,8 @@ via `eshell-errorn'." | |||
| 1465 | 1478 | ||
| 1466 | (defun eshell-lisp-command (object &optional args) | 1479 | (defun eshell-lisp-command (object &optional args) |
| 1467 | "Insert Lisp OBJECT, using ARGS if a function." | 1480 | "Insert Lisp OBJECT, using ARGS if a function." |
| 1481 | (unless eshell-allow-commands | ||
| 1482 | (signal 'eshell-commands-forbidden '(lisp))) | ||
| 1468 | (catch 'eshell-external ; deferred to an external command | 1483 | (catch 'eshell-external ; deferred to an external command |
| 1469 | (setq eshell-last-command-status 0 | 1484 | (setq eshell-last-command-status 0 |
| 1470 | eshell-last-arguments args) | 1485 | eshell-last-arguments args) |
diff --git a/test/lisp/eshell/em-cmpl-tests.el b/test/lisp/eshell/em-cmpl-tests.el index ea907f1945d..29a41625d5e 100644 --- a/test/lisp/eshell/em-cmpl-tests.el +++ b/test/lisp/eshell/em-cmpl-tests.el | |||
| @@ -69,11 +69,10 @@ ACTUAL and EXPECTED should both be lists of strings." | |||
| 69 | (ert-deftest em-cmpl-test/parse-arguments/pipeline () | 69 | (ert-deftest em-cmpl-test/parse-arguments/pipeline () |
| 70 | "Test that parsing arguments for completion discards earlier commands." | 70 | "Test that parsing arguments for completion discards earlier commands." |
| 71 | (with-temp-eshell | 71 | (with-temp-eshell |
| 72 | (let ((eshell-test-value '("foo" "bar"))) | 72 | (insert "echo hi | cat") |
| 73 | (insert "echo hi | cat") | 73 | (should (eshell-arguments-equal |
| 74 | (should (eshell-arguments-equal | 74 | (car (eshell-complete-parse-arguments)) |
| 75 | (car (eshell-complete-parse-arguments)) | 75 | '("cat"))))) |
| 76 | '("cat")))))) | ||
| 77 | 76 | ||
| 78 | (ert-deftest em-cmpl-test/parse-arguments/multiple-dots () | 77 | (ert-deftest em-cmpl-test/parse-arguments/multiple-dots () |
| 79 | "Test parsing arguments with multiple dots like \".../\"." | 78 | "Test parsing arguments with multiple dots like \".../\"." |
| @@ -123,6 +122,45 @@ ACTUAL and EXPECTED should both be lists of strings." | |||
| 123 | (car (eshell-complete-parse-arguments)) | 122 | (car (eshell-complete-parse-arguments)) |
| 124 | '("echo" "foo" "bar")))))) | 123 | '("echo" "foo" "bar")))))) |
| 125 | 124 | ||
| 125 | (ert-deftest em-cmpl-test/parse-arguments/unevaluated-subcommand () | ||
| 126 | "Test that subcommands return a stub when parsing for completion." | ||
| 127 | (with-temp-eshell | ||
| 128 | (insert "echo {echo hi}") | ||
| 129 | (should (eshell-arguments-equal | ||
| 130 | (car (eshell-complete-parse-arguments)) | ||
| 131 | `("echo" ,(propertize | ||
| 132 | "\0" 'eshell-argument-stub 'named-command))))) | ||
| 133 | (with-temp-eshell | ||
| 134 | (insert "echo ${echo hi}") | ||
| 135 | (should (eshell-arguments-equal | ||
| 136 | (car (eshell-complete-parse-arguments)) | ||
| 137 | `("echo" ,(propertize | ||
| 138 | "\0" 'eshell-argument-stub 'named-command)))))) | ||
| 139 | |||
| 140 | (ert-deftest em-cmpl-test/parse-arguments/unevaluated-lisp-form () | ||
| 141 | "Test that Lisp forms return a stub when parsing for completion." | ||
| 142 | (with-temp-eshell | ||
| 143 | (insert "echo (concat \"hi\")") | ||
| 144 | (should (eshell-arguments-equal | ||
| 145 | (car (eshell-complete-parse-arguments)) | ||
| 146 | `("echo" ,(propertize | ||
| 147 | "\0" 'eshell-argument-stub 'lisp-command))))) | ||
| 148 | (with-temp-eshell | ||
| 149 | (insert "echo $(concat \"hi\")") | ||
| 150 | (should (eshell-arguments-equal | ||
| 151 | (car (eshell-complete-parse-arguments)) | ||
| 152 | `("echo" ,(propertize | ||
| 153 | "\0" 'eshell-argument-stub 'lisp-command)))))) | ||
| 154 | |||
| 155 | (ert-deftest em-cmpl-test/parse-arguments/unevaluated-inner-subcommand () | ||
| 156 | "Test that nested subcommands return a stub when parsing for completion." | ||
| 157 | (with-temp-eshell | ||
| 158 | (insert "echo $exec-path[${echo 0}]") | ||
| 159 | (should (eshell-arguments-equal | ||
| 160 | (car (eshell-complete-parse-arguments)) | ||
| 161 | `("echo" ,(propertize | ||
| 162 | "\0" 'eshell-argument-stub 'named-command)))))) | ||
| 163 | |||
| 126 | (ert-deftest em-cmpl-test/file-completion/unique () | 164 | (ert-deftest em-cmpl-test/file-completion/unique () |
| 127 | "Test completion of file names when there's a unique result." | 165 | "Test completion of file names when there's a unique result." |
| 128 | (with-temp-eshell | 166 | (with-temp-eshell |
| @@ -150,14 +188,39 @@ ACTUAL and EXPECTED should both be lists of strings." | |||
| 150 | (forward-line -1) | 188 | (forward-line -1) |
| 151 | (should (looking-at "Complete, but not unique"))))))) | 189 | (should (looking-at "Complete, but not unique"))))))) |
| 152 | 190 | ||
| 191 | (ert-deftest em-cmpl-test/file-completion/glob () | ||
| 192 | "Test completion of file names using a glob." | ||
| 193 | (with-temp-eshell | ||
| 194 | (ert-with-temp-directory default-directory | ||
| 195 | (write-region nil nil (expand-file-name "file.txt")) | ||
| 196 | (write-region nil nil (expand-file-name "file.el")) | ||
| 197 | (should (equal (eshell-insert-and-complete "echo fi*.el") | ||
| 198 | "echo file.el "))))) | ||
| 199 | |||
| 153 | (ert-deftest em-cmpl-test/file-completion/after-list () | 200 | (ert-deftest em-cmpl-test/file-completion/after-list () |
| 154 | "Test completion of file names after previous list arguments. | 201 | "Test completion of file names after previous list arguments. |
| 155 | See bug#59956." | 202 | See bug#59956." |
| 156 | (with-temp-eshell | 203 | (with-temp-eshell |
| 157 | (ert-with-temp-directory default-directory | 204 | (let ((eshell-test-value '("foo" "bar"))) |
| 158 | (write-region nil nil (expand-file-name "file.txt")) | 205 | (ert-with-temp-directory default-directory |
| 159 | (should (equal (eshell-insert-and-complete "echo (list 1 2) fi") | 206 | (write-region nil nil (expand-file-name "file.txt")) |
| 160 | "echo (list 1 2) file.txt "))))) | 207 | (should (equal (eshell-insert-and-complete "echo $eshell-test-value fi") |
| 208 | "echo $eshell-test-value file.txt ")))))) | ||
| 209 | |||
| 210 | (ert-deftest em-cmpl-test/command-completion () | ||
| 211 | "Test completion of command names like \"command\"." | ||
| 212 | (with-temp-eshell | ||
| 213 | (should (equal (eshell-insert-and-complete "listif") | ||
| 214 | "listify ")))) | ||
| 215 | |||
| 216 | (ert-deftest em-cmpl-test/subcommand-completion () | ||
| 217 | "Test completion of command names like \"{command}\"." | ||
| 218 | (with-temp-eshell | ||
| 219 | (should (equal (eshell-insert-and-complete "{ listif") | ||
| 220 | "{ listify "))) | ||
| 221 | (with-temp-eshell | ||
| 222 | (should (equal (eshell-insert-and-complete "echo ${ listif") | ||
| 223 | "echo ${ listify ")))) | ||
| 161 | 224 | ||
| 162 | (ert-deftest em-cmpl-test/lisp-symbol-completion () | 225 | (ert-deftest em-cmpl-test/lisp-symbol-completion () |
| 163 | "Test completion of Lisp forms like \"#'symbol\" and \"`symbol\". | 226 | "Test completion of Lisp forms like \"#'symbol\" and \"`symbol\". |
| @@ -174,7 +237,10 @@ See <lisp/eshell/esh-cmd.el>." | |||
| 174 | See <lisp/eshell/esh-cmd.el>." | 237 | See <lisp/eshell/esh-cmd.el>." |
| 175 | (with-temp-eshell | 238 | (with-temp-eshell |
| 176 | (should (equal (eshell-insert-and-complete "echo (eshell/ech") | 239 | (should (equal (eshell-insert-and-complete "echo (eshell/ech") |
| 177 | "echo (eshell/echo")))) | 240 | "echo (eshell/echo"))) |
| 241 | (with-temp-eshell | ||
| 242 | (should (equal (eshell-insert-and-complete "echo $(eshell/ech") | ||
| 243 | "echo $(eshell/echo")))) | ||
| 178 | 244 | ||
| 179 | (ert-deftest em-cmpl-test/special-ref-completion/type () | 245 | (ert-deftest em-cmpl-test/special-ref-completion/type () |
| 180 | "Test completion of the start of special references like \"#<buffer\". | 246 | "Test completion of the start of special references like \"#<buffer\". |