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