;;; swank-repl.lisp --- Server side part of the Lisp listener. ;; ;; License: public domain (in-package swank) (defpackage swank-repl (:use cl swank/backend) (:export *send-repl-results-function*) (:import-from swank *default-worker-thread-bindings* *loopback-interface* add-hook *connection-closed-hook* eval-region with-buffer-syntax connection connection.socket-io connection.repl-results connection.user-input connection.user-output connection.user-io connection.trace-output connection.dedicated-output connection.env multithreaded-connection mconn.active-threads mconn.repl-thread mconn.auto-flush-thread use-threads-p *emacs-connection* default-connection with-connection send-to-emacs *communication-style* handle-requests wait-for-event make-tag thread-for-evaluation socket-quest authenticate-client encode-message auto-flush-loop clear-user-input current-thread-id cat with-struct* with-retry-restart with-bindings package-string-for-prompt find-external-format-or-lose defslimefun ;; FIXME: those should be exported from swank-repl only, but how to ;; do that whithout breaking init files? *use-dedicated-output-stream* *dedicated-output-stream-port* *globally-redirect-io*)) (in-package swank-repl) (defvar *use-dedicated-output-stream* nil "When T swank will attempt to create a second connection to Emacs which is used just to send output.") (defvar *dedicated-output-stream-port* 0 "Which port we should use for the dedicated output stream.") (defvar *dedicated-output-stream-buffering* (if (eq *communication-style* :spawn) t nil) "The buffering scheme that should be used for the output stream. Valid values are nil, t, :line") (defvar *globally-redirect-io* :started-from-emacs "When T globally redirect all standard streams to Emacs. When :STARTED-FROM-EMACS redirect when launched by M-x slime") (defun globally-redirect-io-p () (case *globally-redirect-io* ((t) t) (:started-from-emacs swank-loader:*started-from-emacs*))) (defun open-streams (connection properties) "Return the 5 streams for IO redirection: DEDICATED-OUTPUT INPUT OUTPUT IO REPL-RESULTS" (let* ((input-fn (lambda () (with-connection (connection) (with-simple-restart (abort-read "Abort reading input from Emacs.") (read-user-input-from-emacs))))) (dedicated-output (if *use-dedicated-output-stream* (open-dedicated-output-stream connection (getf properties :coding-system)))) (in (make-input-stream input-fn)) (out (or dedicated-output (make-output-stream (make-output-function connection)))) (io (make-two-way-stream in out)) (repl-results (swank:make-output-stream-for-target connection :repl-result))) (typecase connection (multithreaded-connection (setf (mconn.auto-flush-thread connection) (make-auto-flush-thread out)))) (values dedicated-output in out io repl-results))) (defun make-output-function (connection) "Create function to send user output to Emacs." (lambda (string) (with-connection (connection) (send-to-emacs `(:write-string ,string))))) (defun open-dedicated-output-stream (connection coding-system) "Open a dedicated output connection to the Emacs on SOCKET-IO. Return an output stream suitable for writing program output. This is an optimized way for Lisp to deliver output to Emacs." (let ((socket (socket-quest *dedicated-output-stream-port* nil)) (ef (find-external-format-or-lose coding-system))) (unwind-protect (let ((port (local-port socket))) (encode-message `(:open-dedicated-output-stream ,port ,coding-system) (connection.socket-io connection)) (let ((dedicated (accept-connection socket :external-format ef :buffering *dedicated-output-stream-buffering* :timeout 30))) (authenticate-client dedicated) (close-socket socket) (setf socket nil) dedicated)) (when socket (close-socket socket))))) (defmethod thread-for-evaluation ((connection multithreaded-connection) (id (eql :find-existing))) (or (car (mconn.active-threads connection)) (find-repl-thread connection))) (defmethod thread-for-evaluation ((connection multithreaded-connection) (id (eql :repl-thread))) (find-repl-thread connection)) (defun find-repl-thread (connection) (cond ((not (use-threads-p)) (current-thread)) (t (let ((thread (mconn.repl-thread connection))) (cond ((not thread) nil) ((thread-alive-p thread) thread) (t (setf (mconn.repl-thread connection) (spawn-repl-thread connection "new-repl-thread")))))))) (defun spawn-repl-thread (connection name) (spawn (lambda () (with-bindings *default-worker-thread-bindings* (repl-loop connection))) :name name)) (defun repl-loop (connection) (handle-requests connection)) ;;;;; Redirection during requests ;;; ;;; We always redirect the standard streams to Emacs while evaluating ;;; an RPC. This is done with simple dynamic bindings. (defslimefun create-repl (target &key coding-system) (assert (eq target nil)) (let ((conn *emacs-connection*)) (initialize-streams-for-connection conn `(:coding-system ,coding-system)) (with-struct* (connection. @ conn) (setf (@ env) `((*standard-input* . ,(@ user-input)) ,@(unless (globally-redirect-io-p) `((*standard-output* . ,(@ user-output)) (*trace-output* . ,(or (@ trace-output) (@ user-output))) (*error-output* . ,(@ user-output)) (*debug-io* . ,(@ user-io)) (*query-io* . ,(@ user-io)) (*terminal-io* . ,(@ user-io)))))) (maybe-redirect-global-io conn) (add-hook *connection-closed-hook* 'update-redirection-after-close) (typecase conn (multithreaded-connection (setf (mconn.repl-thread conn) (spawn-repl-thread conn "repl-thread")))) (list (package-name *package*) (package-string-for-prompt *package*))))) (defun initialize-streams-for-connection (connection properties) (multiple-value-bind (dedicated in out io repl-results) (open-streams connection properties) (setf (connection.dedicated-output connection) dedicated (connection.user-io connection) io (connection.user-output connection) out (connection.user-input connection) in (connection.repl-results connection) repl-results) connection)) (defun read-user-input-from-emacs () (let ((tag (make-tag))) (force-output) (send-to-emacs `(:read-string ,(current-thread-id) ,tag)) (let ((ok nil)) (unwind-protect (prog1 (caddr (wait-for-event `(:emacs-return-string ,tag value))) (setq ok t)) (unless ok (send-to-emacs `(:read-aborted ,(current-thread-id) ,tag))))))) ;;;;; Listener eval (defvar *listener-eval-function* 'repl-eval) (defvar *listener-saved-value* nil) (defslimefun listener-save-value (slimefun &rest args) "Apply SLIMEFUN to ARGS and save the value. The saved value should be visible to all threads and retrieved via LISTENER-GET-VALUE." (setq *listener-saved-value* (apply slimefun args)) t) (defslimefun listener-get-value () "Get the last value saved by LISTENER-SAVE-VALUE. The value should be produced as if it were requested through LISTENER-EVAL directly, so that spacial variables *, etc are set." (listener-eval (let ((*package* (find-package :keyword))) (write-to-string '*listener-saved-value*)))) (defslimefun listener-eval (string &key (window-width nil window-width-p)) (if window-width-p (let ((*print-right-margin* window-width)) (funcall *listener-eval-function* string)) (funcall *listener-eval-function* string))) (defslimefun clear-repl-variables () (let ((variables '(*** ** * /// // / +++ ++ +))) (loop for variable in variables do (setf (symbol-value variable) nil)))) (defvar *send-repl-results-function* 'send-repl-results-to-emacs) (defun repl-eval (string) (clear-user-input) (with-buffer-syntax () (with-retry-restart (:msg "Retry SLIME REPL evaluation request.") (track-package (lambda () (multiple-value-bind (values last-form) (eval-region string) (setq *** ** ** * * (car values) /// // // / / values +++ ++ ++ + + last-form) (funcall *send-repl-results-function* values)))))) nil) (defun track-package (fun) (let ((p *package*)) (unwind-protect (funcall fun) (unless (eq *package* p) (send-to-emacs (list :new-package (package-name *package*) (package-string-for-prompt *package*))))))) (defun send-repl-results-to-emacs (values) (finish-output) (if (null values) (send-to-emacs `(:write-string "; No value" :repl-result)) (dolist (v values) (send-to-emacs `(:write-string ,(cat (prin1-to-string v) #\newline) :repl-result))))) (defslimefun redirect-trace-output (target) (setf (connection.trace-output *emacs-connection*) (swank:make-output-stream-for-target *emacs-connection* target)) nil) ;;;; IO to Emacs ;;; ;;; This code handles redirection of the standard I/O streams ;;; (`*standard-output*', etc) into Emacs. The `connection' structure ;;; contains the appropriate streams, so all we have to do is make the ;;; right bindings. ;;;;; Global I/O redirection framework ;;; ;;; Optionally, the top-level global bindings of the standard streams ;;; can be assigned to be redirected to Emacs. When Emacs connects we ;;; redirect the streams into the connection, and they keep going into ;;; that connection even if more are established. If the connection ;;; handling the streams closes then another is chosen, or if there ;;; are no connections then we revert to the original (real) streams. ;;; ;;; It is slightly tricky to assign the global values of standard ;;; streams because they are often shadowed by dynamic bindings. We ;;; solve this problem by introducing an extra indirection via synonym ;;; streams, so that *STANDARD-INPUT* is a synonym stream to ;;; *CURRENT-STANDARD-INPUT*, etc. We never shadow the "current" ;;; variables, so they can always be assigned to affect a global ;;; change. ;;;;; Global redirection setup (defvar *saved-global-streams* '() "A plist to save and restore redirected stream objects. E.g. the value for '*standard-output* holds the stream object for *standard-output* before we install our redirection.") (defun setup-stream-indirection (stream-var &optional stream) "Setup redirection scaffolding for a global stream variable. Supposing (for example) STREAM-VAR is *STANDARD-INPUT*, this macro: 1. Saves the value of *STANDARD-INPUT* in `*SAVED-GLOBAL-STREAMS*'. 2. Creates *CURRENT-STANDARD-INPUT*, initially with the same value as *STANDARD-INPUT*. 3. Assigns *STANDARD-INPUT* to a synonym stream pointing to *CURRENT-STANDARD-INPUT*. This has the effect of making *CURRENT-STANDARD-INPUT* contain the effective global value for *STANDARD-INPUT*. This way we can assign the effective global value even when *STANDARD-INPUT* is shadowed by a dynamic binding." (let ((current-stream-var (prefixed-var '#:current stream-var)) (stream (or stream (symbol-value stream-var)))) ;; Save the real stream value for the future. (setf (getf *saved-global-streams* stream-var) stream) ;; Define a new variable for the effective stream. ;; This can be reassigned. (proclaim `(special ,current-stream-var)) (set current-stream-var stream) ;; Assign the real binding as a synonym for the current one. (let ((stream (make-synonym-stream current-stream-var))) (set stream-var stream) (set-default-initial-binding stream-var `(quote ,stream))))) (defun prefixed-var (prefix variable-symbol) "(PREFIXED-VAR \"FOO\" '*BAR*) => SWANK::*FOO-BAR*" (let ((basename (subseq (symbol-name variable-symbol) 1))) (intern (format nil "*~A-~A" (string prefix) basename) :swank))) (defvar *standard-output-streams* '(*standard-output* *error-output* *trace-output*) "The symbols naming standard output streams.") (defvar *standard-input-streams* '(*standard-input*) "The symbols naming standard input streams.") (defvar *standard-io-streams* '(*debug-io* *query-io* *terminal-io*) "The symbols naming standard io streams.") (defun init-global-stream-redirection () (when (globally-redirect-io-p) (cond (*saved-global-streams* (warn "Streams already redirected.")) (t (mapc #'setup-stream-indirection (append *standard-output-streams* *standard-input-streams* *standard-io-streams*)))))) (defun globally-redirect-io-to-connection (connection) "Set the standard I/O streams to redirect to CONNECTION. Assigns *CURRENT-* for all standard streams." (dolist (o *standard-output-streams*) (set (prefixed-var '#:current o) (connection.user-output connection))) ;; FIXME: If we redirect standard input to Emacs then we get the ;; regular Lisp top-level trying to read from our REPL. ;; ;; Perhaps the ideal would be for the real top-level to run in a ;; thread with local bindings for all the standard streams. Failing ;; that we probably would like to inhibit it from reading while ;; Emacs is connected. ;; ;; Meanwhile we just leave *standard-input* alone. #+NIL (dolist (i *standard-input-streams*) (set (prefixed-var '#:current i) (connection.user-input connection))) (dolist (io *standard-io-streams*) (set (prefixed-var '#:current io) (connection.user-io connection)))) (defun revert-global-io-redirection () "Set *CURRENT-* to *REAL-* for all standard streams." (dolist (stream-var (append *standard-output-streams* *standard-input-streams* *standard-io-streams*)) (set (prefixed-var '#:current stream-var) (getf *saved-global-streams* stream-var)))) ;;;;; Global redirection hooks (defvar *global-stdio-connection* nil "The connection to which standard I/O streams are globally redirected. NIL if streams are not globally redirected.") (defun maybe-redirect-global-io (connection) "Consider globally redirecting to CONNECTION." (when (and (globally-redirect-io-p) (null *global-stdio-connection*) (connection.user-io connection)) (unless *saved-global-streams* (init-global-stream-redirection)) (setq *global-stdio-connection* connection) (globally-redirect-io-to-connection connection))) (defun update-redirection-after-close (closed-connection) "Update redirection after a connection closes." (check-type closed-connection connection) (when (eq *global-stdio-connection* closed-connection) (if (and (default-connection) (globally-redirect-io-p)) ;; Redirect to another connection. (globally-redirect-io-to-connection (default-connection)) ;; No more connections, revert to the real streams. (progn (revert-global-io-redirection) (setq *global-stdio-connection* nil))))) (provide :swank-repl)