Skip to content

Latest commit

 

History

History
137 lines (108 loc) · 4.31 KB

slime.md

File metadata and controls

137 lines (108 loc) · 4.31 KB

Working with swank/slime

Since ncurses is single threaded, it is only ever allowed to call ncurses functions from the thread that initialized the screen. As this thread must be the one controlling the terminal, ncurses must only be called from the REPL prompt directly. With a recent enough croatoan version, slime/swank does work though, but it requires a bit of extra work. The following is an example of how one would work with croatoan using slime/swank. First, a bit of boiler plate:

;; load croatoan and swank
(eval-when (:compile-toplevel :execute :load-toplevel)
  (ql:quickload '(:croatoan :swank)))

;; Defining a package is always a good idea
(defpackage #:scratch
  (:use #:cl)
  (:export #:main))

(in-package #:scratch)

;; The main screen, we need a global so that we can access it from
;; slime
(defparameter *scr* nil)

;; Main entry point, to be called from the terminal thread, it will
;; initialize the screen and enter the event loop
(defun main ()
  (croatoan:with-screen (scr
                         ;; Set input blocking to 100 ms. This _must_
                         ;; be set for swank to work, otherwise
                         ;; get-event will block and croatoan only
                         ;; polls the job queue when a key is pressed.
                         :input-blocking 100
                         ;; Do not override the swank debugger hook,
                         ;; as we want to enter the slime debugger in
                         ;; emacs when a error occurs.
                         :bind-debugger-hook nil)
    ;; Set *scr* to the initilized scr so that we can access it form
    ;; the swank thread and then enter the event-loop.
    (croatoan:run-event-loop (setf *scr* scr))))

;; Initialize swank, setting dont-close will prevent the server from
;; shutting down in case slime disconnects. The default port is 4005,
;; one can specify a different one with :port.
(swank:create-server :dont-close t)

;; Initialize screen and enter the event loop
(main)

This should be saved in a file and then loaded directly from sbcl (or any other CL Implementation known to work with croatoan). For sbcl this would be (when saved to scratch.lisp):

sbcl --load scratch.lisp

The user should now see a blank terminal window with the cursor placed at the top left corner. Time to connect to it from slime, from within emacs run:

M-x slime-connect

Once started, we need to change into the right package, do this by entering (inside the SLIME repl):

CL-USER> (in-package :scratch)

To now run something inside the terminal thread one must use CROATOAN:SUBMIT:

SCRATCH> (croatoan:submit (croatoan:add-string *scr* "Hey!"))

And voila, thats the basics of working with croatoan from slime! The next thing one might want to do is set some keybindings, for example, bind c to clear the screen:

SCRATCH> (croatoan:submit
           (croatoan:bind *scr* #\c (lambda (win event)
                                      (croatoan:clear *scr*))))

And q to quit the event loop:

SCRATCH> (croatoan:submit
           (croatoan:bind *scr* #\q 'croatoan:exit-event-loop))

There is one caveat though, *STANDARD-OUTPUT* differs between the swank and the terminal thread. It is set to the swank output stream (which prints everything to the slime repl) inside the swank thread, and bound to the terminal stdin/stdout inside the terminal thread. Hence doing the following, would mess up the terminal screen instead of printing to the slime repl:

SCRATCH> (croatoan:submit (format t "Hellou!~%"))

What we need is a way to refer to the swank stream, which can be easily achieved by setting another global to whatever stream *STANDARD-OUTPUT* points to inside the slime repl:

SCRATCH> (defparameter *swank-output* *standard-output*)

If we now run:

SCRATCH> (croatoan:submit (format *swank-output* "Hellou!~%"))

We should see Hellou! printed in the slime repl.

This is quite helpful, especially when debugging something within the terminal thread:

SCRATCH> (croatoan:submit
           (croatoan:bind *scr* :resize
                          (lambda (win event)
                            (format *swank-output* "Terminal resized to width: ~a, height: ~a~%"
                                    (croatoan:width win)
                                    (croatoan:height win)))))