emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Teaching emacsclient to act as a pager, and more


From: sbaugh
Subject: Re: Teaching emacsclient to act as a pager, and more
Date: Sat, 10 Sep 2016 16:15:46 -0400
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)

Stefan Monnier <address@hidden> writes:
> Or simpler:
> - emacsclient connects to emacs daemon in pretty much the normal way.
> - then (after warning the emacs daemon appropriately), turn the
>   emacsclient into a simple proxy which reads from its stdin and passes
>   that straight to the daemon's socket (and vice versa for the daemon
>   socket's output).

I produced some patches that use this approach. Even with this approach,
still not sure about the best API.

>From a5d53800a79330ab8a0e3abdaccc0091cd2d153b Mon Sep 17 00:00:00 2001
From: Spencer Baugh <address@hidden>
Date: Sat, 10 Sep 2016 12:55:41 -0400
Subject: [PATCH 1/5] emacsclient: add term_message wrapper around printf

---
 lib-src/emacsclient.c | 40 ++++++++++++++++++++++++++++------------
 1 file changed, 28 insertions(+), 12 deletions(-)

diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index 1991aaa..c9b4578 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -455,9 +455,25 @@ message (bool is_error, const char *format, ...)
   va_end (args);
 }
 
+/* Display a normal or error message, always on the terminal. */
+static void term_message (bool, const char *, ...) ATTRIBUTE_FORMAT_PRINTF (2, 
3);
+static void
+term_message (bool is_error, const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+
+  FILE *f = is_error ? stderr : stdout;
+
+  vfprintf (f, format, args);
+  fflush (f);
+
+  va_end (args);
+}
+
 /* Decode the options from argv and argc.
    The global variable `optind' will say how many arguments we used up.  */
-
 static void
 decode_options (int argc, char **argv)
 {
@@ -1502,7 +1518,7 @@ start_daemon_and_retry_set_socket (void)
     }
   else if (dpid < 0)
     {
-      fprintf (stderr, "Error: Cannot fork!\n");
+      term_message (true, "Error: Cannot fork!\n");
       exit (EXIT_FAILURE);
     }
   else
@@ -1802,7 +1818,7 @@ main (int argc, char **argv)
   /* Wait for an answer. */
   if (!eval && !tty && !nowait && !quiet)
     {
-      printf ("Waiting for Emacs...");
+      term_message (false, "Waiting for Emacs...");
       needlf = 2;
     }
   fflush (stdout);
@@ -1862,8 +1878,8 @@ main (int argc, char **argv)
               /* -print STRING: Print STRING on the terminal. */
               str = unquote_argument (p + strlen ("-print "));
               if (needlf)
-                printf ("\n");
-              printf ("%s", str);
+                term_message (false, "\n");
+              term_message (false, "%s", str);
               needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n';
             }
           else if (strprefix ("-print-nonl ", p))
@@ -1871,7 +1887,7 @@ main (int argc, char **argv)
               /* -print-nonl STRING: Print STRING on the terminal.
                  Used to continue a preceding -print command.  */
               str = unquote_argument (p + strlen ("-print-nonl "));
-              printf ("%s", str);
+              term_message (false, "%s", str);
               needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n';
             }
           else if (strprefix ("-error ", p))
@@ -1879,8 +1895,8 @@ main (int argc, char **argv)
               /* -error DESCRIPTION: Signal an error on the terminal. */
               str = unquote_argument (p + strlen ("-error "));
               if (needlf)
-                printf ("\n");
-              fprintf (stderr, "*ERROR*: %s", str);
+                term_message (false, "\n");
+              term_message (true, "*ERROR*: %s", str);
               needlf = str[0] == '\0' ? needlf : str[strlen (str) - 1] != '\n';
               exit_status = EXIT_FAILURE;
             }
@@ -1889,7 +1905,7 @@ main (int argc, char **argv)
            {
              /* -suspend: Suspend this terminal, i.e., stop the process. */
              if (needlf)
-               printf ("\n");
+               term_message (false, "\n");
              needlf = 0;
              kill (0, SIGSTOP);
            }
@@ -1898,15 +1914,15 @@ main (int argc, char **argv)
            {
              /* Unknown command. */
              if (needlf)
-               printf ("\n");
+               term_message (false, "\n");
              needlf = 0;
-             printf ("*ERROR*: Unknown message: %s\n", p);
+             term_message (true, "*ERROR*: Unknown message: %s\n", p);
            }
        }
     }
 
   if (needlf)
-    printf ("\n");
+    term_message (false, "\n");
   fflush (stdout);
   while (fdatasync (1) != 0 && errno == EINTR)
     continue;
-- 
2.9.3

