Add 'server-eval-args-left' to server.el

Passing arbitrary arguments to functions through
"emacsclient --eval" sometimes requires complicated escaping
to avoid them being parsed as Lisp (as seen in
emacsclient-mail.desktop before this change).

The new variable 'server-eval-args-left' allows access to the
arguments before they are parsed as Lisp.  By removing
arguments from the variable before they're parsed, a snippet
of Lisp can consume arguments, as in emacsclient-mail.desktop.

org-protocol might be able to use this as well, which might allow it
to drop its current advice on server-visit-files.

* etc/emacsclient-mail.desktop: Use 'server-eval-args-left'.
* lisp/server.el (server-eval-args-left): New variable.
(server-process-filter, server-execute): Make '-eval' arguments
available through 'server-eval-args-left'.
* lisp/startup.el (argv): Mention 'server-eval-args-left' in
docstring.
* etc/NEWS: Announce 'server-eval-args-left'.
* doc/emacs/misc.texi (emacsclient Options): Document
'server-eval-args-left'.  (Bug#65902)
feature/cl-lib-improvements
Spencer Baugh 2023-09-21 21:35:50 -04:00 committed by Eli Zaretskii
parent f3a27180b7
commit 683efb8de5
5 changed files with 45 additions and 13 deletions

View File

@ -2078,6 +2078,15 @@ files. When this option is given, the arguments to
@command{emacsclient} are interpreted as a list of expressions to
evaluate, @emph{not} as a list of files to visit.
@vindex server-eval-args-left
If you have arbitrary data which you want to provide as input to one
of your expressions, you can pass the data as another argument to
@command{emacsclient} and use @var{server-eval-args-left} in the
expression to access the data. Be careful to have your expression
remove the data from @var{server-eval-args-left} regardless of whether
your code succeeds, such as by using @code{pop}, otherwise Emacs will
attempt to evaluate the data as a Lisp expression.
@item -f @var{server-file}
@itemx --server-file=@var{server-file}
Specify a server file (@pxref{TCP Emacs server}) for connecting to an

View File

@ -233,6 +233,16 @@ to enter the file you want to modify.
It can be used to customize the look of the appointment notification
displayed on the mode line when 'appt-display-mode-line' is non-nil.
** Emacs Server and Client
---
*** 'server-eval-args-left' can be used to pop subsequent eval args
When '--eval' is passed to emacsclient and Emacs is evaluating each
argument, this variable is set to those which have not yet been
evaluated. It can be used to 'pop' arguments to prevent them from
being evaluated, which is useful when those arguments contain
arbitrary data.
* Editing Changes in Emacs 30.1

View File

@ -1,10 +1,7 @@
[Desktop Entry]
Categories=Network;Email;
Comment=GNU Emacs is an extensible, customizable text editor - and more
# We want to pass the following commands to the shell wrapper:
# u=$(echo "$1" | sed 's/[\"]/\\&/g'); exec emacsclient --alternate-editor= --display="$DISPLAY" --eval "(message-mailto \"$u\")"
# Special chars '"', '$', and '\' must be escaped as '\\"', '\\$', and '\\\\'.
Exec=sh -c "u=\\$(echo \\"\\$1\\" | sed 's/[\\\\\\"]/\\\\\\\\&/g'); exec emacsclient --alternate-editor= --display=\\"\\$DISPLAY\\" --eval \\"(message-mailto \\\\\\"\\$u\\\\\\")\\"" sh %u
Exec=emacsclient --alternate-editor= --eval "(message-mailto (pop server-eval-args-left))" %u
Icon=emacs
Name=Emacs (Mail, Client)
MimeType=x-scheme-handler/mailto;
@ -16,7 +13,7 @@ Actions=new-window;new-instance;
[Desktop Action new-window]
Name=New Window
Exec=sh -c "u=\\$(echo \\"\\$1\\" | sed 's/[\\\\\\"]/\\\\\\\\&/g'); exec emacsclient --alternate-editor= --create-frame --eval \\"(message-mailto \\\\\\"\\$u\\\\\\")\\"" sh %u
Exec=emacsclient --alternate-editor= --create-frame --eval "(message-mailto (pop server-eval-args-left))" %u
[Desktop Action new-instance]
Name=New Instance

View File

@ -1199,6 +1199,7 @@ The following commands are accepted by the client:
parent-id ; Window ID for XEmbed
dontkill ; t if client should not be killed.
commands
evalexprs
dir
use-current-frame
frame-parameters ;parameters for newly created frame
@ -1332,8 +1333,7 @@ The following commands are accepted by the client:
(let ((expr (pop args-left)))
(if coding-system
(setq expr (decode-coding-string expr coding-system)))
(push (lambda () (server-eval-and-print expr proc))
commands)
(push expr evalexprs)
(setq filepos nil)))
;; -env NAME=VALUE: An environment variable.
@ -1358,7 +1358,7 @@ The following commands are accepted by the client:
;; arguments, use an existing frame.
(and nowait
(not (eq tty-name 'window-system))
(or files commands)
(or files commands evalexprs)
(setq use-current-frame t))
(setq frame
@ -1407,7 +1407,7 @@ The following commands are accepted by the client:
(let ((default-directory
(if (and dir (file-directory-p dir))
dir default-directory)))
(server-execute proc files nowait commands
(server-execute proc files nowait commands evalexprs
dontkill frame tty-name)))))
(when (or frame files)
@ -1417,22 +1417,35 @@ The following commands are accepted by the client:
;; condition-case
(t (server-return-error proc err))))
(defun server-execute (proc files nowait commands dontkill frame tty-name)
(defvar server-eval-args-left nil
"List of eval args not yet processed.
Adding or removing strings from this variable while the Emacs
server is processing a series of eval requests will affect what
Emacs evaluates.
See also `argv' for a similar variable which works for
invocations of \"emacs\".")
(defun server-execute (proc files nowait commands evalexprs dontkill frame tty-name)
;; This is run from timers and process-filters, i.e. "asynchronously".
;; But w.r.t the user, this is not really asynchronous since the timer
;; is run after 0s and the process-filter is run in response to the
;; user running `emacsclient'. So it is OK to override the
;; inhibit-quit flag, which is good since `commands' (as well as
;; inhibit-quit flag, which is good since `evalexprs' (as well as
;; find-file-noselect via the major-mode) can run arbitrary code,
;; including code that needs to wait.
(with-local-quit
(condition-case err
(let ((buffers (server-visit-files files proc nowait)))
(mapc 'funcall (nreverse commands))
(let ((server-eval-args-left (nreverse evalexprs)))
(while server-eval-args-left
(server-eval-and-print (pop server-eval-args-left) proc)))
;; If we were told only to open a new client, obey
;; `initial-buffer-choice' if it specifies a file
;; or a function.
(unless (or files commands)
(unless (or files commands evalexprs)
(let ((buf
(cond ((stringp initial-buffer-choice)
(find-file-noselect initial-buffer-choice))

View File

@ -120,7 +120,10 @@ the remaining command-line args are in the variable `command-line-args-left'.")
"List of command-line args not yet processed.
This is a convenience alias, so that one can write (pop argv)
inside of --eval command line arguments in order to access
following arguments."))
following arguments.
See also `server-eval-args-left' for a similar variable which
works for invocations of \"emacsclient --eval\"."))
(internal-make-var-non-special 'argv)
(defvar command-line-args-left nil