unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode
@ 2019-06-05 11:03 Enric Rodríguez Carbonell
  2019-06-06  1:02 ` Noam Postavsky
  2023-10-03 10:54 ` Mauro Aranda
  0 siblings, 2 replies; 4+ messages in thread
From: Enric Rodríguez Carbonell @ 2019-06-05 11:03 UTC (permalink / raw)
  To: 36103

Hi,

I would like to report what I think is a bug in the history expansion in 
shell mode.

This is the sequence of steps:

$ emacs -Q

Once in emacs:

M-x shell

Then I type in the command line of the shell window:

$ for file in $(ls); do echo $file; done

Then I type at the prompt:

$ !for

and then run command:

M-x comint-replace-by-expanded-history

which replaces the

$ !for

by

$ for file in $ ( ls ) ; do echo $file ; done

The blank spaces around the parentheses should not be there.
Because of these blank spaces, this command cannot be executed,
as "$ (" should be "$(".

Best regards,

Enric

****************************************************************

In GNU Emacs 24.5.1 (x86_64-pc-linux-gnu, GTK+ Version 3.18.9)
  of 2017-09-20 on lcy01-07, modified by Debian
Windowing system distributor `The X.Org Foundation', version 11.0.11804000
System Description:    Ubuntu 16.04.6 LTS

Configured using:
  `configure --build x86_64-linux-gnu --prefix=/usr
  --sharedstatedir=/var/lib --libexecdir=/usr/lib
  --localstatedir=/var/lib --infodir=/usr/share/info
  --mandir=/usr/share/man --with-pop=yes
  --enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.5/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.5/site-lisp:/usr/share/emacs/site-lisp
  --build x86_64-linux-gnu --prefix=/usr --sharedstatedir=/var/lib
  --libexecdir=/usr/lib --localstatedir=/var/lib
  --infodir=/usr/share/info --mandir=/usr/share/man --with-pop=yes
  --enable-locallisppath=/etc/emacs24:/etc/emacs:/usr/local/share/emacs/24.5/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.5/site-lisp:/usr/share/emacs/site-lisp
  --with-x=yes --with-x-toolkit=gtk3 --with-toolkit-scroll-bars
  'CFLAGS=-g -O2 -fstack-protector-strong -Wformat
  -Werror=format-security -Wall' 'CPPFLAGS=-Wdate-time
  -D_FORTIFY_SOURCE=2' 'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro''

Important settings:
   value of $LC_MONETARY: es_ES.UTF-8
   value of $LC_NUMERIC: es_ES.UTF-8
   value of $LC_TIME: es_ES.UTF-8
   value of $LANG: en_US.UTF-8
   value of $XMODIFIERS: @im=ibus
   locale-coding-system: utf-8-unix

Major mode: Shell

Minor modes in effect:
   shell-dirtrack-mode: t
   tooltip-mode: t
   electric-indent-mode: t
   mouse-wheel-mode: t
   tool-bar-mode: t
   menu-bar-mode: t
   file-name-shadow-mode: t
   global-font-lock-mode: t
   font-lock-mode: t
   blink-cursor-mode: t
   auto-composition-mode: t
   auto-encryption-mode: t
   auto-compression-mode: t
   line-number-mode: t
   transient-mark-mode: t

Recent messages:
For information about GNU Emacs and the GNU system, type C-h C-a.
History item: 1

Load-path shadows:
None found.

Features:
(shadow sort gnus-util mail-extr emacsbug message format-spec rfc822 mml
easymenu mml-sec mm-decode mm-bodies mm-encode mail-parse rfc2231
mailabbrev gmm-utils mailheader sendmail rfc2047 rfc2045 ietf-drums
mm-util help-fns mail-prsvr mail-utils shell pcomplete comint ansi-color
ring time-date tooltip electric uniquify ediff-hook vc-hooks
lisp-float-type mwheel x-win x-dnd tool-bar dnd fontset image regexp-opt
fringe tabulated-list newcomment lisp-mode prog-mode register page
menu-bar rfn-eshadow timer select scroll-bar mouse jit-lock font-lock
syntax facemenu font-core frame cham georgian utf-8-lang misc-lang
vietnamese tibetan thai tai-viet lao korean japanese hebrew greek
romanian slovak czech european ethiopic indian cyrillic chinese
case-table epa-hook jka-cmpr-hook help simple abbrev minibuffer nadvice
loaddefs button faces cus-face macroexp files text-properties overlay
sha1 md5 base64 format env code-pages mule custom widget
hashtable-print-readable backquote make-network-process dbusbind
gfilenotify dynamic-setting system-font-setting font-render-setting
move-toolbar gtk x-toolkit x multi-tty emacs)

