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)))))