mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-24 07:20:37 +00:00
Treat escaped newlines in Eshell as the empty string
This fixes a regression introduced during Emacs 29's development. * lisp/eshell/esh-arg.el (eshell-parse-argument): Handle 'eshell-empty-token' as the result of an argument-parsing hook. (eshell-parse-argument-hook): Document 'eshell-empty-token'. (eshell-parse-backslash): Return 'eshell-empty-token' when encountering an escaped newline. * test/lisp/eshell/eshell-tests.el (eshell-test/escape-nonspecial) (eshell-test/escape-nonspecial-unicode) (eshell-test/escape-nonspecial-quoted) (eshell-test/escape-special-quoted): Move from here... * test/lisp/eshell/esh-arg-tests.el (esh-arg-test/escape/nonspecial) (esh-arg-test/escape/nonspecial-unicode) (esh-arg-test/escape-quoted/nonspecial) (esh-arg-test/escape-quoted/special): ... to here. (esh-arg-test/escape/special, esh-arg-test/escape/newline) (esh-arg-test/escape-quoted/newline): New tests. * doc/misc/eshell.texi (Arguments): Explain escaping logic in more detail (bug#59622).
This commit is contained in:
parent
c774e83e36
commit
a37df90276
@ -256,12 +256,40 @@ as an argument will ``spread'' the elements into multiple arguments:
|
||||
@end example
|
||||
|
||||
@subsection Quoting and escaping
|
||||
As with other shells, you can escape special characters and spaces
|
||||
with by prefixing the character with a backslash (@code{\}), or by
|
||||
surrounding the string with apostrophes (@code{''}) or double quotes
|
||||
(@code{""}). This is needed especially for file names with special
|
||||
characters like pipe (@code{|}), which could be part of remote file
|
||||
names.
|
||||
As with other shells, you can escape special characters and spaces by
|
||||
prefixing the character with a backslash (@samp{\}), or by surrounding
|
||||
the string with apostrophes (@samp{''}) or double quotes (@samp{""}).
|
||||
This is needed especially for file names with special characters like
|
||||
pipe (@samp{|}), which could be part of remote file names.
|
||||
|
||||
When you escape a character with @samp{\} outside of any quotes, the
|
||||
result is the literal character immediately following it. For
|
||||
example, @code{\$10} means the literal string @code{$10}.
|
||||
|
||||
Inside of double quotes, most characters have no special meaning.
|
||||
However, @samp{\}, @samp{"}, and @samp{$} are still special; to escape
|
||||
them, use backslash as above. Thus, if the value of the variable
|
||||
@var{answer} is @code{42}, then @code{"The answer is: \"$answer\""}
|
||||
returns the string @code{The answer is: "42"}. However, when escaping
|
||||
characters with no special meaning, the result is the full
|
||||
@code{\@var{c}} sequence. For example, @code{"foo\bar"} means the
|
||||
literal string @code{foo\bar}.
|
||||
|
||||
Additionally, when escaping a newline, the whole escape sequence is
|
||||
removed by the parser. This lets you continue commands across
|
||||
multiple lines:
|
||||
|
||||
@example
|
||||
~ $ echo "foo\
|
||||
bar"
|
||||
foobar
|
||||
@end example
|
||||
|
||||
Inside apostrophes, escaping works differently. All characters
|
||||
between the apostrophes have their literal meaning except @samp{'},
|
||||
which ends the quoted string. To insert a literal apostrophe, you can
|
||||
use @samp{''}, so @code{'It''s me'} means the literal string
|
||||
@code{It's me}.
|
||||
|
||||
When using expansions (@pxref{Expansion}) in an Eshell command, the
|
||||
result may potentially be of any data type. To ensure that the result
|
||||
|
@ -146,9 +146,10 @@ If POS is nil, the location of point is checked."
|
||||
When each function on this hook is called, point will be at the
|
||||
current position within the argument list. The function should either
|
||||
return nil, meaning that it did no argument parsing, or it should
|
||||
return the result of the parse as a sexp. It is also responsible for
|
||||
moving the point forward to reflect the amount of input text that was
|
||||
parsed.
|
||||
return the result of the parse as a sexp. If the function did do
|
||||
argument parsing, but the result was nothing at all, it should return
|
||||
`eshell-empty-token'. The function is also responsible for moving the
|
||||
point forward to reflect the amount of input text that was parsed.
|
||||
|
||||
If the hook determines that it has reached the end of an argument, it
|
||||
should call `eshell-finish-arg' to complete processing of the current
|
||||
@ -325,13 +326,14 @@ Point is left at the end of the arguments."
|
||||
(prog1
|
||||
(char-to-string (char-after))
|
||||
(forward-char)))))
|
||||
(if (not eshell-current-argument)
|
||||
(setq eshell-current-argument result)
|
||||
(unless eshell-arg-listified
|
||||
(setq eshell-current-argument
|
||||
(list eshell-current-argument)
|
||||
eshell-arg-listified t))
|
||||
(nconc eshell-current-argument (list result))))))
|
||||
(unless (eq result 'eshell-empty-token)
|
||||
(if (not eshell-current-argument)
|
||||
(setq eshell-current-argument result)
|
||||
(unless eshell-arg-listified
|
||||
(setq eshell-current-argument
|
||||
(list eshell-current-argument)
|
||||
eshell-arg-listified t))
|
||||
(nconc eshell-current-argument (list result)))))))
|
||||
(when (and outer eshell-current-argument)
|
||||
(add-text-properties arg-begin (1+ arg-begin)
|
||||
'(arg-begin t rear-nonsticky
|
||||
@ -375,15 +377,20 @@ after are both returned."
|
||||
(when (eshell-looking-at-backslash-return (point))
|
||||
(throw 'eshell-incomplete ?\\))
|
||||
(forward-char 2) ; Move one char past the backslash.
|
||||
;; If the char is in a quote, backslash only has special meaning
|
||||
;; if it is escaping a special char.
|
||||
(if eshell-current-quoted
|
||||
(if (memq (char-before) eshell-special-chars-inside-quoting)
|
||||
(if (eq (char-before) ?\n)
|
||||
;; Escaped newlines are extra-special: they expand to an empty
|
||||
;; token to allow for continuing Eshell commands across
|
||||
;; multiple lines.
|
||||
'eshell-empty-token
|
||||
;; If the char is in a quote, backslash only has special meaning
|
||||
;; if it is escaping a special char.
|
||||
(if eshell-current-quoted
|
||||
(if (memq (char-before) eshell-special-chars-inside-quoting)
|
||||
(list 'eshell-escape-arg (char-to-string (char-before)))
|
||||
(concat "\\" (char-to-string (char-before))))
|
||||
(if (memq (char-before) eshell-special-chars-outside-quoting)
|
||||
(list 'eshell-escape-arg (char-to-string (char-before)))
|
||||
(concat "\\" (char-to-string (char-before))))
|
||||
(if (memq (char-before) eshell-special-chars-outside-quoting)
|
||||
(list 'eshell-escape-arg (char-to-string (char-before)))
|
||||
(char-to-string (char-before))))))
|
||||
(char-to-string (char-before)))))))
|
||||
|
||||
(defun eshell-parse-literal-quote ()
|
||||
"Parse a literally quoted string. Nothing has special meaning!"
|
||||
|
105
test/lisp/eshell/esh-arg-tests.el
Normal file
105
test/lisp/eshell/esh-arg-tests.el
Normal file
@ -0,0 +1,105 @@
|
||||
;;; esh-arg-tests.el --- esh-arg test suite -*- lexical-binding:t -*-
|
||||
|
||||
;; Copyright (C) 2022 Free Software Foundation, Inc.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Tests for Eshell's argument handling.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'esh-mode)
|
||||
(require 'eshell)
|
||||
|
||||
(require 'eshell-tests-helpers
|
||||
(expand-file-name "eshell-tests-helpers"
|
||||
(file-name-directory (or load-file-name
|
||||
default-directory))))
|
||||
|
||||
(defvar eshell-test-value nil)
|
||||
|
||||
;;; Tests:
|
||||
|
||||
(ert-deftest esh-arg-test/escape/nonspecial ()
|
||||
"Test that \"\\c\" and \"c\" are equivalent when \"c\" is not a
|
||||
special character."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo he\\llo"
|
||||
"hello\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape/nonspecial-unicode ()
|
||||
"Test that \"\\c\" and \"c\" are equivalent when \"c\" is a
|
||||
unicode character (unicode characters are nonspecial by
|
||||
definition)."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo Vid\\éos"
|
||||
"Vidéos\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape/special ()
|
||||
"Test that the backslash is not preserved for escaped special
|
||||
chars."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo he\\\\llo"
|
||||
;; Backslashes are doubled for regexp.
|
||||
"he\\\\llo\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape/newline ()
|
||||
"Test that an escaped newline is equivalent to the empty string.
|
||||
When newlines are *nonspecial*, an escaped newline should be
|
||||
treated as just a newline."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo hi\\\nthere"
|
||||
"hithere\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape/newline-conditional ()
|
||||
"Test invocation of an if/else statement using line continuations."
|
||||
(let ((eshell-test-value t))
|
||||
(eshell-command-result-equal
|
||||
"if $eshell-test-value \\\n{echo yes} \\\n{echo no}"
|
||||
"yes"))
|
||||
(let ((eshell-test-value nil))
|
||||
(eshell-command-result-equal
|
||||
"if $eshell-test-value \\\n{echo yes} \\\n{echo no}"
|
||||
"no")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape-quoted/nonspecial ()
|
||||
"Test that the backslash is preserved for escaped nonspecial
|
||||
chars."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo \"h\\i\""
|
||||
;; Backslashes are doubled for regexp.
|
||||
"h\\\\i\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape-quoted/special ()
|
||||
"Test that the backslash is not preserved for escaped special
|
||||
chars."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo \"\\\"hi\\\\\""
|
||||
;; Backslashes are doubled for regexp.
|
||||
"\\\"hi\\\\\n")))
|
||||
|
||||
(ert-deftest esh-arg-test/escape-quoted/newline ()
|
||||
"Test that an escaped newline is equivalent to the empty string.
|
||||
When newlines are *nonspecial*, an escaped newline should be
|
||||
treated literally, as a backslash and a newline."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo \"hi\\\nthere\""
|
||||
"hithere\n")))
|
||||
|
||||
;; esh-arg-tests.el ends here
|
@ -105,37 +105,6 @@
|
||||
(format template "format \"%s\" eshell-in-pipeline-p")
|
||||
"nil")))
|
||||
|
||||
(ert-deftest eshell-test/escape-nonspecial ()
|
||||
"Test that \"\\c\" and \"c\" are equivalent when \"c\" is not a
|
||||
special character."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo he\\llo"
|
||||
"hello\n")))
|
||||
|
||||
(ert-deftest eshell-test/escape-nonspecial-unicode ()
|
||||
"Test that \"\\c\" and \"c\" are equivalent when \"c\" is a
|
||||
unicode character (unicode characters are nonspecial by
|
||||
definition)."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo Vid\\éos"
|
||||
"Vidéos\n")))
|
||||
|
||||
(ert-deftest eshell-test/escape-nonspecial-quoted ()
|
||||
"Test that the backslash is preserved for escaped nonspecial
|
||||
chars"
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo \"h\\i\""
|
||||
;; Backslashes are doubled for regexp.
|
||||
"h\\\\i\n")))
|
||||
|
||||
(ert-deftest eshell-test/escape-special-quoted ()
|
||||
"Test that the backslash is not preserved for escaped special
|
||||
chars"
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo \"\\\"hi\\\\\""
|
||||
;; Backslashes are doubled for regexp.
|
||||
"\\\"hi\\\\\n")))
|
||||
|
||||
(ert-deftest eshell-test/command-running-p ()
|
||||
"Modeline should show no command running"
|
||||
(with-temp-eshell
|
||||
|
Loading…
Reference in New Issue
Block a user