Memory information:
((conses 16 75917 7030)
  (symbols 48 18019 0)
  (miscs 40 45 149)
  (strings 32 11232 4144)
  (string-bytes 1 311767)
  (vectors 16 9670)
  (vector-slots 8 389946 11489)
  (floats 8 63 219)
  (intervals 56 308 19)
  (buffers 960 12)
  (heap 1024 16912 853))
-- 
_______________________________________
Enric Rodriguez Carbonell
Technical University of Catalonia (UPC)
www.cs.upc.edu/~erodri
+34-93-4137815

Office 113 - Omega Building
Department of Computer Science (CS)
Jordi Girona 1, 08034 Barcelona, Spain
_______________________________________






^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode
  2019-06-05 11:03 bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode Enric Rodríguez Carbonell
@ 2019-06-06  1:02 ` Noam Postavsky
  2023-10-03 10:54 ` Mauro Aranda
  1 sibling, 0 replies; 4+ messages in thread
From: Noam Postavsky @ 2019-06-06  1:02 UTC (permalink / raw)
  To: Enric Rodríguez Carbonell; +Cc: 36103

merge 36103 8027
quit

Enric Rodríguez Carbonell <erodri@cs.upc.edu> writes:

> $ for file in $(ls); do echo $file; done

> $ !for

> M-x comint-replace-by-expanded-history

> $ for file in $ ( ls ) ; do echo $file ; done
>
> The blank spaces around the parentheses should not be there.
> Because of these blank spaces, this command cannot be executed,
> as "$ (" should be "$(".

Yes, the problem is in comint-arguments which attempts to parse a shell
line into separate arguments, using just a bunch of regexps.  This is
doomed to failure I think.  But the alternative would be a full blown
shell parser, so I don't know how feasible it will be to fix this bug.
It's been with us a long time.






^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode
  2019-06-05 11:03 bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode Enric Rodríguez Carbonell
  2019-06-06  1:02 ` Noam Postavsky
@ 2023-10-03 10:54 ` Mauro Aranda
  2023-10-07 13:39   ` Mauro Aranda
  1 sibling, 1 reply; 4+ messages in thread
From: Mauro Aranda @ 2023-10-03 10:54 UTC (permalink / raw)
  To: 36103
  Cc: Enric Rodríguez Carbonell, Lars Ingebrigtsen, Noam Postavsky,
	relson

Noam Postavsky <npostavs@gmail.com> writes:

 > Enric Rodríguez Carbonell <erodri@cs.upc.edu> writes:
 >
 >> $ for file in $(ls); do echo $file; done
 >
 >> $ !for
 >
 >> M-x comint-replace-by-expanded-history
 >
 >> $ for file in $ ( ls ) ; do echo $file ; done
 >>
 >> The blank spaces around the parentheses should not be there.
 >> Because of these blank spaces, this command cannot be executed,
 >> as "$ (" should be "$(".
 >
 > Yes, the problem is in comint-arguments which attempts to parse a shell
 > line into separate arguments, using just a bunch of regexps.  This is
 > doomed to failure I think.  But the alternative would be a full blown
 > shell parser, so I don't know how feasible it will be to fix this bug.
 > It's been with us a long time.

I just wanted to point out that since we now have treesitter support for
bash, this bug might be easier to fix, at least partially...

A first idea (trying to be backward-compatible and non-intrusive) is to
define a new variable, comint-arguments-function, turn the current
comint-arguments function into a comint-arguments-default function, and
set comint-arguments-function to that by default.  From now on,
comint-arguments would just call the comint-arguments-function and
return whatever that returns.

Then, shell.el can set comint-arguments-function to a new function,
that, if there is treesitter support for the program being run by shell
(bash or sh, AFAICS), tries to do a better job than comint-arguments as
of now.  If there's no treesitter support, or something fails, then it
can just punt to comint-arguments-default-function.






^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode
  2023-10-03 10:54 ` Mauro Aranda