>From dca1b7a8c41f0f431a6c80ac0aded42c4d7e5ec1 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <address@hidden>
Date: Sat, 10 Sep 2016 13:00:47 -0400
Subject: [PATCH 2/5] emacsclient: add --force-stderr argument

---
 lib-src/emacsclient.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index c9b4578..dfa49c5 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -119,6 +119,9 @@ int quiet = 0;
 /* Nonzero means args are expressions to be evaluated.  --eval.  */
 int eval = 0;
 
+/* Nonzero means to force all output to stderr.  --force-stderr.  */
+int force_stderr = 0;
+
 /* Nonzero means don't open a new frame.  Inverse of --create-frame.  */
 int current_frame = 1;
 
@@ -172,6 +175,7 @@ struct option longopts[] =
   { "server-file",     required_argument, NULL, 'f' },
   { "display", required_argument, NULL, 'd' },
   { "parent-id", required_argument, NULL, 'p' },
+  { "force-stderr",    no_argument,       NULL, 'r' },
   { 0, 0, 0, 0 }
 };
 
@@ -464,7 +468,7 @@ term_message (bool is_error, const char *format, ...)
 
   va_start (args, format);
 
-  FILE *f = is_error ? stderr : stdout;
+  FILE *f = (is_error || force_stderr) ? stderr : stdout;
 
   vfprintf (f, format, args);
   fflush (f);
