Introduction
In Lem, listener-mode
is equivalent to Emacs’ comint-mode
. You use it to create interactive buffers, like a REPL or terminal.
Defining the mode
The first thing to do when creating a listener interface, is to define a major mode.
(define-major-mode my-listener-mode nil
(:name "my-listener-mode"
:keymap *my-listener-mode-keymap*)
(reset-listener-vars
('listener-set-prompt-function . #'identity)
('listener-check-input-function . (constantly t))
('listener-execute-function . 'execute-input)
('listener-prompt-attribute . nil))
(start-listener-mode))
This is the most basic definition for a listener mode, the most important thing to notice is 'execute-input
. This is the function that handles the input into the interface when you press enter.
Our mode definition uses the following macro to help things more concise, it sets listener-mode variables for the current buffer.
(defmacro reset-listener-vars (&body pairs)
`(progn
,@(mapcar
(lambda (it)
`(setf (variable-value ,(car it) :buffer (current-buffer)) ,(cdr it)))
pairs)))
Handling input
Now, lets define the function that handles input:
(defun execute-input (point string)
(line-up-last
(read-from-string (car (last (str:lines string))))
(eval)
(format nil "~a~%")
(insert-string (buffer-end-point (point-buffer point)))))
This is just the most basic REPL I could come up with, it is not very efficient since it reads the whole buffer and not just the last line.
The result of any input is inserted back into the buffer by the last line.
Exposing an interface
We’re done with all the hard parts, now we will make a function that creates a buffer and starts our listener.
(defun get-my-listener-buffer ()
(let ((buffer (make-buffer "*my-listener*")))
(unless (eq (buffer-major-mode buffer) 'my-listener-mode)
(change-buffer-mode buffer 'my-listener-mode))
buffer))
To expose it to the user, create a command like this:
(define-command run-my-listener () ()
(pop-to-buffer (get-my-listener-buffer)))
Using the new REPL
Now you should be able to use M-x run-my-listener
to open your REPL.
Complete code
(defpackage #:my-listener
(:use :cl :alexandria-2 :lem :lem/listener-mode))
(in-package :my-listener)
(defmacro reset-listener-vars (&body pairs)
`(progn
,@(mapcar
(lambda (it)
`(setf (variable-value ,(car it) :buffer (current-buffer)) ,(cdr it)))
pairs)))
(define-major-mode my-listener-mode nil
(:name "my-listener-mode"
:keymap *my-listener-mode-keymap*)
(reset-listener-vars
('listener-set-prompt-function . #'identity)
('listener-check-input-function . (constantly t))
('listener-execute-function . 'execute-input)
('listener-prompt-attribute . nil))
(start-listener-mode))
(defun execute-input (point string)
(line-up-last
(read-from-string (car (last (str:lines string))))
(eval)
(format nil "~a~%")
(insert-string (buffer-end-point (point-buffer point)))))
(defun get-my-listener-buffer ()
(let ((buffer (make-buffer "*my-listener*")))
(unless (eq (buffer-major-mode buffer) 'my-listener-mode)
(change-buffer-mode buffer 'my-listener-mode))
buffer))
(define-command run-my-listener () ()
(pop-to-buffer (get-my-listener-buffer)))