unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#25270: eshell -- programmatically send input -- feature request
@ 2016-12-25 19:00 Keith David Bershatsky
  2022-05-13 14:02 ` Lars Ingebrigtsen
  0 siblings, 1 reply; 5+ messages in thread
From: Keith David Bershatsky @ 2016-12-25 19:00 UTC (permalink / raw)
  To: 25270

As far as I am aware, users of eshell have been limited to sending input programmatically by inserting the command into the eshell buffer (at the command prompt) and then executing `eshell-send-input`.  Some users (like myself) may feel that such a solution is lo-tech -- i.e., not very eloquent.  Perhaps this is because I, and perhaps others, am/are spoiled by functions available when running shell such as:

    (let ((buf (shell)))
      (comint-send-string buf "ls -la")
      (comint-send-input))

or

    (let ((buf (shell)))
      (comint-simple-send buf "ls -la"))

Here is a link to a thread entitled "How to programmatically execute a command in eshell?":

http://emacs.stackexchange.com/questions/7617/how-to-programmatically-execute-a-command-in-eshell

And the accepted answer is as follows:

    (with-current-buffer "*eshell*"
      (eshell-return-to-prompt)
      (insert "ls")
      (eshell-send-input))

The following is an example of how this new feature might be implemented:

SAMPLE USAGE:  (eshell-send-input nil nil nil "ls -la /")

(require 'eshell)

(defun eshell-send-input (&optional use-region queue-p no-newline input-string-a)
  "Send the input received to Eshell for parsing and processing.
After `eshell-last-output-end', sends all text from that marker to
point as input.  Before that marker, calls `eshell-get-old-input' to
retrieve old input, copies it to the end of the buffer, and sends it.
-  If USE-REGION is non-nil, the current region (between point and mark)
will be used as input.
-  If QUEUE-P is non-nil, input will be queued until the next prompt,
rather than sent to the currently active process.  If no process, the
input is processed immediately.
-  If NO-NEWLINE is non-nil, the input is sent without an implied final
newline."
  (interactive "P")
  ;; Note that the input string does not include its terminal newline.
  (let ((proc-running-p
          (and (eshell-interactive-process)
               (not queue-p)))
        (inhibit-point-motion-hooks t)
        after-change-functions)
    (unless (and proc-running-p
                 (not (eq (process-status (eshell-interactive-process)) 'run)))
      (if (or proc-running-p
              (>= (point) eshell-last-output-end))
        (goto-char (point-max))
        ;; This is for a situation when point is before `point-max'.
        (let ((copy (or input-string-a (eshell-get-old-input use-region))))
          (goto-char eshell-last-output-end)
          (insert-and-inherit copy)))
      (unless (or no-newline
                  (and eshell-send-direct-to-subprocesses
                       proc-running-p))
        (insert-before-markers-and-inherit ?\n))
      (if proc-running-p
        (progn
          (eshell-update-markers eshell-last-output-end)
          (if (or eshell-send-direct-to-subprocesses
                  (= eshell-last-input-start eshell-last-input-end))
            (unless no-newline
              (process-send-string (eshell-interactive-process) "\n"))
                (process-send-region (eshell-interactive-process)
                   eshell-last-input-start
                   eshell-last-input-end)))
        (if (and (null input-string-a) (= eshell-last-output-end (point)))
          ;; This next line is for a situation when nothing is there -- just make a new command prompt.
          (run-hooks 'eshell-post-command-hook)
          (let (input)
            (eshell-condition-case err
              (progn
                (setq input (or input-string-a
                                (buffer-substring-no-properties
                                   eshell-last-output-end (1- (point)))))
                (run-hook-with-args 'eshell-expand-input-functions
                        eshell-last-output-end (1- (point)))
                (let ((cmd (eshell-parse-command-input
                             eshell-last-output-end (1- (point)) nil input-string-a)))
                  (when cmd
                    (eshell-update-markers eshell-last-output-end)
                    (setq input (buffer-substring-no-properties
                                  eshell-last-input-start
                                  (1- eshell-last-input-end)))
                    (run-hooks 'eshell-input-filter-functions)
                    (and (catch 'eshell-terminal
                           (ignore
                             (if (eshell-invoke-directly cmd)
                               (eval cmd)
                               (eshell-eval-command cmd input))))
                         (eshell-life-is-too-much)))))
                  (quit
                    (eshell-reset t)
                    (run-hooks 'eshell-post-command-hook)
                    (signal 'quit nil))
                  (error
                    (eshell-reset t)
                    (eshell-interactive-print
                      (concat (error-message-string err) "\n"))
                    (run-hooks 'eshell-post-command-hook)
                    (insert-and-inherit input)))))))))

(defun eshell-parse-command-input (beg end &optional args input-string-b)
  "Parse the command input from BEG to END.
The difference is that `eshell-parse-command' expects a complete
command string (and will error if it doesn't get one), whereas this
function will inform the caller whether more input is required.
-  If nil is returned, more input is necessary (probably because a
multi-line input string wasn't terminated properly).  Otherwise, it
will return the parsed command."
  (let (delim command)
    (if (setq delim (catch 'eshell-incomplete
                      (ignore
                        (setq command (eshell-parse-command (cons beg end) args t input-string-b)))))
      (ignore
        (message "Expecting completion of delimiter %c ..."
          (if (listp delim)
              (car delim)
            delim)))
      command)))

(defun eshell-parse-command (command &optional args toplevel input-string-c)
  "Parse the COMMAND, adding ARGS if given.
COMMAND can either be a string, or a cons cell demarcating a buffer
region.  TOPLEVEL, if non-nil, means that the outermost command (the
user's input command) is being parsed, and that pre and post command
hooks should be run before and after the command."
  (let* (
      eshell--sep-terms
      (terms
        (if input-string-c
          (eshell-parse-arguments--temp-buffer input-string-c)
          (append
            (if (consp command)
              (eshell-parse-arguments (car command) (cdr command))
              (let ((here (point))
                    (inhibit-point-motion-hooks t))
                (with-silent-modifications
                  ;; FIXME: Why not use a temporary buffer and avoid this
                  ;; "insert&delete" business?  --Stef
                  (insert command)
                  (prog1
                      (eshell-parse-arguments here (point))
                    (delete-region here (point))))))
            args)))
      (commands
        (mapcar
          (function
            (lambda (cmd)
              (setq cmd (if (or (not (car eshell--sep-terms))
                                (string= (car eshell--sep-terms) ";"))
                          (eshell-parse-pipeline cmd)
                          `(eshell-do-subjob
                              (list ,(eshell-parse-pipeline cmd)))))
              (setq eshell--sep-terms (cdr eshell--sep-terms))
              (if eshell-in-pipeline-p
                cmd
                `(eshell-trap-errors ,cmd))))
          (eshell-separate-commands terms "[&;]" nil 'eshell--sep-terms))) )
    (let ((cmd commands))
      (while cmd
        (if (cdr cmd)
            (setcar cmd `(eshell-commands ,(car cmd))))
        (setq cmd (cdr cmd))))
    (if toplevel
      `(eshell-commands (progn
                                (run-hooks 'eshell-pre-command-hook)
                                (catch 'top-level (progn ,@commands))
                                (run-hooks 'eshell-post-command-hook)))
      (macroexp-progn commands))))

(defun eshell-parse-arguments--temp-buffer (input-string-d)
  "Parse all of the arguments at point from BEG to END.
Returns the list of arguments in their raw form.
Point is left at the end of the arguments."
  (with-temp-buffer
    (insert input-string-d)
    (let ((inhibit-point-motion-hooks t)
          (args (list t))
          delim)
      (with-silent-modifications
        (remove-text-properties (point-min) (point-max)
                                '(arg-begin nil arg-end nil))
        (goto-char (point-min))
        (if (setq
             delim
             (catch 'eshell-incomplete
               (while (not (eobp))
                 (let* ((here (point))
                        (arg (eshell-parse-argument)))
                   (if (= (point) here)
                       (error "Failed to parse argument '%s'"
                              (buffer-substring here (point-max))))
                   (and arg (nconc args (list arg)))))))
            (throw 'eshell-incomplete (if (listp delim)
                                          delim
                                        (list delim (point) (cdr args)))))
        (cdr args)))))





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

* bug#25270: eshell -- programmatically send input -- feature request
  2016-12-25 19:00 bug#25270: eshell -- programmatically send input -- feature request Keith David Bershatsky
@ 2022-05-13 14:02 ` Lars Ingebrigtsen
  2022-05-16  5:46   ` Jim Porter
  0 siblings, 1 reply; 5+ messages in thread
From: Lars Ingebrigtsen @ 2022-05-13 14:02 UTC (permalink / raw)
  To: Keith David Bershatsky; +Cc: Jim Porter, 25270

Keith David Bershatsky <esq@lawlist.com> writes:

> As far as I am aware, users of eshell have been limited to sending
> input programmatically by inserting the command into the eshell buffer
> (at the command prompt) and then executing `eshell-send-input`.  Some
> users (like myself) may feel that such a solution is lo-tech -- i.e.,
> not very eloquent. 

[...]

> The following is an example of how this new feature might be implemented:
>
> SAMPLE USAGE:  (eshell-send-input nil nil nil "ls -la /")
>
> (require 'eshell)
>
> (defun eshell-send-input (&optional use-region queue-p no-newline input-string-a)

(I'm going through old bug reports that unfortunately weren't resolved
at the time.)

I think this makes sense, but I'm not very familiar with eshell
internals, so I've added Jim to the CCs; perhaps he has some comments.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





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

* bug#25270: eshell -- programmatically send input -- feature request
  2022-05-13 14:02 ` Lars Ingebrigtsen
@ 2022-05-16  5:46   ` Jim Porter
  2022-05-16 12:13     ` Lars Ingebrigtsen
  0 siblings, 1 reply; 5+ messages in thread
From: Jim Porter @ 2022-05-16  5:46 UTC (permalink / raw)
  To: Lars Ingebrigtsen, Keith David Bershatsky; +Cc: 25270

On 5/13/2022 7:02 AM, Lars Ingebrigtsen wrote:
> Keith David Bershatsky <esq@lawlist.com> writes:
> 
>> As far as I am aware, users of eshell have been limited to sending
>> input programmatically by inserting the command into the eshell buffer
>> (at the command prompt) and then executing `eshell-send-input`.  Some
>> users (like myself) may feel that such a solution is lo-tech -- i.e.,
>> not very eloquent.
> 
> [...]
> 
>> The following is an example of how this new feature might be implemented:
>>
>> SAMPLE USAGE:  (eshell-send-input nil nil nil "ls -la /")
>>
>> (require 'eshell)
>>
>> (defun eshell-send-input (&optional use-region queue-p no-newline input-string-a)
> 
> (I'm going through old bug reports that unfortunately weren't resolved
> at the time.)
> 
> I think this makes sense, but I'm not very familiar with eshell
> internals, so I've added Jim to the CCs; perhaps he has some comments.

Hm, I think it's reasonable to have something similar to 
`comint-send-string' for Eshell, but I'm not quite sure what the best 
way to do this would be. I think a separate function, like 
`eshell-send-string', would probably be a nicer API, since it could be 
called like `comint-send-string'.

Also, for the code posted in the original message, I'm not sure the 
changes to `eshell-parse-command' are needed. It should already let you 
pass a command string to it. Maybe this is because there's an issue with 
how `eshell-parse-command' temporarily inserts COMMAND into the buffer 
(see the FIXME comment in the code in the original message)? If there 
is, we'd probably have to think quite a bit more about how to resolve it.

Some background: I think it would be pretty risky to try to perform 
Eshell argument parsing anywhere *but* in the contents of the current 
Eshell buffer, as in `eshell-parse-arguments--temp-buffer' in the 
original message. See `eshell-with-temp-command' in Emacs 29 (which is 
what the FIXME comment morphed into), in particular this part:

          ;; Since parsing relies partly on buffer-local state
          ;; (e.g. that of `eshell-parse-argument-hook'), we need to
          ;; perform the parsing in the Eshell buffer.

Basically, Eshell is extremely flexible, and argument parsing is handled 
by `eshell-parse-argument-hook', which can do all sorts of things to 
change how arguments are parsed, and may even be buffer-local to a 
particular Eshell instance. Parsing Eshell arguments anywhere but the 
"target" Eshell buffer is asking for trouble.

I'll see if I can put together a patch along the above line in the next 
couple weeks.





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

* bug#25270: eshell -- programmatically send input -- feature request
  2022-05-16  5:46   ` Jim Porter
@ 2022-05-16 12:13     ` Lars Ingebrigtsen
  2022-05-16 17:33       ` Jim Porter
  0 siblings, 1 reply; 5+ messages in thread
From: Lars Ingebrigtsen @ 2022-05-16 12:13 UTC (permalink / raw)
  To: Jim Porter; +Cc: Keith David Bershatsky, 25270

Jim Porter <jporterbugs@gmail.com> writes:

> Also, for the code posted in the original message, I'm not sure the
> changes to `eshell-parse-command' are needed. It should already let
> you pass a command string to it. Maybe this is because there's an
> issue with how `eshell-parse-command' temporarily inserts COMMAND into
> the buffer (see the FIXME comment in the code in the original
> message)? If there is, we'd probably have to think quite a bit more
> about how to resolve it.

Yes, if I understood correctly, that was the main problem -- inserting
things into the buffer to eval it sometimes leaves artefacts (or doesn't
work), so it'd be better to talk directly to the underlying shell to get
things evalled.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





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

* bug#25270: eshell -- programmatically send input -- feature request
  2022-05-16 12:13     ` Lars Ingebrigtsen
@ 2022-05-16 17:33       ` Jim Porter
  0 siblings, 0 replies; 5+ messages in thread
From: Jim Porter @ 2022-05-16 17:33 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Keith David Bershatsky, 25270

On 5/16/2022 5:13 AM, Lars Ingebrigtsen wrote:
> Jim Porter <jporterbugs@gmail.com> writes:
> 
>> Also, for the code posted in the original message, I'm not sure the
>> changes to `eshell-parse-command' are needed. It should already let
>> you pass a command string to it. Maybe this is because there's an
>> issue with how `eshell-parse-command' temporarily inserts COMMAND into
>> the buffer (see the FIXME comment in the code in the original
>> message)? If there is, we'd probably have to think quite a bit more
>> about how to resolve it.
> 
> Yes, if I understood correctly, that was the main problem -- inserting
> things into the buffer to eval it sometimes leaves artefacts (or doesn't
> work), so it'd be better to talk directly to the underlying shell to get
> things evalled.

I think for talking to Eshell itself, inserting things into the buffer 
(temporarily) should be ok. Eshell already does this (as of Emacs 29, I 
think) when parsing some complex commands. If you have some $-expansions 
inside double-quotes, e.g. 'echo "${echo \"hi there\"}"', Eshell 
temporarily inserts 'echo "hi there"' into the buffer to parse it, and 
then removes it before proceeding.

For sending input to a child process being run inside Eshell, we 
wouldn't do all this, since Eshell wouldn't be treating the input as an 
Eshell command to be parsed. In that case, we could just send the string 
to the child process directly (possibly with some extra Eshell bookkeeping).





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

end of thread, other threads:[~2022-05-16 17:33 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-12-25 19:00 bug#25270: eshell -- programmatically send input -- feature request Keith David Bershatsky
2022-05-13 14:02 ` Lars Ingebrigtsen
2022-05-16  5:46   ` Jim Porter
2022-05-16 12:13     ` Lars Ingebrigtsen
2022-05-16 17:33       ` Jim Porter

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).