@@ -483,9 +487,9 @@ decode_options (int argc, char **argv)
     {
       int opt = getopt_long_only (argc, argv,
 #ifndef NO_SOCKETS_IN_FILE_SYSTEM
-                            "VHneqa:s:f:d:F:tc",
+                            "VHneqa:s:f:d:F:tcr",
 #else
-                            "VHneqa:f:d:F:tc",
+                            "VHneqa:f:d:F:tcr",
 #endif
                             longopts, 0);
 
@@ -560,6 +564,10 @@ decode_options (int argc, char **argv)
           frame_parameters = optarg;
           break;
 
+        case 'r':
+          force_stderr = 1;
+          break;
+
        default:
          message (true, "Try '%s --help' for more information\n", progname);
          exit (EXIT_FAILURE);
-- 
2.9.3

>From 0f59db6be2d1d20a0c97997b3f6a77c928fe3119 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <address@hidden>
Date: Sat, 10 Sep 2016 14:26:04 -0400
Subject: [PATCH 3/5] emacsclient: find tty by looking at stderr

The POSIX standard suggests that applications which need to find the
controlling tty should look at stderr, since it's less likely to be
redirected. Let's do this.
---
 lib-src/emacsclient.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index dfa49c5..aeef93d 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -1042,7 +1042,7 @@ static int
 find_tty (const char **tty_type, const char **tty_name, int noabort)
 {
   const char *type = egetenv ("TERM");
-  const char *name = ttyname (fileno (stdout));
+  const char *name = ttyname (fileno (stderr));
 
   if (!name)
     {
-- 
2.9.3

>From de2ad77b39412cd396901008722f59ab01e0a9c1 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <address@hidden>
Date: Sat, 10 Sep 2016 14:38:41 -0400
Subject: [PATCH 4/5] server: add defvar server-emacsclient-proc

This is useful for implementing emacsclient-based paging.
---
 lisp/server.el | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lisp/server.el b/lisp/server.el
index 5300984..93b3ca4 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1262,6 +1262,12 @@ server-execute-continuation
     ;; condition-case
     (error (server-return-error proc err))))
 
+(defvar server-emacsclient-proc nil
+  "Non-nil if running commands for a client of the server.
+If we are currently evaluating Lisp in response to client commands,
+this variable contains the process for communicating with that
+client.")
+
 (defun server-execute (proc files nowait commands 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
@@ -1272,7 +1278,8 @@ server-execute
   ;; including code that needs to wait.
   (with-local-quit
     (condition-case err
-        (let ((buffers (server-visit-files files proc nowait)))
+        (let ((buffers (server-visit-files files proc nowait))
+              (server-emacsclient-proc proc))
           (mapc 'funcall (nreverse commands))
 
          ;; If we were told only to open a new client, obey
-- 
2.9.3

>From af56249cadefef58ca628a566e70a8a71e306bb9 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <address@hidden>
Date: Sat, 10 Sep 2016 15:49:33 -0400
Subject: [PATCH 5/5] emacsclient, server.el: add -proxy-stdio and use

When the client gets -proxy-stdio it will stop listening for or sending
commands and instead just blindly copy its stdin to Emacs and from Emacs
to its stdout. This mode can be activated by running a function like
server-pager through emacsclient --eval "(server-pager)".
---
 lib-src/emacsclient.c | 43 +++++++++++++++++++++++++++++++++++++++++++
 lisp/server.el        | 33 ++++++++++++++++++++++++++++++---
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c
index aeef93d..f1a2c71 100644
--- a/lib-src/emacsclient.c
+++ b/lib-src/emacsclient.c
@@ -1622,6 +1622,35 @@ start_daemon_and_retry_set_socket (void)
 #endif /* WINDOWSNT */
 }
 
+int write_all (int fd, char *buf, size_t size);
+int
+write_all (int fd, char *buf, size_t size)
+{
+  int ret;
+  while (size > 0) {
+    ret = write (fd, buf, size);
+    if (ret == -1) return -1;
+    size -= ret;
+  }
+  return 0;
+}
+
+int start_proxying (char *buf, size_t size);
+int
+start_proxying (char *buf, size_t size)
+{
+  int ret;
+
+  /* not actually proxying output from emacs to our stdout yet */
+  for (;;) {
+    ret = read (fileno (stdin), buf, size);
+    if (ret == 0) return EXIT_SUCCESS;
+    if (ret == -1) return EXIT_FAILURE;
+    ret = write_all (emacs_socket, buf, ret);
+    if (ret == -1) return EXIT_FAILURE;
+  }
+}
+
 int
 main (int argc, char **argv)
 {
@@ -1918,6 +1947,20 @@ main (int argc, char **argv)
              kill (0, SIGSTOP);
            }
 #endif
+         else if (strprefix ("-proxy-stdio ", p))
+            {
+             /* -proxy-stdio: Begin proxying stdio */
+             /* All further data sent to us by Emacs (including the
+              * rest of this message), we should write out verbatim
+              * to stdout. */
+             /* And we should start reading stdin and sending it
+              * verbatim to Emacs. */
+             if (write_all (fileno(stdout), end_p, (string + rl) - end_p) != 0)
+               return EXIT_FAILURE;
+             /* We'll do this forever and do nothing else. */
+             return start_proxying(string, sizeof(string));
+            }
+
          else
            {
              /* Unknown command. */
diff --git a/lisp/server.el b/lisp/server.el
index 93b3ca4..c47ef74 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -1279,9 +1279,11 @@ server-execute
   (with-local-quit
     (condition-case err
         (let ((buffers (server-visit-files files proc nowait))
-              (server-emacsclient-proc proc))
+              (server-emacsclient-proc proc)
+              pager-func)
           (mapc 'funcall (nreverse commands))
 
+          (setq pager-func (process-get proc 'server-client-pager-func))
          ;; If we were told only to open a new client, obey
          ;; `initial-buffer-choice' if it specifies a file
           ;; or a function.
@@ -1301,10 +1303,17 @@ server-execute
             ;; Client requested nowait; return immediately.
             (server-log "Close nowait client" proc)
             (server-delete-client proc))
-           ((and (not dontkill) (null buffers))
+           ((and (not dontkill) (null buffers) (not pager-func))
             ;; This client is empty; get rid of it immediately.
             (server-log "Close empty client" proc)
-            (server-delete-client proc)))
+            (server-delete-client proc))
+           ((and (not dontkill) (null buffers) pager-func)
+            ;; We've been instructed to turn this emacsclient into a pager
+            ;; this passes it outside the control of server.el, so for us it's 
like killing it
+            (server-send-string proc "-proxy-stdio \n")
+            ;; pager-func should set up a new process-filter and return 
promptly
+            (funcall pager-func proc)
+            (setq server-clients (delq proc server-clients))))
           (cond
            ((or isearch-mode (minibufferp))
             nil)
@@ -1324,6 +1333,24 @@ server-execute
          (message "Quit emacsclient request"))
        (server-return-error proc err)))))
 
+(defun server-pager ()
+  "Create a buffer holding the stdin of the current client.
+This function will create a buffer *pager* which will receive
+input from the stdin of the current emacsclient.
+
+This function should only be run by passing --eval to emacsclient,
+like so:
+   echo some data | emacsclient --eval '(server-pager)'"
+  (when (null server-emacsclient-proc)
+    (error "Cannot be run out of emacsclient --eval context"))
+  (process-put server-emacsclient-proc 'server-client-pager-func
+               (lambda (proc)
+                 (let ((buffer (generate-new-buffer "*pager*")))
+                   (set-process-buffer proc buffer)
+                   (set-process-filter proc nil)
+                   (pop-to-buffer buffer))))
+  nil)
+
 (defun server-return-error (proc err)
   (ignore-errors
     (server-send-string
-- 
2.9.3


reply via email to

[Prev in Thread] Current Thread [Next in Thread]