@ 2023-10-07 13:39   ` Mauro Aranda
  0 siblings, 0 replies; 4+ messages in thread
From: Mauro Aranda @ 2023-10-07 13:39 UTC (permalink / raw)
  To: 36103; +Cc: Enric Rodríguez Carbonell, Lars Ingebrigtsen, Noam Postavsky

[-- Attachment #1: Type: text/plain, Size: 26 bytes --]

Here's a draft of my idea.

[-- Attachment #2: 0001-Give-comint-derived-modes-a-way-to-specialize-comint.patch --]
[-- Type: text/x-patch, Size: 9501 bytes --]

From 56628875e89b022ab42a2d78f207a4391a75198f Mon Sep 17 00:00:00 2001
From: Mauro Aranda <maurooaranda@gmail.com>
Date: Sun, 1 Oct 2023 09:51:53 -0300
Subject: [PATCH] Give comint derived modes a way to specialize
 comint-arguments

The comint-arguments regexp approach for splitting arguments falls
short, as demonstrated in Bug#36103.

* lisp/comint.el (comint-arguments-function): New variable.
(comint-arguments-default-function): New function.
(comint-arguments): Use it.
(comint-delimiter-argument-list): Adapt docstring.

* lisp/shell.el (shell-arguments): New function.  When treesitter
support is available for the shell program, specialize the way we
look for arguments in input.
(shell-mode): Set it as comint-arguments-function.

* test/lisp/shell-tests.el (shell-test-with-temporary-shell): New
macro.
(shell-test-history-expansion-helper): New function.
(shell-test-history-expansion): New test.
---
 lisp/comint.el           | 43 +++++++++++++++++++++++++++++----------
 lisp/shell.el            | 44 ++++++++++++++++++++++++++++++++++++++++
 test/lisp/shell-tests.el | 35 ++++++++++++++++++++++++++++++++
 3 files changed, 111 insertions(+), 11 deletions(-)

diff --git a/lisp/comint.el b/lisp/comint.el
index de7cc5b0e86..f0c05dd8e2c 100644
--- a/lisp/comint.el
+++ b/lisp/comint.el
@@ -202,13 +202,22 @@ comint-delimiter-argument-list
   "List of characters to recognize as separate arguments in input.
 Strings comprising a character in this list will separate the arguments
 surrounding them, and also be regarded as arguments in their own right (unlike
-whitespace).  See `comint-arguments'.
+whitespace).  See `comint-arguments-default-function'.
 Defaults to the empty list.
 
 For shells, a good value is (?\\| ?& ?< ?> ?\\( ?\\) ?;).
 
 This is a good thing to set in mode hooks.")
 
+(defvar-local comint-arguments-function #'comint-arguments-default-function
+  "Function to use to split arguments from an input.
+
+By default, this is `comint-arguments-default-function', which assumes, in
+general, that whitespace separates arguments and treats runs of characters in
+`comint-delimiter-argument-list' as a separate argument.
+
+This is a good thing to set in mode hooks.")
+
 (defcustom comint-input-autoexpand nil
   "If non-nil, expand input command history references on completion.
 This mirrors the optional behavior of tcsh (its autoexpand and histlist).
@@ -1785,17 +1794,14 @@ comint-delim-arg
 	  (setq args (cons (substring arg start pos) args))))
       args)))
 
-(defun comint-arguments (string nth mth)
+(defun comint-arguments-default-function (string nth mth)
   "Return from STRING the NTH to MTH arguments.
-NTH and/or MTH can be nil, which means the last argument.
-NTH and MTH can be negative to count from the end; -1 means
-the last argument.
-Returned arguments are separated by single spaces.  We assume
-whitespace separates arguments, except within quotes and except
-for a space or tab that immediately follows a backslash.  Also, a
-run of one or more of a single character in
-`comint-delimiter-argument-list' is a separate argument.
-Argument 0 is the command name."
+
+Used as the default function for `comint-arguments', returns the arguments
+separated by single spaces.  Assumes that whitespace separates arguments,
+except within quotes and except for a space or tab that immediately follows a
+backslash.  Also, a run of one or more of a single character in
+`comint-delimiter-argument-list' is a separate argument."
   ;; The first line handles ordinary characters and backslash-sequences
   ;; (except with w32 msdos-like shells, where backslashes are valid).
   ;; The second matches "-quoted strings.
@@ -1853,6 +1859,21 @@ comint-arguments
               (t          (1- (- mth))))))
       (mapconcat
        (lambda (a) a) (nthcdr n (nreverse (nthcdr m args))) " "))))
