;;; 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-<STREAM>* 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-<STREAM>* to *REAL-<STREAM>* 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)
|