From 4881017055ea6831ee7fe2d722eb79856946d907 Mon Sep 17 00:00:00 2001 From: Spencer Baugh Date: Tue, 12 Sep 2023 22:20:15 -0400 Subject: [PATCH] Add --apply argument to avoid escaping arguments Passing arguments to functions through emacs --eval or emacsclient --eval requires complicated escaping (as seen in emacsclient-mail.desktop before this change). The new --apply argument for both emacs and emacsclient passes command line arguments as uninterpreted strings to the specified function. This simplifies use cases where arbitrary input needs to be passed to Emacs. Note that there's a minor difference in behavior between emacs --apply and emacsclient --apply: emacs --apply func calls func with only command line arguments occuring after --apply, emacsclient --apply func calls func with all command line arguments, even those which occurred before --apply. This is hard to avoid since emacsclient uses getopt instead of fancier custom Lisp argument parsing in Emacs. * etc/emacsclient-mail.desktop: Use --apply. (bug#65902) * lib-src/emacsclient.c (longopts, decode_options, main): Add support for --apply. * lisp/server.el (server-apply-and-print): Add. (server-process-filter): Add support for -apply and -applyargs * lisp/startup.el (command-line-1): Add support for --apply. * src/emacs.c (usage_message, standard_args): Add support for --apply. --- etc/emacsclient-mail.desktop | 9 +++------ lib-src/emacsclient.c | 36 ++++++++++++++++++++++++++++++++++-- lisp/server.el | 34 ++++++++++++++++++++++++++++++++++ lisp/startup.el | 18 +++++++++++++++--- src/emacs.c | 3 +++ 5 files changed, 89 insertions(+), 11 deletions(-) diff --git a/etc/emacsclient-mail.desktop b/etc/emacsclient-mail.desktop index 0a2420ddead..750fcddacdc 100644 --- a/etc/emacsclient-mail.desktop +++ b/etc/emacsclient-mail.desktop @@ -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= --apply message-mailto -- %u Icon=emacs Name=Emacs (Mail, Client) MimeType=x-scheme-handler/mailto; @@ -16,8 +13,8 @@ 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 --apply message-mailto -- %u [Desktop Action new-instance] Name=New Instance -Exec=emacs -f message-mailto %u +Exec=emacs --apply message-mailto -- %u diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 698bf9b50ae..159c22d1ae9 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -116,6 +116,9 @@ #define DEFAULT_TIMEOUT (30) /* True means args are expressions to be evaluated. --eval. */ static bool eval; +/* The function to call. Other arguments are passed as strings. --apply. */ +static char *apply; + /* True means open a new frame. --create-frame etc. */ static bool create_frame; @@ -169,6 +172,7 @@ #define DEFAULT_TIMEOUT (30) { "quiet", no_argument, NULL, 'q' }, { "suppress-output", no_argument, NULL, 'u' }, { "eval", no_argument, NULL, 'e' }, + { "apply", required_argument, NULL, 'y' }, { "help", no_argument, NULL, 'H' }, { "version", no_argument, NULL, 'V' }, { "tty", no_argument, NULL, 't' }, @@ -552,6 +556,10 @@ decode_options (int argc, char **argv) eval = true; break; + case 'y': + apply = optarg; + break; + case 'q': quiet = true; break; @@ -690,6 +698,7 @@ print_help_and_exit (void) -F ALIST, --frame-parameters=ALIST\n\ Set the parameters of a new frame\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ +-y, --apply FUNC Call ELisp FUNC, passing all FILE arguments as strings\n\ -n, --no-wait Don't wait for the server to return\n\ -w, --timeout=SECONDS Seconds to wait before timing out\n\ -q, --quiet Don't display messages on success\n\ @@ -1953,7 +1962,7 @@ main (int argc, char **argv) /* Process options. */ decode_options (argc, argv); - if (! (optind < argc || eval || create_frame)) + if (! (optind < argc || eval || apply || create_frame)) { message (true, ("%s: file name or argument required\n" "Try '%s --help' for more information\n"), @@ -1961,6 +1970,14 @@ main (int argc, char **argv) exit (EXIT_FAILURE); } + if (eval && apply) + { + message (true, ("%s: can't pass both --eval and --apply\n" + "Try '%s --help' for more information\n"), + progname, progname); + exit (EXIT_FAILURE); + } + #ifdef SOCKETS_IN_FILE_SYSTEM if (tty) { @@ -2080,6 +2097,13 @@ main (int argc, char **argv) send_to_emacs (emacs_socket, " "); continue; } + else if (apply) + { + send_to_emacs (emacs_socket, "-applyarg "); + quote_argument (emacs_socket, argv[i]); + send_to_emacs (emacs_socket, " "); + continue; + } char *p = argv[i]; if (*p == '+') @@ -2136,10 +2160,18 @@ main (int argc, char **argv) send_to_emacs (emacs_socket, " "); } + if (apply) + { + send_to_emacs (emacs_socket, "-apply "); + quote_argument (emacs_socket, apply); + send_to_emacs (emacs_socket, " "); + } + + send_to_emacs (emacs_socket, "\n"); /* Wait for an answer. */ - if (!eval && !tty && !nowait && !quiet && 0 <= process_grouping ()) + if (!eval && !apply && !tty && !nowait && !quiet && 0 <= process_grouping ()) { printf ("Waiting for Emacs..."); skiplf = false; diff --git a/lisp/server.el b/lisp/server.el index c3325e5a24c..5981e90625d 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -873,6 +873,17 @@ server-eval-and-print (point-min) (point-max)))) (server-reply-print (server-quote-arg text) proc))))))) +(defun server-apply-and-print (func args proc) + "Call FUNC on ARGS and send the result back to client PROC." + (let ((v (with-local-quit (eval (apply (intern func) args) t)))) + (when proc + (with-temp-buffer + (let ((standard-output (current-buffer))) + (pp v) + (let ((text (buffer-substring-no-properties + (point-min) (point-max)))) + (server-reply-print (server-quote-arg text) proc))))))) + (defconst server-msg-size 1024 "Maximum size of a message sent to a client.") @@ -1196,6 +1207,7 @@ server-process-filter tty-type ; string. files filepos + applyargs args-left) ;; Remove this line from STRING. (setq string (substring string (match-end 0))) @@ -1323,6 +1335,28 @@ server-process-filter commands) (setq filepos nil))) + ;; -apply FUNC: Call a function on arguments. + ("-apply" + (if use-current-frame + (setq use-current-frame 'always)) + (let ((func (pop args-left))) + (if coding-system + (setq func (decode-coding-string func coding-system))) + (push (lambda () (server-apply-and-print func applyargs proc)) + commands) + (setq applyargs nil) + (setq filepos nil))) + + ;; -applyarg ARG: Add an argument for later -apply. + ("-applyarg" + (if use-current-frame + (setq use-current-frame 'always)) + (let ((arg (pop args-left))) + (if coding-system + (setq arg (decode-coding-string arg coding-system))) + (push arg applyargs) + (setq filepos nil))) + ;; -env NAME=VALUE: An environment variable. ("-env" (let ((var (pop args-left))) diff --git a/lisp/startup.el b/lisp/startup.el index 7f601668369..bb8da76bdf1 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -2531,10 +2531,11 @@ command-line-1 ;; straight away upon any --directory/-L option. splice just-files ;; t if this follows the magic -- option. + applysym applyargs ;; function and arguments for --apply ;; This includes our standard options' long versions ;; and long versions of what's on command-switch-alist. (longopts - (append '("--funcall" "--load" "--insert" "--kill" + (append '("--funcall" "--apply" "--load" "--insert" "--kill" "--dump-file" "--seccomp" "--directory" "--eval" "--execute" "--no-splash" "--find-file" "--visit" "--file" "--no-desktop") @@ -2632,6 +2633,11 @@ command-line-1 (command-execute tem) (funcall tem))) + ((member argi '("-y" "-apply")) + (setq inhibit-startup-screen t) + ;; Subsequent file args will be accumulated into applyargs. + (setq applysym (intern (or argval (pop command-line-args-left))))) + ((member argi '("-eval" "-execute")) (setq inhibit-startup-screen t) (let* ((str-expr (or argval (pop command-line-args-left))) @@ -2763,13 +2769,19 @@ command-line-1 ;; screen for -nw? (unless initial-window-system (setq inhibit-startup-screen t)) - (funcall process-file-arg orig-argi))))) + (if applysym + (push orig-argi applyargs) + (funcall process-file-arg orig-argi)))))) ;; In unusual circumstances, the execution of Lisp code due ;; to command-line options can cause the last visible frame ;; to be deleted. In this case, kill emacs to avoid an ;; abort later. - (unless (frame-live-p (selected-frame)) (kill-emacs nil))))))) + (unless (frame-live-p (selected-frame)) (kill-emacs nil)))) + + ;; Call the function specified with --apply, if any. + (when applysym + (apply applysym (nreverse applyargs)))))) (when (eq initial-buffer-choice t) ;; When `initial-buffer-choice' equals t make sure that *scratch* diff --git a/src/emacs.c b/src/emacs.c index 80a013b68df..8de40936250 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -323,6 +323,8 @@ #define MAIN_PROGRAM --file FILE visit FILE\n\ --find-file FILE visit FILE\n\ --funcall, -f FUNC call Emacs Lisp function FUNC with no arguments\n\ +--apply, -y FUNC call Emacs Lisp function FUNC, passing\n\ + subsequent positional FILE arguments as strings\n\ --insert FILE insert contents of FILE into current buffer\n\ --kill exit without asking for confirmation\n\ --load, -l FILE load Emacs Lisp FILE using the load function\n\ @@ -2648,6 +2650,7 @@ main (int argc, char **argv) option. In any case, this is entirely an internal option. */ { "-scriptload", NULL, 0, 1 }, { "-f", "--funcall", 0, 1 }, + { "-y", "--apply", 0, 1 }, { "-funcall", 0, 0, 1 }, { "-eval", "--eval", 0, 1 }, { "-execute", "--execute", 0, 1 }, -- 2.41.0