+
+(defun comint-arguments (string nth mth)
+    "Return from STRING the NTH to MTH arguments.
+
+NTH and/or MTH can be nil, which means the last argument.
+NTH and MTH can be negative to count from the end; -1 means
+the last argument.
+
+Argument 0 is the command name.
+
+Calls the `comint-arguments-function' with STRING, NTH and MTH as arguments
+and returns whatever that function returns, which should be the NTH to MTH
+arguments from STRING."
+  (funcall comint-arguments-function string nth mth))
+
 \f
 ;;
 ;; Input processing stuff
diff --git a/lisp/shell.el b/lisp/shell.el
index 48978fecbdd..103f39bc455 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -100,6 +100,11 @@
 (eval-when-compile (require 'files-x)) ;with-connection-local-variables
 (require 'subr-x)
 (eval-when-compile (require 'cl-lib))
+;; Used for better history expansion with event/word designators.
+(declare-function treesit-node-text "treesit.el")
+(declare-function treesit-node-on "treesit.el")
+(declare-function treesit-node-children "treesit.el")
+(declare-function treesit-ready-p "treesit.el")
 
 ;;; Customization and Buffer Variables
 
@@ -606,6 +611,44 @@ shell-completion-vars
 
 (defvar sh-shell-file)
 
+(defun shell-arguments (string nth mth)
+  "Return from STRING the NTH to MTH arguments, separated by whitespace.
+
+Used as `comint-arguments-function'.  When there's no tree-sitter support
+for the shell being used, falls back to `comint-arguments-default-function'."
+  (cond ((and (member shell--start-prog '("bash" "sh"))
+              (progn (require 'treesit)
+                     (and (treesit-available-p)
+                          (treesit-ready-p 'bash)))
+              (condition-case nil
+                  (with-temp-buffer
+                    (insert string)
+                    (let ((inhibit-message t))
+                      (bash-ts-mode))
+                    (let* ((ts-node (treesit-node-on (point-min) (point-max)))
+                           (args
+                            (mapcar #'treesit-node-text
+                                    ;; We don't want to return ")" for
+                                    ;; a STRING like $( cat file ).
+                                    ;; So treat everything that's not a
+                                    ;; command node as a single node.
+                                    (if (string= (treesit-node-type ts-node)
+                                                 "command")
+                                        (treesit-node-children ts-node)
+                                      (list ts-node))))
+                           (count (length args))
+                           (n (cond
+                               ((null nth) (1- count))
+                               ((>= nth 0) nth)
+                               (t (+ count nth))))
+                           (m (cond
+                               ((null mth) count)
+                               ((>= mth 0) (1+ mth))
+                               (t (1+ (- count mth))))))
+                      (mapconcat #'identity (seq-subseq args n m) " ")))
+                (error nil))))
+        (t (comint-arguments-default-function string nth mth))))
+
 (define-derived-mode shell-mode comint-mode "Shell"
   "Major mode for interacting with an inferior shell.
 \\<shell-mode-map>
@@ -684,6 +727,7 @@ shell-mode
   (setq-local shell-dirstack nil)
   (setq-local shell-last-dir nil)
   (setq-local comint-get-old-input #'shell-get-old-input)
+  (setq-local comint-arguments-function #'shell-arguments)
   ;; People expect Shell mode to keep the last line of output at
   ;; window bottom.
   (setq-local scroll-conservatively 101)
diff --git a/test/lisp/shell-tests.el b/test/lisp/shell-tests.el
index ddddfdb2e0f..c88b18194d3 100644
--- a/test/lisp/shell-tests.el
+++ b/test/lisp/shell-tests.el
@@ -25,6 +25,7 @@
 
 (require 'shell)
 (require 'ert)
+(require 'treesit)
 
 (ert-deftest shell-tests-unquote-1 ()
   "Test problem found by Filipp Gunbin in emacs-devel."
@@ -95,4 +96,38 @@ shell-directory-tracker-cd
       (should (not (equal start-dir list-buffers-directory)))
       (should (string-prefix-p list-buffers-directory start-dir)))))
 
+(defmacro shell-test-with-temporary-shell (shell &rest body)
+  "Run a temporary SHELL and return the result of evaluating BODY."
+  (declare (indent defun))
+  `(let ((explicit-shell-file-name ,shell))
+     (shell)
+     (unwind-protect
+         (progn ,@body)
+       (let ((shell-kill-buffer-on-exit t))
+         (comint-send-eof)))))
+
+(defun shell-test-history-expansion-helper (cmd designator)
+  "Add CMD to comint history, expand DESIGNATOR and return its expansion."
+  (shell-test-with-temporary-shell "bash"
+    (comint-add-to-input-history cmd)
+    (end-of-buffer)
+    (let ((opoint (point)))
+      (insert designator)
+      (completion-at-point)
+      (prog1 (buffer-substring opoint (point))
+        (delete-region opoint (point))))))
+
+(ert-deftest shell-test-history-expansion ()
+  "Test that history expansion with designators works."
+  (skip-unless (and (treesit-ready-p 'bash)
+                    (executable-find "bash")))
+  (let ((cmd "cat <( date )"))
+    ;; The following three tests come from Bug5007.
+    ;; TODO: More tests with different designators.
+    (should (string= (shell-test-history-expansion-helper cmd "!!") cmd))
+    (should (string= (shell-test-history-expansion-helper cmd "!:$") "<( date )"))
+    (should (string= (shell-test-history-expansion-helper
+                      (substring cmd 4) "!:$")
+                     (substring cmd 4)))))
+
 ;;; shell-tests.el ends here
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2023-10-07 13:39 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-05 11:03 bug#36103: 24.5; Blank spaces around parentheses in history expansion in shell mode Enric Rodríguez Carbonell
2019-06-06  1:02 ` Noam Postavsky
2023-10-03 10:54 ` Mauro Aranda
2023-10-07 13:39   ` Mauro Aranda

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).