Add message passing support

* eat.el (eat-message-handler-alist): New user option.
* eat.el (eat--handle-message): New function.
* eat.el (eat--handle-uic, eat--eshell-handle-uic): Handle
message passing sequence.
* eat.texi (Message Passing): New section in chapter "Shell
Integration".
* integration/bash (__eat_enable_integration): Remove the
unnecessary complex code to update PROMPT_COMMAND.
* integration/bash (_eat_msg):
* integration/zsh (_eat_msg):
New function.
This commit is contained in:
Akib Azmain Turja 2023-09-17 21:58:40 +06:00
parent 9e129f33a2
commit b2ad1be411
No known key found for this signature in database
GPG key ID: 5535FCF54D88616B
4 changed files with 100 additions and 13 deletions

46
eat.el
View file

@ -296,6 +296,20 @@ the history of commands like `eat', `shell-command' and
:group 'eat-ui
:group 'eat-eshell)
(defcustom eat-message-handler-alist nil
"Alist of message handler name and its handler function.
The keys are the names of message handlers, and the values are their
respective handler functions.
Shells can send Eat messages, as defined in this user option. If an
appropiate message handler is defined, it's called with the other
arguments, otherwise it's ignored."
:type '(alist :key-type string
:value-type function)
:group 'eat-ui
:group 'eat-eshell)
(defcustom eat-enable-native-shell-prompt-editing nil
"Non-nil means allowing editing shell prompt using Emacs commands.
@ -5215,6 +5229,22 @@ BUFFER is the terminal buffer."
locale-coding-system))
format))))
(defun eat--handle-message (name &rest args)
"Handle message with handler name NAME and ARGS."
(when-let* ((name (ignore-errors (decode-coding-string
(base64-decode-string name)
locale-coding-system)))
(handler (assoc name eat-message-handler-alist)))
(save-restriction
(widen)
(save-excursion
(apply (cdr handler)
(mapcar (lambda (arg)
(ignore-errors (decode-coding-string
(base64-decode-string arg)
locale-coding-system)))
args))))))
(defun eat--handle-uic (_ cmd)
"Handle UI Command sequence CMD."
(pcase cmd
@ -5257,20 +5287,27 @@ BUFFER is the terminal buffer."
(let status (one-or-more digit))
string-end)
(eat--set-cmd-status (string-to-number status)))
;; UIC e ; I ; <n> ST.
;; UIC e ; I ; 0 ; <t> ; <t> ; <t> ST.
((rx string-start "e;I;0;"
(let format (zero-or-more (not ?\;)))
?\; (let host (zero-or-more (not ?\;)))
?\; (let path (zero-or-more anything))
string-end)
(eat--get-shell-history (cons host path) format))
;; UIC e ; I ; 1 ; <t> ; <t> ST.
((rx string-start "e;I;1;"
(let format (zero-or-more (not ?\;)))
?\; (let hist (zero-or-more anything))
string-end)
(eat--get-shell-history hist format))
;; UIC e ; J ST.
("e;J"
(eat--before-new-prompt))))
(eat--before-new-prompt))
;; UIC e ; M ; ... ST.
((rx string-start "e;M;"
(let msg (zero-or-more anything))
string-end)
(apply #'eat--handle-message (string-split msg ";")))))
(defun eat-previous-shell-prompt (&optional arg)
"Go to the previous shell prompt.
@ -6894,6 +6931,11 @@ PROGRAM can be a shell command."
;; UIC e ; I ; 0 ; <t> ST.
((rx string-start "e;I;0;" (zero-or-more anything) string-end)
(eat-term-send-string eat--terminal "\e]51;e;I;0\e\\"))
;; UIC e ; M ; ... ST.
((rx string-start "e;M;"
(let msg (zero-or-more anything))
string-end)
(apply #'eat--handle-message (string-split msg ";")))
;; Other sequences are ignored.
))

View file

@ -479,7 +479,6 @@ When set to @code{right-margin}, Eat uses the right margin.
@vindex eat-shell-prompt-annotation-success
@vindex eat-shell-prompt-annotation-failure-margin-indicator
@vindex eat-shell-prompt-annotation-failure
Eat uses the strings ``-'', ``0'' and ``X'' respectively to indicate
the command is running, the command has succeeded and the command has
failed. You can also customize the them. The user option
@ -495,6 +494,40 @@ face @code{eat-shell-prompt-annotation-failure} control the indicator
used to indicate the command has exited unsuccessfully with non-zero
exit status.
@anchor{Message Passing}
@section Message Passing
After enabling shell integration, you can send messages to Emacs from
your shell. Then you can handle the message on Emacs side using usual
Emacs Lisp function.
When shell integration script is loaded, a function named
@command{_eat_msg} is defined in your shell. You can use this to send
any message to Emacs. (The @samp{_} in the beginning of the function
name is intentional to prevent shadowing any actual command.)
@deffn Command _eat_msg @var{handler-name} @var{message}...
Send message @var{message}, handled by the handler named
@var{handler-name} in Emacs.
@end deffn
The messages are handled with the handlers defined in
@code{eat-message-handler-alist}.
@vindex eat-message-handler-alist
@defopt eat-message-handler-alist
Alist of message handler name and its handler function. The keys are
the names of message handlers (i.e. the @var{handler-name} argument of
@command{_eat_msg}), and the values are their respective handler
functions. The handler function is called with the @var{message}
arguments of @command{_eat_msg}. Messages with undefined handlers are
ignored. To disable message passing, set this to nil.
@end defopt
Beware, messages can be sent by malicious and/or buggy programs
running in the shell, therefore you should always verify the message
before doing anywhere.
@anchor{Native Shell Prompt Editing}
@cindex native shell prompt editing
@cindex shell prompt native editing

View file

@ -89,21 +89,13 @@ __eat_enable_integration ()
PROMPT_COMMAND+=(__eat_prompt_command)
trap '__eat_before_exec' DEBUG
# Wrap 'PROMPT_COMMAND' to avoid it getting trapped in 'DEBUG' trap.
# Step 1: Append to PROMPT_COMMAND.
PROMPT_COMMAND+=(__eat_after_prompt_command)
# Step 2: Prepend to PROMPT_COMMAND.
# Step 2.1: Move all elements to make the first index free.
# Fun fact: Microsoft doesn't still about know this simple trick.
# They ended up using something as silly and pityful as
# 'VAR=$PROMPT_COMMAND' to copy a Bash array in VSCode Bash
# integration script, which simply won't work ever, and then
# complain about Bash in the comments! xD
for i in $(eval "echo {${#PROMPT_COMMAND[*]}..1..-1}")
do
PROMPT_COMMAND[$i]=${PROMPT_COMMAND[$((i-1))]}
done
# Step 2.2: Assign the first element.
PROMPT_COMMAND[0]=__eat_before_prompt_command
PROMPT_COMMAND+=(__eat_after_prompt_command)
PROMPT_COMMAND=(__eat_before_prompt_command "${PROMPT_COMMAND[@]}")
# Send the history, for native shell prompt.
printf '\e]51;e;I;0;bash;%s;%s\e\\' \
"$(printf "%s" "$HOSTNAME" | base64)" \
@ -117,6 +109,16 @@ __eat_enable_integration ()
fi
}
_eat_msg () {
local msg=$'\e]51;e;M'
for _ in $(eval "echo {1..$#}")
do
msg="$msg;$(printf "%s" "$1" | base64)"
shift
done
printf "%s\e\\" "$msg"
}
# Enable.
if test -z "$__eat_integration_enabled" && \
test "${TERM:0:4}" = "eat-"

View file

@ -80,6 +80,16 @@ __eat_enable_integration ()
fi
}
_eat_msg () {
local msg=$'\e]51;e;M'
for _ in $(eval "echo {1..$#}")
do
msg="$msg;$(printf "%s" "$1" | base64)"
shift
done
printf "%s\e\\" "$msg"
}
# Enable.
if test -z "$__eat_integration_enabled" && \
test "${TERM:0:4}" = "eat-"