branch: elpa/racket-mode
commit b8ff3bdcb1430a0c6e960e1cd25ba6dc634098de
Author: Björn Lindström <b...@elektrubadur.se>
Commit: Greg Hendershott <g...@greghendershott.com>

    Handle command line args to racket-program/backend
    
    The documentation already had examples of configurations like
    
        (racket-add-back-end "/ssh:headless:~/gui-project/"
                             :racket-program "xvfb-run racket")
    
    This would (accidentally?) work for remote backends, because the way the SSH
    command is invoked ends up applying shell word splitting on the program.
    
    However, when I wanted to use this locally something like this:
    
        (racket-add-backend "/project/"
                            :racket-program "podman
                                             run --rm --interactive
                                             --volume=/project/:/project/:z
                                             --workdir=/project/
                                             […]
                                             docker.io/racket/racket/8.9-full"
                                             "racket")
    
    This wouldn't work because the `executable-find' call would try to find the 
path
    to an executable with that full string as a name.
    
    This commit adds the option of defining `racket-program` (or the back-end
    `:racket-program` option) as a list rather than a string, in which case the
    first element will be used as the command to call, and the rest of the 
elements
    as command line arguments.
---
 doc/racket-mode.texi | 17 ++++++++++++---
 racket-back-end.el   | 61 ++++++++++++++++++++++++++++++----------------------
 racket-custom.el     | 12 +++++++++--
 racket-doc.el        | 14 +++++++-----
 racket-shell.el      | 17 ++++++++++-----
 5 files changed, 80 insertions(+), 41 deletions(-)

diff --git a/doc/racket-mode.texi b/doc/racket-mode.texi
index 108515c7d8..604518120b 100644
--- a/doc/racket-mode.texi
+++ b/doc/racket-mode.texi
@@ -3147,7 +3147,18 @@ Delete the ``compiled'' directories made by 
@ref{racket-mode-start-faster}.
 @node racket-program
 @subsection racket-program
 
-Pathname of the Racket executable.
+Pathname of the Racket executable or command line to launch it.
+
+@itemize
+@item
+If the value of this variable is a string, it will be interpreted as a
+simple command without arguments.
+
+@item
+If it is a list of strings, the first element will be taken to be the
+executable, and the rest of the list command line arguments to pass to
+it before any other arguments.
+@end itemize
 
 Note that a back end configuration can override this with a
 non-nil @code{racket-program} property list value. See
@@ -4195,9 +4206,9 @@ are a few examples.
 
     ;; 4. For example's sake, assume for buffers visiting
     ;; /ssh:headless:~/gui-project/ we want :racket-program instead
-    ;; to be "xvfb-run racket".
+    ;; to be '("xvfb-run" "racket").
     (racket-add-back-end "/ssh:headless:~/gui-project/"
-                         :racket-program "xvfb-run racket")
+                         :racket-program '("xvfb-run" "racket"))
 @end lisp
 
 If you use various versions of Racket by setting PATH values via
diff --git a/racket-back-end.el b/racket-back-end.el
index abad379f0c..3b44c7a423 100644
--- a/racket-back-end.el
+++ b/racket-back-end.el
@@ -196,9 +196,9 @@ are a few examples.
 
     ;; 4. For example's sake, assume for buffers visiting
     ;; /ssh:headless:~/gui-project/ we want :racket-program instead
