commit ab7c2f809219b0c29e7ee2b5ac66f18b0e657080 (HEAD, refs/remotes/origin/master) Author: Jim Porter Date: Sun Feb 5 21:37:08 2023 -0800 Ensure that Eshell users can run lines of command output as input Previously, this failed to work properly because any additional input the user entered would have no 'field' property, confusing 'eshell-get-old-input'. To fix this, we simply ensure that any user-entered text in the output field retains said output field (bug#61310). * lisp/eshell/esh-util.el (eshell-command-output-properties): New variable. (eshell--mark-as-output, eshell--mark-yanked-as-output): New functions, mostly copied from comint. * lisp/eshell/esh-proc.el (eshell-interactive-process-filter): * lisp/eshell/esh-mode.el (eshell-interactive-print): Call 'eshell--mark-as-output'. (eshell-get-old-input): Remove properties from the returned string just to be safe. * test/lisp/eshell/eshell-tests.el (eshell-test-value): New variable. (eshell-test/get-old-input/rerun-command) (eshell-test/get-old-input/run-output): New tests. * test/lisp/eshell/em-prompt-tests.el (em-prompt-test/field-properties) (em-prompt-test/field-properties/no-highlight): Use 'eshell-command-output-properties'. diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el index 503d9ba1b63..654e26777e0 100644 --- a/lisp/eshell/esh-mode.el +++ b/lisp/eshell/esh-mode.el @@ -525,9 +525,7 @@ eshell-goto-input-start (defun eshell-interactive-print (string) "Print STRING to the eshell display buffer." (when string - (add-text-properties 0 (length string) - '(field command-output rear-nonsticky (field)) - string) + (eshell--mark-as-output 0 (length string) string) (eshell-interactive-filter nil string))) (defsubst eshell-begin-on-new-line () @@ -891,7 +889,7 @@ eshell-get-old-input (let ((inhibit-field-text-motion) (end (point))) (beginning-of-line) - (buffer-substring (point) end))))) + (buffer-substring-no-properties (point) end))))) (defun eshell-copy-old-input () "Insert after prompt old input at point as new input to be edited." diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el index 27cd521e82e..a86e7502795 100644 --- a/lisp/eshell/esh-proc.el +++ b/lisp/eshell/esh-proc.el @@ -24,6 +24,7 @@ ;;; Code: (require 'esh-io) +(require 'esh-util) (defgroup eshell-proc nil "When Eshell invokes external commands, it always does so @@ -411,9 +412,7 @@ eshell-interactive-process-filter "Send the output from PROCESS (STRING) to the interactive display. This is done after all necessary filtering has been done." (when string - (add-text-properties 0 (length string) - '(field command-output rear-nonsticky (field)) - string) + (eshell--mark-as-output 0 (length string) string) (require 'esh-mode) (declare-function eshell-interactive-filter "esh-mode" (buffer string)) (eshell-interactive-filter (if process (process-buffer process) diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el index 9549e7f1a10..c0685757789 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el @@ -132,6 +132,19 @@ eshell-user-names (defvar eshell-user-timestamp nil "A timestamp of when the user file was read.") +(defvar eshell-command-output-properties + `( field command-output + front-sticky (field) + rear-nonsticky (field) + ;; Text inserted by a user in the middle of process output + ;; should be marked as output. This is needed for commands + ;; such as `yank' or `just-one-space' which don't use + ;; `insert-and-inherit' and thus bypass default text property + ;; inheritance. + insert-in-front-hooks (,#'eshell--mark-as-output + ,#'eshell--mark-yanked-as-output)) + "A list of text properties to apply to command output.") + ;;; Obsolete variables: (define-obsolete-variable-alias 'eshell-host-names @@ -157,6 +170,27 @@ eshell-condition-case ,@handlers) form)) +(defun eshell--mark-as-output (start end &optional object) + "Mark the text from START to END as Eshell output. +OBJECT can be a buffer or string. If nil, mark the text in the +current buffer." + (with-silent-modifications + (add-text-properties start end eshell-command-output-properties + object))) + +(defun eshell--mark-yanked-as-output (start end) + "Mark yanked text from START to END as Eshell output." + ;; `yank' removes the field text property from the text it inserts + ;; due to `yank-excluded-properties', so arrange for this text + ;; property to be reapplied in the `after-change-functions'. + (letrec ((hook + (lambda (start1 end1 _len1) + (remove-hook 'after-change-functions hook t) + (when (and (= start start1) + (= end end1)) + (eshell--mark-as-output start1 end1))))) + (add-hook 'after-change-functions hook nil t))) + (defun eshell-find-delimiter (open close &optional bound reverse-p backslash-p) "From point, find the CLOSE delimiter corresponding to OPEN. diff --git a/test/lisp/eshell/em-prompt-tests.el b/test/lisp/eshell/em-prompt-tests.el index db45e2ae3a7..257549e40fb 100644 --- a/test/lisp/eshell/em-prompt-tests.el +++ b/test/lisp/eshell/em-prompt-tests.el @@ -54,8 +54,8 @@ em-prompt-test/field-properties (should (equal last-input "echo hello\n")) (should (equal-including-properties last-output - (propertize "hello\n" 'rear-nonsticky '(field) - 'field 'command-output)))))) + (apply #'propertize "hello\n" + eshell-command-output-properties)))))) (ert-deftest em-prompt-test/field-properties/no-highlight () "Check that field properties are properly set on Eshell output/prompts. @@ -77,8 +77,8 @@ em-prompt-test/field-properties/no-highlight (should (equal last-input "echo hello\n")) (should (equal-including-properties last-output - (propertize "hello\n" 'rear-nonsticky '(field) - 'field 'command-output))))))) + (apply #'propertize "hello\n" + eshell-command-output-properties))))))) (ert-deftest em-prompt-test/next-previous-prompt () "Check that navigating forward/backward through old prompts works correctly." diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el index 776cfb9b92f..743cc28b9b5 100644 --- a/test/lisp/eshell/eshell-tests.el +++ b/test/lisp/eshell/eshell-tests.el @@ -34,6 +34,8 @@ (file-name-directory (or load-file-name default-directory)))) +(defvar eshell-test-value nil) + ;;; Tests: (ert-deftest eshell-test/pipe-headproc () @@ -160,6 +162,32 @@ eshell-test/get-old-input (beginning-of-line)) (should (string= (eshell-get-old-input) "echo alpha")))) +(ert-deftest eshell-test/get-old-input/rerun-command () + "Test that we can rerun an old command when point is on it." + (with-temp-eshell + (let ((eshell-test-value "first")) + (eshell-match-command-output "echo $eshell-test-value" "first")) + ;; Go to the previous prompt. + (forward-line -2) + (let ((inhibit-field-text-motion t)) + (end-of-line)) + ;; Rerun the command, but with a different variable value. + (let ((eshell-test-value "second")) + (eshell-send-input)) + (eshell-match-output "second"))) + +(ert-deftest eshell-test/get-old-input/run-output () + "Test that we can run a line of output as a command when point is on it." + (with-temp-eshell + (eshell-match-command-output "echo \"echo there\"" "echo there") + ;; Go to the output, and insert "hello" after "echo". + (forward-line -1) + (forward-word) + (insert " hello") + ;; Run the line as a command. + (eshell-send-input) + (eshell-match-output "(\"hello\" \"there\")"))) + (provide 'eshell-tests) ;;; eshell-tests.el ends here commit c53255f67758cbd528c3422e248c0cb979a9a676 Author: Jim Porter Date: Tue Jan 24 17:14:54 2023 -0800 Ensure that deferred commands don't make Eshell forget let-bound values * lisp/eshell/esh-cmd.el (Command evaluation macros): Expand this documentation to list allowed special forms and caveats for working with 'if' and 'while'. (eshell-do-eval): Provide more detail in docstring. Handle 'eshell-defer' inside 'let' forms. * test/lisp/eshell/esh-cmd-tests.el (esh-cmd-test/let-rebinds-after-defer): New test (bug#59469). diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index b5f1d60ff18..efc46f10c96 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -741,18 +741,24 @@ eshell-separate-commands ;; The structure of the following macros is very important to ;; `eshell-do-eval' [Iterative evaluation]: ;; -;; @ Don't use forms that conditionally evaluate their arguments, such -;; as `setq', `if', `while', `let*', etc. The only special forms -;; that can be used are `let', `condition-case' and -;; `unwind-protect'. +;; @ Don't use special forms that conditionally evaluate their +;; arguments, such as `let*', unless Eshell explicitly supports +;; them. Eshell supports the following special forms: `catch', +;; `condition-case', `if', `let', `prog1', `progn', `quote', `setq', +;; `unwind-protect', and `while'. ;; -;; @ The main body of a `let' can contain only one form. Use `progn' -;; if necessary. +;; @ When using `if' or `while', first let-bind `eshell-test-body' and +;; `eshell-command-body' to '(nil). Eshell uses these variables to +;; handle conditional evaluation. ;; ;; @ The two `special' variables are `eshell-current-handles' and ;; `eshell-current-subjob-p'. Bind them locally with a `let' if you ;; need to change them. Change them directly only if your intention ;; is to change the calling environment. +;; +;; These rules likewise apply to any other code that generates forms +;; that `eshell-do-eval' will evaluated, such as command rewriting +;; hooks (see `eshell-rewrite-command-hook' and friends). (defmacro eshell-do-subjob (object) "Evaluate a command OBJECT as a subjob. @@ -1095,9 +1101,17 @@ eshell-manipulate (eshell-debug-command ,(concat "done " (eval tag)) form)))) (defun eshell-do-eval (form &optional synchronous-p) - "Evaluate form, simplifying it as we go. + "Evaluate FORM, simplifying it as we go. Unless SYNCHRONOUS-P is non-nil, throws `eshell-defer' if it needs to -be finished later after the completion of an asynchronous subprocess." +be finished later after the completion of an asynchronous subprocess. + +As this function evaluates FORM, it will gradually replace +subforms with the (quoted) result of evaluating them. For +example, a function call is replaced with the result of the call. +This allows us to resume evaluation of FORM after something +inside throws `eshell-defer' simply by calling this function +again. Any forms preceding one that throw `eshell-defer' will +have been replaced by constants." (cond ((not (listp form)) (list 'quote (eval form))) @@ -1161,21 +1175,48 @@ eshell-do-eval (setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p)) (eval form)) ((eq (car form) 'let) - (if (not (eq (car (cadr args)) 'eshell-do-eval)) - (eshell-manipulate "evaluating let args" - (dolist (letarg (car args)) - (if (and (listp letarg) - (not (eq (cadr letarg) 'quote))) - (setcdr letarg - (list (eshell-do-eval - (cadr letarg) synchronous-p))))))) + (when (not (eq (car (cadr args)) 'eshell-do-eval)) + (eshell-manipulate "evaluating let args" + (dolist (letarg (car args)) + (when (and (listp letarg) + (not (eq (cadr letarg) 'quote))) + (setcdr letarg + (list (eshell-do-eval + (cadr letarg) synchronous-p))))))) (cl-progv - (mapcar (lambda (binding) (if (consp binding) (car binding) binding)) + (mapcar (lambda (binding) + (if (consp binding) (car binding) binding)) (car args)) ;; These expressions should all be constants now. - (mapcar (lambda (binding) (if (consp binding) (eval (cadr binding)))) + (mapcar (lambda (binding) + (when (consp binding) (eval (cadr binding)))) (car args)) - (eshell-do-eval (macroexp-progn (cdr args)) synchronous-p))) + (let (deferred result) + ;; Evaluate the `let' body, catching `eshell-defer' so we + ;; can handle it below. + (setq deferred + (catch 'eshell-defer + (ignore (setq result (eshell-do-eval + (macroexp-progn (cdr args)) + synchronous-p))))) + ;; If something threw `eshell-defer', we need to update + ;; the let-bindings' values so that those values are + ;; correct when we resume evaluation of this form. + (when deferred + (eshell-manipulate "rebinding let args after `eshell-defer'" + (let ((bindings (car args))) + (while bindings + (let ((binding (if (consp (car bindings)) + (caar bindings) + (car bindings)))) + (setcar bindings + (list binding + (list 'quote (symbol-value binding))))) + (pop bindings)))) + (throw 'eshell-defer deferred)) + ;; If we get here, there was no `eshell-defer' thrown, so + ;; just return the `let' body's result. + result))) ((memq (car form) '(catch condition-case unwind-protect)) ;; `condition-case' and `unwind-protect' have to be ;; handled specially, because we only want to call diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el index bcecc9a531f..94763954622 100644 --- a/test/lisp/eshell/esh-cmd-tests.el +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -73,6 +73,23 @@ esh-cmd-test/subcommand-lisp e.g. \"{(+ 1 2)} 3\" => 3" (eshell-command-result-equal "{(+ 1 2)} 3" 3)) +(ert-deftest esh-cmd-test/let-rebinds-after-defer () + "Test that let-bound values are properly updated after `eshell-defer'. +When inside a `let' block in an Eshell command form, we need to +ensure that deferred commands update any let-bound variables so +they have the correct values when resuming evaluation. See +bug#59469." + (skip-unless (executable-find "echo")) + (with-temp-eshell + (eshell-match-command-output + (concat "{" + " export LOCAL=value; " + " echo \"$LOCAL\"; " + " *echo external; " ; This will throw `eshell-defer'. + " echo \"$LOCAL\"; " + "}") + "value\nexternal\nvalue\n"))) + ;; Lisp forms commit d6d25a3c221e566de4df5319181e9ba9a8df285e Author: Dmitry Gutov Date: Thu Feb 9 21:57:18 2023 +0200 xref--insert-xrefs: Use 'shadow' for the line number colon * lisp/progmodes/xref.el (xref--insert-xrefs): Use face 'shadow' for the line number colon instead of continuing it face (bug#61340). diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 63e065e696e..581eda0513e 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el @@ -1126,7 +1126,9 @@ xref--insert-xrefs maximize (xref-location-line (xref-item-location xref))) for line-format = (and max-line - (format "%%%dd:" (1+ (floor (log max-line 10))))) + (format + #("%%%dd:" 0 4 (face xref-line-number) 5 6 (face shadow)) + (1+ (floor (log max-line 10))))) with item-text-props = (list 'mouse-face 'highlight 'keymap xref--button-map 'help-echo @@ -1146,8 +1148,7 @@ xref--insert-xrefs ((and (equal line prev-line) (equal prev-group group)) "") - (t (propertize (format line-format line) - 'face 'xref-line-number))))) + (t (format line-format line))))) ;; Render multiple matches on the same line, together. (when (and (equal prev-group group) (or (null line) commit 8784b9e817e434a13aba68006732d65962dec128 Author: Juri Linkov Date: Thu Feb 9 20:08:15 2023 +0200 * lisp/icomplete.el (icomplete-exhibit): Check validity of the current buffer. This function is applicable only in buffers where functions like icomplete--field-beg can be called, and they expects either non-nil completion-in-region--data or window-minibuffer-p (bug#61308). diff --git a/lisp/icomplete.el b/lisp/icomplete.el index 014f38b2024..f7a91599f3b 100644 --- a/lisp/icomplete.el +++ b/lisp/icomplete.el @@ -686,11 +686,13 @@ icomplete-exhibit Should be run via minibuffer `post-command-hook'. See `icomplete-mode' and `minibuffer-setup-hook'." (when (and icomplete-mode + ;; Check if still in the right buffer (bug#61308) + (or (window-minibuffer-p) completion-in-region--data) (icomplete-simple-completing-p)) ;Shouldn't be necessary. (let ((saved-point (point))) (save-excursion (goto-char (icomplete--field-end)) - ; Insert the match-status information: + ;; Insert the match-status information: (when (and (or icomplete-show-matches-on-no-input (not (equal (icomplete--field-string) icomplete--initial-input)))