;; An experimental implementation of multiple REPLs multiplexed over a
;; single Slime socket. M-x slime-new-mrepl creates a new REPL buffer.
;;
(require 'slime)
(require 'inferior-slime) ; inferior-slime-indent-lime
(require 'cl-lib)
(define-slime-contrib slime-mrepl
"Multiple REPLs."
(:authors "Helmut Eller <heller@common-lisp.net>")
(:license "GPL")
(:swank-dependencies swank-mrepl))
(require 'comint)
(defvar slime-mrepl-remote-channel nil)
(defvar slime-mrepl-expect-sexp nil)
(define-derived-mode slime-mrepl-mode comint-mode "mrepl"
;; idea lifted from ielm
(unless (get-buffer-process (current-buffer))
(let* ((process-connection-type nil)
(proc (start-process "mrepl (dummy)" (current-buffer) "hexl")))
(set-process-query-on-exit-flag proc nil)))
(set (make-local-variable 'comint-use-prompt-regexp) nil)
(set (make-local-variable 'comint-inhibit-carriage-motion) t)
(set (make-local-variable 'comint-input-sender) 'slime-mrepl-input-sender)
(set (make-local-variable 'comint-output-filter-functions) nil)
(set (make-local-variable 'slime-mrepl-expect-sexp) t)
;;(set (make-local-variable 'comint-get-old-input) 'ielm-get-old-input)
(set-syntax-table lisp-mode-syntax-table)
)
(slime-define-keys slime-mrepl-mode-map
((kbd "RET") 'slime-mrepl-return)
([return] 'slime-mrepl-return)
;;((kbd "TAB") 'slime-indent-and-complete-symbol)
((kbd "C-c C-b") 'slime-interrupt)
((kbd "C-c C-c") 'slime-interrupt))
(defun slime-mrepl-process% () (get-buffer-process (current-buffer))) ;stupid
(defun slime-mrepl-mark () (process-mark (slime-mrepl-process%)))
(defun slime-mrepl-insert (string)
(comint-output-filter (slime-mrepl-process%) string))
(slime-define-channel-type listener)
(slime-define-channel-method listener :prompt (package prompt)
(with-current-buffer (slime-channel-get self 'buffer)
(slime-mrepl-prompt package prompt)))
(defun slime-mrepl-prompt (package prompt)
(setf slime-buffer-package package)
(slime-mrepl-insert (format "%s%s> "
(cl-case (current-column)
(0 "")
(t "\n"))
prompt))
(slime-mrepl-recenter))
(defun slime-mrepl-recenter ()
(when (get-buffer-window)
(recenter -1)))
(slime-define-channel-method listener :write-result (result)
(with-current-buffer (slime-channel-get self 'buffer)
(goto-char (point-max))
(slime-mrepl-insert result)))
(slime-define-channel-method listener :evaluation-aborted ()
(with-current-buffer (slime-channel-get self 'buffer)
(goto-char (point-max))
(slime-mrepl-insert "; Evaluation aborted\n")))
(slime-define-channel-method listener :write-string (string)
(slime-mrepl-write-string self string))
(defun slime-mrepl-write-string (self string)
(with-current-buffer (slime-channel-get self 'buffer)
(goto-char (slime-mrepl-mark))
(slime-mrepl-insert string)))
(slime-define-channel-method listener :set-read-mode (mode)
(with-current-buffer (slime-channel-get self 'buffer)
(cl-ecase mode
(:read (setq slime-mrepl-expect-sexp nil)
(message "[Listener is waiting for input]"))
(:eval (setq slime-mrepl-expect-sexp t)))))
(defun slime-mrepl-return (&optional end-of-input)
(interactive "P")
(slime-check-connected)
(goto-char (point-max))
(cond ((and slime-mrepl-expect-sexp
(or (slime-input-complete-p (slime-mrepl-mark) (point))
end-of-input))
(comint-send-input))
((not slime-mrepl-expect-sexp)
(unless end-of-input
(insert "\n"))
(comint-send-input t))
(t
(insert "\n")
(inferior-slime-indent-line)
(message "[input not complete]")))
(slime-mrepl-recenter))
(defun slime-mrepl-input-sender (proc string)
(slime-mrepl-send-string (substring-no-properties string)))
(defun slime-mrepl-send-string (string &optional command-string)
(slime-mrepl-send `(:process ,string)))
(defun slime-mrepl-send (msg)
"Send MSG to the remote channel."
(slime-send-to-remote-channel slime-mrepl-remote-channel msg))
(defun slime-new-mrepl ()
"Create a new listener window."
(interactive)
(let ((channel (slime-make-channel slime-listener-channel-methods)))
(slime-eval-async
`(swank-mrepl:create-mrepl ,(slime-channel.id channel))
(slime-rcurry
(lambda (result channel)
(cl-destructuring-bind (remote thread-id package prompt) result
(pop-to-buffer (generate-new-buffer (slime-buffer-name :mrepl)))
(slime-mrepl-mode)
(setq slime-current-thread thread-id)
(setq slime-buffer-connection (slime-connection))
(set (make-local-variable 'slime-mrepl-remote-channel) remote)
(slime-channel-put channel 'buffer (current-buffer))
(slime-channel-send channel `(:prompt ,package ,prompt))))
channel))))
(defun slime-mrepl ()
(let ((conn (slime-connection)))
(cl-find-if (lambda (x)
(with-current-buffer x
(and (eq major-mode 'slime-mrepl-mode)
(eq (slime-current-connection) conn))))
(buffer-list))))
(def-slime-selector-method ?m
"First mrepl-buffer"
(or (slime-mrepl)
(error "No mrepl buffer (%s)" (slime-connection-name))))
(provide 'slime-mrepl)