-    ;; to be \"xvfb-run racket\".
+    ;; to be \\='(\"xvfb-run\" \"racket\").
     (racket-add-back-end \"/ssh:headless:~/gui-project/\"
-                         :racket-program \"xvfb-run racket\")
+                         :racket-program \\='(\"xvfb-run\" \"racket\"))
 #+END_SRC
 
 If you use various versions of Racket by setting PATH values via
@@ -249,7 +249,12 @@ alongside each .envrc file:
                  (signal 'wrong-type-argument (list type key v)))))
             (number-or-null-p (n) (or (not n) (numberp n))))
     (check #'stringp :directory)
-    (check #'string-or-null-p :racket-program)
+    (let ((racket-program (plist-get plist :racket-program)))
+      (if (listp racket-program)
+          (dolist (s racket-program)
+            (unless (stringp s)
+              (signal 'wrong-type-argument (list #'stringp :racket-program 
s))))
+        (check #'stringp :racket-program)))
     (when (file-remote-p (plist-get plist :directory))
       (check #'stringp :remote-source-dir)
       (check #'file-name-absolute-p :remote-source-dir))
@@ -453,29 +458,33 @@ a possibly slow remote connection."
 
 (defun racket--back-end-args->command (back-end racket-command-args)
   "Given RACKET-COMMAND-ARGS, prepend path to racket for BACK-END."
-  (if (racket--back-end-local-p back-end)
-      (cons (let ((racket-program (or (plist-get back-end :racket-program)
-                                      racket-program)))
-              (or (executable-find racket-program)
-                  (error
-                   "Cannot executable-find Racket:\n  racket-program: %S\n  
exec-path: %S"
-                   racket-program
-                   exec-path)))
-            racket-command-args)
-    (pcase-let ((`(,host ,user ,port ,_name)
-                 (racket--file-name->host+user+port+name
-                  (plist-get back-end :directory))))
-      `("ssh"
-        ,@(when port
-            `("-p" ,(format "%s" port)))
-        ,(if user
-             (format "%s@%s"
-                     user
-                     host)
-           host)
-        ,(or (plist-get back-end :racket-program)
-             racket-program) ;can't use `executable-find' remotely
-        ,@racket-command-args))))
+  (let* ((racket-program (or (plist-get back-end :racket-program)
+                             racket-program))
+         (command (if (stringp racket-program)
+                      (list racket-program)
+                    racket-program))
+         (program (car command))
+         (flags (cdr command)))
+    (if (racket--back-end-local-p back-end)
+        (let ((program (or (executable-find program)
+                           (error
+                            "Cannot executable-find Racket:\n  racket-program: 
%S\n  exec-path: %S"
+                            program
+                            exec-path))))
+          (cons program (append flags racket-command-args)))
+      (pcase-let ((`(,host ,user ,port ,_name)
+                   (racket--file-name->host+user+port+name
+                    (plist-get back-end :directory))))
+        `("ssh"
+          ,@(when port
+              `("-p" ,(format "%s" port)))
+          ,(if user
+               (format "%s@%s"
+                       user
+                       host)
+             host)
+          ,@command ;can't use `executable-find' remotely
+          ,@racket-command-args)))))
 
 ;;; File system watches
 
diff --git a/racket-custom.el b/racket-custom.el
index 1e57fcc818..f4fe09a4ba 100644
--- a/racket-custom.el
+++ b/racket-custom.el
@@ -40,12 +40,20 @@
 (defvar racket--winp (eq 'windows-nt system-type))
 
 (defcustom racket-program (if racket--winp "Racket.exe" "racket")
-  "Pathname of the Racket executable.
+  "Pathname of the Racket executable or command line to launch it.
+
+- If the value of this variable is a string, it will be interpreted as a
+  simple command without arguments.
+
+- If it is a list of strings, the first element will be taken to be the
+  executable, and the rest of the list command line arguments to pass to
+  it before any other arguments.
 
 Note that a back end configuration can override this with a
 non-nil `racket-program` property list value. See
 `racket-add-back-end'."
-  :type '(file :must-match t)
+  :type '(choice (string)
+                 (repeat string))
   :risky t)
 
 (make-obsolete-variable 'racket-command-port nil "2020-04-25")
diff --git a/racket-doc.el b/racket-doc.el
index 574be59136..4f066d9a23 100644
--- a/racket-doc.el
+++ b/racket-doc.el
@@ -60,11 +60,15 @@ Centralizes how to issue doc command and handle response 
correctly."
 
 (defun racket--search-doc-locally (str)
   (racket--doc-assert-local-back-end)
-  (call-process racket-program
-                nil ;INFILE: none
-                0   ;DESTINATION: discard/don't wait
-                nil ;DISPLAY: none
-                "-l" "raco" "docs" str))
+  (let ((command (if (stringp racket-program)
+                     (list racket-program)
+                   racket-program)))
+    (apply #'call-process `(,(car command)
+                            nil ;INFILE: none
+                            0   ;DESTINATION: discard/don't wait
+                            nil ;DISPLAY: none
+                            ,@(cdr command)
+                            "-l" "raco" "docs" ,str))))
 
 (provide 'racket-doc)
 
diff --git a/racket-shell.el b/racket-shell.el
index 711a2323ec..016c1b30cc 100644
--- a/racket-shell.el
+++ b/racket-shell.el
@@ -11,6 +11,7 @@
 (require 'racket-custom)
 (require 'racket-util)
 (require 'shell)
+(require 'subr-x)
 (require 'term)
 
 (defun racket-racket ()
@@ -34,11 +35,17 @@ variable `racket-shell-or-terminal-function'."
 
 (defun racket--shell-or-terminal (args)
   (racket--save-if-changed)
-  (let* ((exe (shell-quote-argument
-               (if (file-name-absolute-p racket-program)
-                   (expand-file-name racket-program) ;handle e.g. ~/
-                 racket-program)))
-         (cmd (concat exe " " args))
+  (let* ((command (if (stringp racket-program)
+                      (list racket-program)
+                    racket-program))
+         (program (car command))
+         (exe (shell-quote-argument
+               (if (file-name-absolute-p program)
+                   (expand-file-name program) ;handle e.g. ~/
+                 program)))
+         (flags (mapcar (lambda (x) (shell-quote-argument x))
+                        (cdr command)))
+         (cmd (concat exe " " (string-join flags " ") args))
          (win (selected-window)))
     (funcall racket-shell-or-terminal-function cmd)
     (select-window win)))

Reply via email to