commit fada04cfc788a486265c9da6636611986b48ae49 (HEAD, refs/remotes/origin/master) Author: Jim Porter Date: Fri Oct 4 22:26:01 2024 -0700 Add support for chaining conditionals in Eshell * lisp/eshell/esh-cmd.el (eshell-structure-basic-command): Check for the presence of the conditional. Allow any number of BODY forms. (eshell-rewrite-if-command): Add support for 'else' keyword and chained conditionals. * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/if-else-statement): Test 'else' keyword. (esh-cmd-test/if-else-statement-chain): New test. * doc/misc/eshell.texi (Control Flow): Document this change. * etc/NEWS: Announce this change. diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index bbb6b2e6aac..9a2714b14fb 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1698,16 +1698,29 @@ satisfied if the subcommand's exit status is 0. @table @code @item if @var{conditional} @var{true-subcommand} -@itemx if @var{conditional} @var{true-subcommand} @var{false-subcommand} +@itemx if @var{conditional} @var{true-subcommand} else @var{false-subcommand} Evaluate @var{true-subcommand} if @var{conditional} is satisfied; otherwise, evaluate @var{false-subcommand}. Both @var{true-subcommand} and @var{false-subcommand} should be subcommands, as with @var{conditional}. +You can also chain together @code{if}/@code{else} forms, for example: + +@example +if @{[ -f file.txt ]@} @{ + echo found file +@} else if @{[ -f alternate.txt ]@} @{ + echo found alternate +@} else @{ + echo not found! +@} +@end example + @item unless @var{conditional} @var{false-subcommand} -@itemx unless @var{conditional} @var{false-subcommand} @var{true-subcommand} +@itemx unless @var{conditional} @var{false-subcommand} else @var{true-subcommand} Evaluate @var{false-subcommand} if @var{conditional} is not satisfied; -otherwise, evaluate @var{true-subcommand}. +otherwise, evaluate @var{true-subcommand}. Like above, you can also +chain together @code{unless}/@code{else} forms. @item while @var{conditional} @var{subcommand} Repeatedly evaluate @var{subcommand} so long as @var{conditional} is diff --git a/etc/NEWS b/etc/NEWS index 4346fb4aedd..f9ba659ed86 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -257,6 +257,20 @@ These functions now take an optional ERROR-TARGET argument to control where to send the standard error output. See the "(eshell) Entry Points" node in the Eshell manual for more details. ++++ +*** Conditional statements in Eshell now use an 'else' keyword. +Eshell now prefers the following form when writing conditionals: + + if {conditional} {true-subcommand} else {false-subcommand} + +The old form (without the 'else' keyword) is retained for compatibility. + ++++ +*** You can now chain conditional statements in Eshell. +When using the newly-preferred conditional form in Eshell, you can now +chain together multiple 'if'/'else' statements. For more information, +see "(eshell) Control Flow" in the Eshell manual. + +++ *** Eshell's built-in 'wait' command now accepts a timeout. By passing '-t' or '--timeout', you can specify a maximum time to wait diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 65f997e5b88..c9096b0d159 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -551,12 +551,14 @@ implemented via rewriting, rather than as a function." ,body) (setq ,for-items (cdr ,for-items))))))) -(defun eshell-structure-basic-command (func names keyword test body - &optional else) +(defun eshell-structure-basic-command (func names keyword test &rest body) "With TERMS, KEYWORD, and two NAMES, structure a basic command. The first of NAMES should be the positive form, and the second the negative. It's not likely that users should ever need to call this function." + (unless test + (error "Missing test for `%s' command" keyword)) + ;; If the test form is a subcommand, wrap it in `eshell-commands' to ;; silence the output. (when (memq (car test) '(eshell-as-subcommand eshell-lisp-command)) @@ -582,33 +584,39 @@ function." (setq test `(not ,test))) ;; Finally, create the form that represents this structured command. - `(,func ,test ,body ,else)) + `(,func ,test ,@body)) (defun eshell-rewrite-while-command (terms) "Rewrite a `while' command into its equivalent Eshell command form. Because the implementation of `while' relies upon conditional evaluation of its argument (i.e., use of a Lisp special form), it must be implemented via rewriting, rather than as a function." - (if (and (stringp (car terms)) - (member (car terms) '("while" "until"))) - (eshell-structure-basic-command - 'while '("while" "until") (car terms) - (cadr terms) - (car (last terms))))) + (when (and (stringp (car terms)) + (member (car terms) '("while" "until"))) + (eshell-structure-basic-command + 'while '("while" "until") (car terms) + (cadr terms) + (caddr terms)))) (defun eshell-rewrite-if-command (terms) "Rewrite an `if' command into its equivalent Eshell command form. Because the implementation of `if' relies upon conditional evaluation of its argument (i.e., use of a Lisp special form), it must be implemented via rewriting, rather than as a function." - (if (and (stringp (car terms)) - (member (car terms) '("if" "unless"))) - (eshell-structure-basic-command - 'if '("if" "unless") (car terms) - (cadr terms) - (car (last terms (if (= (length terms) 4) 2))) - (when (= (length terms) 4) - (car (last terms)))))) + (when (and (stringp (car terms)) + (member (car terms) '("if" "unless"))) + (eshell-structure-basic-command + 'if '("if" "unless") (car terms) + (cadr terms) + (caddr terms) + (if (equal (nth 3 terms) "else") + ;; If there's an "else" keyword, allow chaining together + ;; multiple "if" forms... + (or (eshell-rewrite-if-command (nthcdr 4 terms)) + (nth 4 terms)) + ;; ... otherwise, only allow a single "else" block (without the + ;; keyword) as before for compatibility. + (nth 3 terms))))) (defun eshell-set-exit-info (status &optional result) "Set the exit status and result for the last command. diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index 9e4cbc58201..0f388a9eba4 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -427,11 +427,15 @@ processes correctly." (ert-deftest esh-cmd-test/if-else-statement () "Test invocation of an if/else statement." (let ((eshell-test-value t)) - (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}" - "yes")) + (eshell-command-result-equal + "if $eshell-test-value {echo yes} {echo no}" "yes") + (eshell-command-result-equal + "if $eshell-test-value {echo yes} else {echo no}" "yes")) (let ((eshell-test-value nil)) - (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}" - "no"))) + (eshell-command-result-equal + "if $eshell-test-value {echo yes} {echo no}" "no") + (eshell-command-result-equal + "if $eshell-test-value {echo yes} else {echo no}" "no"))) (ert-deftest esh-cmd-test/if-else-statement-lisp-form () "Test invocation of an if/else statement using a Lisp form." @@ -474,6 +478,16 @@ This tests when `eshell-lisp-form-nil-is-failure' is nil." (eshell-command-result-equal "if {[ foo = bar ]} {echo yes} {echo no}" "no")) +(ert-deftest esh-cmd-test/if-else-statement-chain () + "Test invocation of a chained if/else statement." + (dolist (case '((1 . "one") (2 . "two") (3 . "other"))) + (let ((eshell-test-value (car case))) + (eshell-command-result-equal + (concat "if (= eshell-test-value 1) {echo one} " + "else if (= eshell-test-value 2) {echo two} " + "else {echo other}") + (cdr case))))) + (ert-deftest esh-cmd-test/if-statement-pipe () "Test invocation of an if statement piped to another command." (skip-unless (executable-find "rev")) commit 40ffacb34b194aa82273539ab7a5be2f485a706f Author: Jim Porter Date: Fri Oct 4 21:45:04 2024 -0700 Improve correctness of Eshell sub-forms This makes sure that we treat Eshell sub-forms (whether Lisp or command forms) as values when appropriate, or as regular invocations. This requires a bit more explicit work, but helps to resolve some of the surprising differences between Lisp and command forms in complex Eshell statements. * lisp/eshell/esh-cmd.el (eshell-subcommand-arg-values): Make obsolete. (eshell-parse-lisp-argument): Don't add 'eshell-command-to-value' here. (eshell-rewrite-sexp-command): Don't check for 'eshell-command-to-value here'; instead check for 'eshell-lisp-command'. (eshell-structure-basic-command): Check for 'eshell-lisp-command'. (eshell-term-as-value): New function... (eshell-rewrite-named-command, eshell-rewrite-for-command): ... call it. * lisp/eshell/esh-arg.el (eshell-parse-special-reference): * lisp/eshell/esh-io.el (eshell-strip-redirections): * lisp/eshell/esh-var.el (eshell-prepare-indices): Call 'eshell-term-as-value'. * test/lisp/eshell/esh-arg-tests.el (esh-arg-test/special-reference/command-form): * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/for-loop-lisp-body) (esh-cmd-test/while-loop-lisp-body) (esh-cmd-test/if-else-statement-lisp-body): New tests. * test/lisp/eshell/esh-var-tests.el (esh-var-test/interp-var-indices-subcommand): Add another command to test. * doc/misc/eshell.texi (Control Flow): Update documentation. diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index f8abb3d860a..bbb6b2e6aac 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1689,36 +1689,39 @@ convenience. Most of Eshell's control flow statements accept a @var{conditional}. This can take a few different forms. If @var{conditional} is a dollar -expansion, the condition is satisfied if the result is a -non-@code{nil} value. If @var{conditional} is a @samp{@{ -@var{subcommand} @}} or @samp{(@var{lisp form})}, the condition is -satisfied if the command's exit status is 0. +expansion, the condition is satisfied if the result is a non-@code{nil} +value. Alternately, @var{conditional} may be a subcommand, either in +command form, e.g.@: @samp{@{@var{subcommand}@}}; or in Lisp form, +e.g.@: @samp{(@var{lisp form})}. In that case, the condition is +satisfied if the subcommand's exit status is 0. @table @code -@item if @var{conditional} @{ @var{true-commands} @} -@itemx if @var{conditional} @{ @var{true-commands} @} @{ @var{false-commands} @} -Evaluate @var{true-commands} if @var{conditional} is satisfied; -otherwise, evaluate @var{false-commands}. +@item if @var{conditional} @var{true-subcommand} +@itemx if @var{conditional} @var{true-subcommand} @var{false-subcommand} +Evaluate @var{true-subcommand} if @var{conditional} is satisfied; +otherwise, evaluate @var{false-subcommand}. Both @var{true-subcommand} +and @var{false-subcommand} should be subcommands, as with +@var{conditional}. -@item unless @var{conditional} @{ @var{false-commands} @} -@itemx unless @var{conditional} @{ @var{false-commands} @} @{ @var{true-commands} @} -Evaluate @var{false-commands} if @var{conditional} is not satisfied; -otherwise, evaluate @var{true-commands}. +@item unless @var{conditional} @var{false-subcommand} +@itemx unless @var{conditional} @var{false-subcommand} @var{true-subcommand} +Evaluate @var{false-subcommand} if @var{conditional} is not satisfied; +otherwise, evaluate @var{true-subcommand}. -@item while @var{conditional} @{ @var{commands} @} -Repeatedly evaluate @var{commands} so long as @var{conditional} is +@item while @var{conditional} @var{subcommand} +Repeatedly evaluate @var{subcommand} so long as @var{conditional} is satisfied. -@item until @var{conditional} @{ @var{commands} @} -Repeatedly evaluate @var{commands} until @var{conditional} is +@item until @var{conditional} @var{subcommand} +Repeatedly evaluate @var{subcommand} until @var{conditional} is satisfied. -@item for @var{var} in @var{list}@dots{} @{ @var{commands} @} +@item for @var{var} in @var{list}@dots{} @var{subcommand} Iterate over each element of @var{list}, storing the element in -@var{var} and evaluating @var{commands}. If @var{list} is not a list, -treat it as a list of one element. If you specify multiple -@var{lists}, this will iterate over each of them in turn. +@var{var} and evaluating @var{subcommand}. If @var{list} is not a list, +treat it as a list of one element. If you specify multiple @var{lists}, +this will iterate over each of them in turn. @end table diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el index 6fc700cce89..b441cbfc274 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -35,6 +35,8 @@ (eval-when-compile (require 'cl-lib)) +(declare-function eshell-term-as-value "esh-cmd" (term)) + (defgroup eshell-arg nil "Argument parsing involves transforming the arguments passed on the command line into equivalent Lisp forms that, when evaluated, will @@ -626,7 +628,8 @@ If the form has no `type', the syntax is parsed as if `type' were (prog1 (cons creation-fun (let ((eshell-current-argument-plain t)) - (eshell-parse-arguments (point) end))) + (mapcar #'eshell-term-as-value + (eshell-parse-arguments (point) end)))) (goto-char (1+ end))) (ignore (goto-char here))))))) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 2a299125f22..65f997e5b88 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -454,6 +454,7 @@ command hooks should be run before and after the command." (defun eshell-subcommand-arg-values (terms) "Convert subcommand arguments {x} to ${x}, in order to take their values." + (declare (obsolete nil "31.1")) (setq terms (cdr terms)) ; skip command argument (while terms (if (and (listp (car terms)) @@ -465,9 +466,9 @@ command hooks should be run before and after the command." (defun eshell-rewrite-sexp-command (terms) "Rewrite a sexp in initial position, such as `(+ 1 2)'." ;; this occurs when a Lisp expression is in first position - (if (and (listp (car terms)) - (eq (caar terms) 'eshell-command-to-value)) - (car (cdar terms)))) + (when (and (listp (car terms)) + (eq (caar terms) 'eshell-lisp-command)) + (car terms))) (defun eshell-rewrite-initial-subcommand (terms) "Rewrite a subcommand in initial position, such as `{+ 1 2}'." @@ -477,20 +478,23 @@ command hooks should be run before and after the command." (defun eshell-rewrite-named-command (terms) "If no other rewriting rule transforms TERMS, assume a named command." - (eshell-subcommand-arg-values terms) - (let ((sym (if eshell-in-pipeline-p - 'eshell-named-command* - 'eshell-named-command)) - (grouped-terms (eshell-prepare-splice terms))) - (cond - (grouped-terms - `(let ((terms (nconc ,@grouped-terms))) - (,sym (car terms) (cdr terms)))) - ;; If no terms are spliced, use a simpler command form. - ((cdr terms) - (list sym (car terms) `(list ,@(cdr terms)))) - (t - (list sym (car terms)))))) + (when terms + (setq terms (cons (car terms) + ;; Convert arguments to take their values. + (mapcar #'eshell-term-as-value (cdr terms)))) + (let ((sym (if eshell-in-pipeline-p + 'eshell-named-command* + 'eshell-named-command)) + (grouped-terms (eshell-prepare-splice terms))) + (cond + (grouped-terms + `(let ((new-terms (nconc ,@grouped-terms))) + (,sym (car new-terms) (cdr new-terms)))) + ;; If no terms are spliced, use a simpler command form. + ((cdr terms) + (list sym (car terms) `(list ,@(cdr terms)))) + (t + (list sym (car terms))))))) (defvar eshell--command-body) (defvar eshell--test-body) @@ -537,7 +541,7 @@ implemented via rewriting, rather than as a function." ,@(mapcar (lambda (elem) (if (listp elem) - elem + (eshell-term-as-value elem) `(list ,elem))) (nthcdr 3 terms))))) (while ,for-items @@ -555,7 +559,7 @@ negative. It's not likely that users should ever need to call this function." ;; If the test form is a subcommand, wrap it in `eshell-commands' to ;; silence the output. - (when (eq (car test) 'eshell-as-subcommand) + (when (memq (car test) '(eshell-as-subcommand eshell-lisp-command)) (setq test `(eshell-commands ,test t))) ;; If the test form begins with `eshell-convert' or @@ -686,8 +690,7 @@ This means an exit code of 0." (end-of-file (throw 'eshell-incomplete "("))))) (if (eshell-arg-delimiter) - `(eshell-command-to-value - (eshell-lisp-command (quote ,obj))) + `(eshell-lisp-command (quote ,obj)) (ignore (goto-char here)))))) (defun eshell-split-commands (terms separator &optional @@ -912,6 +915,15 @@ This avoids the need to use `let*'." ,command ,value)))) +(defun eshell-term-as-value (term) + "Convert an Eshell TERM to take its value." + (cond + ((eq (car-safe term) 'eshell-as-subcommand) ; {x} -> ${x} + `(eshell-convert (eshell-command-to-value ,term))) + ((eq (car-safe term) 'eshell-lisp-command) ; (x) -> $(x) + `(eshell-command-to-value ,term)) + (t term))) + ;;;_* Iterative evaluation ;; ;; Eshell runs all of its external commands asynchronously, so that diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index feb4bf8959f..443c39ff0d1 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -75,6 +75,7 @@ (require 'cl-lib)) (declare-function eshell-interactive-print "esh-mode" (string)) +(declare-function eshell-term-as-value "esh-cmd" (term)) (defgroup eshell-io nil "Eshell's I/O management code provides a scheme for treating many @@ -301,8 +302,8 @@ describing the mode, e.g. for using with `eshell-get-target'.") (unless (cdr tt) (error "Missing redirection target")) (nconc eshell-current-redirections - (list (list 'ignore - (append (car tt) (list (cadr tt)))))) + `((ignore ,(append (car tt) + (list (eshell-term-as-value (cadr tt))))))) (setcdr tl (cddr tt)) (setq tt (cddr tt))) (t diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index d53ae997cdf..059bba03ee4 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -670,7 +670,9 @@ the original value of INDEX." (defun eshell-prepare-indices (indices) "Prepare INDICES to be evaluated by Eshell. INDICES is a list of index-lists generated by `eshell-parse-indices'." - `(list ,@(mapcar (lambda (idx-list) (cons 'list idx-list)) indices))) + `(list ,@(mapcar (lambda (idx-list) + (cons 'list (mapcar #'eshell-term-as-value idx-list))) + indices))) (defun eshell-get-variable (name &optional indices quoted) "Get the value for the variable NAME. diff --git a/test/lisp/eshell/esh-arg-tests.el b/test/lisp/eshell/esh-arg-tests.el index b748c5ab4c0..209c4fa8ea9 100644 --- a/test/lisp/eshell/esh-arg-tests.el +++ b/test/lisp/eshell/esh-arg-tests.el @@ -181,6 +181,19 @@ chars." "setq eshell-test-value #>") (should (equal eshell-test-value marker))))) +(ert-deftest esh-arg-test/special-reference/command-form () + "Test that command forms inside special references work." + (with-temp-eshell + (let ((marker (make-marker)) + eshell-test-value) + (set-marker marker 1 (current-buffer)) + (eshell-insert-command + "setq eshell-test-value #") + (should (equal eshell-test-value marker)) + (eshell-insert-command + "setq eshell-test-value #>") + (should (equal eshell-test-value marker))))) + (ert-deftest esh-arg-test/special-reference/special-characters () "Test that \"#<...>\" works correctly when escaping special characters." (with-temp-buffer diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index cac349a2616..9e4cbc58201 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -325,6 +325,12 @@ processes correctly." (eshell-match-command-output "for i in 1 { echo $for-items }" "hello\n"))) +(ert-deftest esh-cmd-test/for-loop-lisp-body () + "Test invocation of a for loop with a Lisp body form." + (with-temp-eshell + (eshell-match-command-output "for i in 1 2 3 (format \"%s\" i)" + "1\n2\n3\n"))) + (ert-deftest esh-cmd-test/for-loop-pipe () "Test invocation of a for loop piped to another command." (skip-unless (executable-find "rev")) @@ -350,6 +356,15 @@ processes correctly." "{ setq eshell-test-value (1+ eshell-test-value) }") "1\n2\n3\n")))) +(ert-deftest esh-cmd-test/while-loop-lisp-body () + "Test invocation of a while loop using a Lisp form for the body." + (with-temp-eshell + (let ((eshell-test-value 0)) + (eshell-match-command-output + (concat "while (/= eshell-test-value 3) " + "(setq eshell-test-value (1+ eshell-test-value))") + "1\n2\n3\n")))) + (ert-deftest esh-cmd-test/while-loop-ext-cmd () "Test invocation of a while loop using an external command." (skip-unless (executable-find "[")) @@ -440,6 +455,17 @@ This tests when `eshell-lisp-form-nil-is-failure' is nil." (eshell-command-result-equal "if (zerop \"foo\") {echo yes} {echo no}" "no")))) +(ert-deftest esh-cmd-test/if-else-statement-lisp-body () + "Test invocation of an if/else statement using Lisp forms for the bodies." + (eshell-command-result-equal "if (zerop 0) (format \"yes\") (format \"no\")" + "yes") + (eshell-command-result-equal "if (zerop 1) (format \"yes\") (format \"no\")" + "no") + (let ((debug-on-error nil)) + (eshell-command-result-equal + "if (zerop \"foo\") (format \"yes\") (format \"no\")" + "no"))) + (ert-deftest esh-cmd-test/if-else-statement-ext-cmd () "Test invocation of an if/else statement using an external command." (skip-unless (executable-find "[")) diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el index 7b29e4a21db..7ac9807a1a7 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -190,6 +190,9 @@ nil, use FUNCTION instead." "zero") (eshell-command-result-equal "echo $eshell-test-value[${*echo 0} ${*echo 2}]" + '("zero" "two")) + (eshell-command-result-equal + "echo $eshell-test-value[{*echo 0} {*echo 2}]" '("zero" "two")))) (ert-deftest esh-var-test/interp-var-length-list () commit af029cdb3a69ba67ae88c9e93b7564778c61a3c6 Author: Jim Porter Date: Tue Sep 17 22:05:50 2024 -0700 Simplify creation of Eshell command forms Previously, Eshell over-aggressively converted subcommands, which forced control flow commands to undo that change. * lisp/eshell/esh-cmd.el (eshell-pre-rewrite-command-hook): Remove 'eshell-subcommand-arg-values' call from here... (eshell-rewrite-named-command): ... and put it here. (eshell-invokify-arg): Make obsolete. (eshell-structure-basic-command): Handle subcommand test forms. (eshell-rewrite-for-command, eshell-rewrite-while-command) (eshell-rewrite-if-command): Don't call 'eshell-invokify-arg'; it's not necessary. diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index 09fc65522ad..2a299125f22 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -181,8 +181,7 @@ describing where Eshell will find the function." :type 'hook) (defcustom eshell-pre-rewrite-command-hook - '(eshell-no-command-conversion - eshell-subcommand-arg-values) + '(eshell-no-command-conversion) "A hook run before command rewriting begins. The terms of the command to be rewritten is passed as arguments, and may be modified in place. Any return value is ignored." @@ -478,6 +477,7 @@ command hooks should be run before and after the command." (defun eshell-rewrite-named-command (terms) "If no other rewriting rule transforms TERMS, assume a named command." + (eshell-subcommand-arg-values terms) (let ((sym (if eshell-in-pipeline-p 'eshell-named-command* 'eshell-named-command)) @@ -503,6 +503,7 @@ current output stream, which is separately redirectable. SILENT means the user and/or any redirections shouldn't see any output from this command. If both SHARE-OUTPUT and SILENT are non-nil, the second is ignored." + (declare (obsolete nil "31.1")) ;; something that begins with `eshell-convert' means that it ;; intends to return a Lisp value. We want to get past this, ;; but if it's not _actually_ a value interpolation -- in which @@ -543,7 +544,7 @@ implemented via rewriting, rather than as a function." (let ((,(intern (cadr terms)) (car ,for-items)) (eshell--local-vars (cons ',(intern (cadr terms)) eshell--local-vars))) - ,(eshell-invokify-arg body t)) + ,body) (setq ,for-items (cdr ,for-items))))))) (defun eshell-structure-basic-command (func names keyword test body @@ -552,6 +553,11 @@ implemented via rewriting, rather than as a function." The first of NAMES should be the positive form, and the second the negative. It's not likely that users should ever need to call this function." + ;; If the test form is a subcommand, wrap it in `eshell-commands' to + ;; silence the output. + (when (eq (car test) 'eshell-as-subcommand) + (setq test `(eshell-commands ,test t))) + ;; If the test form begins with `eshell-convert' or ;; `eshell-escape-arg', it means something data-wise will be ;; returned, and we should let that determine the truth of the @@ -583,8 +589,8 @@ must be implemented via rewriting, rather than as a function." (member (car terms) '("while" "until"))) (eshell-structure-basic-command 'while '("while" "until") (car terms) - (eshell-invokify-arg (cadr terms) nil t) - (eshell-invokify-arg (car (last terms)) t)))) + (cadr terms) + (car (last terms))))) (defun eshell-rewrite-if-command (terms) "Rewrite an `if' command into its equivalent Eshell command form. @@ -595,10 +601,10 @@ must be implemented via rewriting, rather than as a function." (member (car terms) '("if" "unless"))) (eshell-structure-basic-command 'if '("if" "unless") (car terms) - (eshell-invokify-arg (cadr terms) nil t) - (eshell-invokify-arg (car (last terms (if (= (length terms) 4) 2))) t) + (cadr terms) + (car (last terms (if (= (length terms) 4) 2))) (when (= (length terms) 4) - (eshell-invokify-arg (car (last terms)) t))))) + (car (last terms)))))) (defun eshell-set-exit-info (status &optional result) "Set the exit status and result for the last command. commit 561ab9829dd000a8078c6c66f7e8c29056adf31c Author: Dmitry Gutov Date: Thu Oct 17 01:57:40 2024 +0300 New helper function project-read-project * lisp/progmodes/project.el (project-read-project): New helper. diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 18613e9ec33..3cdaa7c2a76 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -1958,6 +1958,12 @@ When PROMPT is non-nil, use it as the prompt string." (project--ensure-read-project-list) (mapcar #'car project--list)) +(defun project-read-project () + "Read a project with completion from the known list. +Returns an object that the API methods can be used with." + ;; Will prompt again if the entered directory is not a project anymore. + (project-current t (funcall project-prompter))) + ;;;###autoload (defun project-execute-extended-command () "Execute an extended command in project root." commit aaa734ac6e80e3375bcf32cad9c2b484c1217b57 Author: Michael R. Mauger Date: Wed Oct 16 17:59:58 2024 -0400 Add suffixes individually and use minor mode directly * lisp/jka-cmpr-hook.el (jka-compr-install): Add jka-compr suffixes individually with add-to-list rather than append. * (with-auto-compression-mode): Use auto-compression-mode minor mode. diff --git a/lisp/jka-cmpr-hook.el b/lisp/jka-cmpr-hook.el index ced998fafb6..7e502f02b3c 100644 --- a/lisp/jka-cmpr-hook.el +++ b/lisp/jka-cmpr-hook.el @@ -160,8 +160,8 @@ and `inhibit-local-variables-suffixes'." (append auto-mode-alist jka-compr-mode-alist-additions)) ;; Make sure that (load "foo") will find /bla/foo.el.gz. - (setq load-file-rep-suffixes - (append load-file-rep-suffixes jka-compr-load-suffixes nil))) + (dolist (suff jka-compr-load-suffixes load-file-rep-suffixes) + (add-to-list 'load-file-rep-suffixes suff t))) (defun jka-compr-installed-p () "Return non-nil if jka-compr is installed. @@ -379,14 +379,14 @@ compressed when writing." "Evaluate BODY with automatic file compression and uncompression enabled." (declare (indent 0)) (let ((already-installed (make-symbol "already-installed"))) - `(let ((,already-installed (jka-compr-installed-p))) + `(let ((,already-installed auto-compression-mode)) (unwind-protect (progn (unless ,already-installed - (jka-compr-install)) + (auto-compression-mode 1)) ,@body) (unless ,already-installed - (jka-compr-uninstall)))))) + (auto-compression-mode -1)))))) ;; This is what we need to know about jka-compr-handler ;; in order to decide when to call it. commit 6213ca44d43688e14641034ebdd8fab4b360d224 Author: Michael Albinus Date: Wed Oct 16 17:26:44 2024 +0200 Use debian:bookworm for tests on emba * test/infra/Dockerfile.emba (emacs-base): Derive from debian:bookworm. (emacs-eglot, emacs-tree-sitter): Derive from emacs-base. (emacs-native-comp): Install libgccjit-12-dev. diff --git a/test/infra/Dockerfile.emba b/test/infra/Dockerfile.emba index de32906212b..f5c79b91ba1 100644 --- a/test/infra/Dockerfile.emba +++ b/test/infra/Dockerfile.emba @@ -24,7 +24,7 @@ # Maintainer: Ted Zlatanov # URL: https://emba.gnu.org/emacs/emacs -FROM debian:bullseye as emacs-base +FROM debian:bookworm as emacs-base RUN apt-get update && \ apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ @@ -60,16 +60,7 @@ RUN ./autogen.sh autoconf RUN ./configure --with-file-notification=gfile RUN make -j `nproc` bootstrap -# Debian bullseye doesn't provide proper packages. So we use Debian -# sid for this. -FROM debian:sid as emacs-eglot - -# This corresponds to emacs-base. -RUN apt-get update && \ - apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ - libc-dev gcc g++ make autoconf automake libncurses-dev gnutls-dev \ - libxml2-dev libdbus-1-dev libacl1-dev acl git texinfo gdb \ - && rm -rf /var/lib/apt/lists/* +FROM emacs-base as emacs-eglot # Install clangd, tsserver. RUN apt-get update && \ @@ -112,16 +103,7 @@ RUN make -j `nproc` bootstrap # --eval '(package-install (quote company))' \ # --eval '(package-install (quote yasnippet))' -# Debian bullseye doesn't provide proper packages. So we use Debian -# sid for this. -FROM debian:sid as emacs-tree-sitter - -# This corresponds to emacs-base. -RUN apt-get update && \ - apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ - libc-dev gcc g++ make autoconf automake libncurses-dev gnutls-dev \ - libxml2-dev libdbus-1-dev libacl1-dev acl git texinfo gdb \ - && rm -rf /var/lib/apt/lists/* +FROM emacs-base as emacs-tree-sitter # Install tree-sitter library. RUN apt-get update && \ @@ -183,7 +165,7 @@ FROM emacs-base as emacs-native-comp # The libgccjit version must correspond to the gcc version. RUN apt-get update && \ apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ - libgccjit-10-dev zlib1g-dev \ + libgccjit-12-dev zlib1g-dev \ && rm -rf /var/lib/apt/lists/* FROM emacs-native-comp as emacs-native-comp-speed0 commit 5340fdaade1f8fe7af08293619cca89ae0796fcf Author: Alan Mackenzie Date: Wed Oct 16 13:17:26 2024 +0000 CC Mode: Fix dodgy lisp `let' form. * lisp/progmodes/cc-engine.el (c-forward-sws): Move a `setq' form from out of a let binding. This form could have been affected by the byte compiler bug bug#67116 before this was fixed. diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index 8fd2b2dc49c..c46cd54438b 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -2200,8 +2200,9 @@ comment at the start of cc-engine.el for more info." (c-put-is-sws (1+ rung-pos) (1+ (point))) (c-put-in-sws rung-pos - (setq rung-pos (point) - last-put-in-sws-pos rung-pos))) + (point)) + (setq rung-pos (point) + last-put-in-sws-pos rung-pos)) ;; Now move over any comments (x)or a CPP construct. (setq simple-ws-end (point))