diff --git a/eat.el b/eat.el index 4ef38a8..c84be77 100644 --- a/eat.el +++ b/eat.el @@ -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 ; ST. + ;; UIC e ; I ; 0 ; ; ; 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 ; ; 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 ; 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. )) diff --git a/eat.texi b/eat.texi index 767b0c6..bfe36d9 100644 --- a/eat.texi +++ b/eat.texi @@ -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 diff --git a/integration/bash b/integration/bash index e760942..897c11d 100644 --- a/integration/bash +++ b/integration/bash @@ -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-" diff --git a/integration/zsh b/integration/zsh index 28480a1..6b7d7ac 100644 --- a/integration/zsh +++ b/integration/zsh @@ -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-"