From: Reuben Thomas Date: Thu, 1 Dec 2016 15:21:57 +0000 (+0000) Subject: Add support for arguments in emacsclient's ALTERNATE_EDITOR (Bug #25082) X-Git-Tag: emacs-26.0.90~296 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=98f01a13a3bf2a4db2dcc82a342ee017326de732;p=emacs.git Add support for arguments in emacsclient's ALTERNATE_EDITOR (Bug #25082) * lib-src/emacsclient.c (fail): Parse ALTERNATE_EDITOR, or corresponding command-line argument, into quote- or space-separated tokens. If a token starts with a quote, then it naturally is expected to end with a quote; escaping is not supported. This is enough to cope with the typical case of requiring the initial path to be quoted, common on Windows where it may contain spaces. * etc/NEWS: Document. * doc/emacs/misc.texi: Likewise. * doc/man/emacsclient.1: Tweak to remove the implication that only an editor can be specified (the manual already mentions a “command”). Fix a small error where “EDITOR” is referred to rather than “ALTERNATE_EDITOR”. * test/lib-src/emacsclient-tests.el: Add tests. --- diff --git a/doc/emacs/misc.texi b/doc/emacs/misc.texi index 73a6bae767a..7602fbb7454 100644 --- a/doc/emacs/misc.texi +++ b/doc/emacs/misc.texi @@ -1821,8 +1821,10 @@ listed below: @table @samp @item -a @var{command} @itemx --alternate-editor=@var{command} -Specify a command to run if @code{emacsclient} fails to contact Emacs. +Specify a shell command to run if @code{emacsclient} fails to contact Emacs. This is useful when running @code{emacsclient} in a script. +The command may include arguments, which may be quoted "like this". +Currently, escaping of quotes is not supported. As a special exception, if @var{command} is the empty string, then @code{emacsclient} starts Emacs in daemon mode (as @command{emacs diff --git a/doc/man/emacsclient.1 b/doc/man/emacsclient.1 index 010eeba19c1..daaacab7f3e 100644 --- a/doc/man/emacsclient.1 +++ b/doc/man/emacsclient.1 @@ -62,10 +62,10 @@ A missing is treated as column 1. This option applies only to the next file specified. .TP -.B \-a, \-\-alternate-editor=EDITOR -if the Emacs server is not running, run the specified editor instead. +.B \-a, \-\-alternate-editor=COMMAND +if the Emacs server is not running, run the specified shell command instead. This can also be specified via the ALTERNATE_EDITOR environment variable. -If the value of EDITOR is the empty string, run "emacs \-\-daemon" to +If the value of ALTERNATE_EDITOR is the empty string, run "emacs \-\-daemon" to start Emacs in daemon mode, and try to connect to it. .TP .B -c, \-\-create-frame diff --git a/etc/NEWS b/etc/NEWS index ef4d8cda397..0889303f82e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -485,6 +485,13 @@ Linum mode and all similar packages are henceforth becoming obsolete. Users and developers are encouraged to switch to this new feature instead. ++++ +** emacsclient now accepts command-line options in ALTERNATE_EDITOR +and --alternate-editor. For example, ALTERNATE_EDITOR="emacs -Q -nw". +Arguments may be quoted "like this", so that for example an absolute +path containing a space may be specified; quote escaping is not +supported. + * Editing Changes in Emacs 26.1 diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index f1d4e8976da..5e181ccacb1 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -110,6 +110,9 @@ char *w32_getenv (const char *); /* Name used to invoke this program. */ const char *progname; +/* The first argument to main. */ +int main_argc; + /* The second argument to main. */ char **main_argv; @@ -201,6 +204,35 @@ xmalloc (size_t size) return result; } +/* Like realloc but get fatal error if memory is exhausted. */ + +static void * +xrealloc (void *ptr, size_t size) +{ + void *result = realloc (ptr, size); + if (result == NULL) + { + perror ("realloc"); + exit (EXIT_FAILURE); + } + return result; +} + +/* Like strdup but get a fatal error if memory is exhausted. */ +char *xstrdup (const char *); + +char * +xstrdup (const char *s) +{ + char *result = strdup (s); + if (result == NULL) + { + perror ("strdup"); + exit (EXIT_FAILURE); + } + return result; +} + /* From sysdep.c */ #if !defined (HAVE_GET_CURRENT_DIR_NAME) || defined (BROKEN_GET_CURRENT_DIR_NAME) @@ -264,21 +296,6 @@ get_current_dir_name (void) #ifdef WINDOWSNT -/* Like strdup but get a fatal error if memory is exhausted. */ -char *xstrdup (const char *); - -char * -xstrdup (const char *s) -{ - char *result = strdup (s); - if (result == NULL) - { - perror ("strdup"); - exit (EXIT_FAILURE); - } - return result; -} - #define REG_ROOT "SOFTWARE\\GNU\\Emacs" char *w32_get_resource (HKEY, const char *, LPDWORD); @@ -673,7 +690,7 @@ Report bugs with M-x report-emacs-bug.\n"); } /* Try to run a different command, or --if no alternate editor is - defined-- exit with an errorcode. + defined-- exit with an error code. Uses argv, but gets it from the global variable main_argv. */ static _Noreturn void @@ -681,9 +698,38 @@ fail (void) { if (alternate_editor) { - int i = optind - 1; + size_t extra_args_size = (main_argc - optind + 1) * sizeof (char *); + size_t new_argv_size = extra_args_size; + char **new_argv = NULL; + char *s = xstrdup (alternate_editor); + unsigned toks = 0; + + /* Unpack alternate_editor's space-separated tokens into new_argv. */ + for (char *tok = s; tok != NULL && *tok != '\0';) + { + /* Allocate new token. */ + ++toks; + new_argv = xrealloc (new_argv, new_argv_size + toks * sizeof (char *)); + + /* Skip leading delimiters, and set separator, skipping any + opening quote. */ + size_t skip = strspn (tok, " \""); + tok += skip; + char sep = (skip > 0 && tok[-1] == '"') ? '"' : ' '; + + /* Record start of token. */ + new_argv[toks - 1] = tok; + + /* Find end of token and overwrite it with NUL. */ + tok = strchr (tok, sep); + if (tok != NULL) + *tok++ = '\0'; + } + + /* Append main_argv arguments to new_argv. */ + memcpy (&new_argv[toks], main_argv + optind, extra_args_size); - execvp (alternate_editor, main_argv + i); + execvp (*new_argv, new_argv); message (true, "%s: error executing alternate editor \"%s\"\n", progname, alternate_editor); } @@ -696,6 +742,7 @@ fail (void) int main (int argc, char **argv) { + main_argc = argc; main_argv = argv; progname = argv[0]; message (true, "%s: Sorry, the Emacs server is supported only\n" @@ -1629,6 +1676,7 @@ main (int argc, char **argv) int start_daemon_if_needed; int exit_status = EXIT_SUCCESS; + main_argc = argc; main_argv = argv; progname = argv[0]; diff --git a/test/lib-src/emacsclient-tests.el b/test/lib-src/emacsclient-tests.el new file mode 100644 index 00000000000..ea757f69144 --- /dev/null +++ b/test/lib-src/emacsclient-tests.el @@ -0,0 +1,50 @@ +;;; emacsclient-tests.el --- Test emacsclient + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(require 'ert) + +(defconst emacsclient-test-emacs + (expand-file-name "emacsclient" (concat + (file-name-directory + (directory-file-name + (file-name-directory invocation-directory))) + "lib-src")) + "Path to emacsclient binary in build tree.") + +(ert-deftest emacsclient-test-alternate-editor-allows-arguments () + (let (process-environment process-environment) + (setenv "ALTERNATE_EDITOR" (concat + (expand-file-name invocation-name invocation-directory) + " --batch")) + (should (= 0 (call-process emacsclient-test-emacs nil nil nil "foo"))))) + +(ert-deftest emacsclient-test-alternate-editor-allows-quotes () + (let (process-environment process-environment) + (setenv "ALTERNATE_EDITOR" (concat + "\"" + (expand-file-name invocation-name invocation-directory) + "\"" " --batch")) + (should (= 0 (call-process emacsclient-test-emacs nil nil nil "foo"))))) + +(provide 'emacsclient-tests) +;;; emacsclient-tests.el ends here