Merge remote-tracking branch 'origin/master' into feature/android

This commit is contained in:
Po Lu 2023-02-10 19:09:37 +08:00
commit 338f4be900
9 changed files with 154 additions and 34 deletions

View file

@ -741,18 +741,24 @@ if none)."
;; 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 @@ produced by `eshell-parse-command'."
(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 @@ be finished later after the completion of an asynchronous subprocess."
(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

View file

@ -525,9 +525,7 @@ Putting this function on `eshell-pre-command-hook' will mimic Plan 9's
(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 @@ If USE-CURRENT-REGION is non-nil, return the current region."
(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."

View file

@ -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 @@ Used only on systems which do not support async subprocesses.")
"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)

View file

@ -132,6 +132,19 @@ function `string-to-number'.")
(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 @@ Otherwise, evaluates FORM with no error handling."
,@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.

View file

@ -686,11 +686,13 @@ If it's on, just add the vertical display."
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)))

View file

@ -1126,7 +1126,9 @@ GROUP is a string for decoration purposes and XREF is an
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 @@ GROUP is a string for decoration purposes and XREF is an
((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)

View file

@ -54,8 +54,8 @@
(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 @@ This tests the case when `eshell-highlight-prompt' is nil."
(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."

View file

@ -73,6 +73,23 @@ Test that trailing arguments outside the subcommand are ignored.
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

View file

@ -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 @@ insert the queued one at the next prompt, and finally run it."
(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