Internal concepts
A buffer contains text to display on screen, and might be associated to a file, but might not be.
Buffers are regular CLOS objects with many slots:
name
temporary (boolean)
filename
directory
read-only-p
major-mode
minor-modes
point
last-write-date
…
A text-buffer
is a class and type for text buffers.
The variable *current-buffer*
holds the current buffer, the one we
are visiting in Lem. There will always be at least one buffer, even if
it is the primordial one named *tmp*
.
We don’t directly access a buffer’s slots, but we use small wrapper functions, such as:
buffer-name
buffer-filename
buffer-directory
…
They are setf
-able, and work by default on the current-buffer.
Other manipulation functions include:
(current-buffer)
(ensure-buffer)
(get-buffer)
;; If 'buffer-or-name' is a buffer, it is returned. If it is a string, it returns the buffer with that name.
See:
See:
(buffer-list)
(delete-buffer)
(get-next-buffer)
(get-previous-buffer)
(defun get-file-buffer (filename)
;; "Return the buffer corresponding to 'filename' or NIL if not found."
etc
A buffer object might hold any other data in its variables
slot (to
get with buffer-variables
), a hash-table that associates a variable
key to its values.
The function buffer-value buffer name &optional default
is used to
access these variables. It is setf
-able.
This feature attaches “attached-window” to the bottom of a specific window. An “attached-window” has an “attached-buffer”. When you attach an attached-window to a specific window, that window becomes the parent window from the perspective of the attached-window. The attached window remains visible even when the parent window is scrolled.
It’s like a comment input form on slack or discord.
They were initially added for the Claude Code integration.

The point is the cursor’s location.
You may use these functions:
(current-point)
(copy-point)
(point=) ;; and other comparison functions
(point-max)
(move-point point new-point)
The macro (save-excursion …)
will run its body and preserve the
point position from before the macro was called.
(save-excursion
(move-point (current-point) point)
(prompt-for-string
"Say hi: "))
See:
Each created point creates an object in memory. After being used, a
point should be discarded. You can use make-point
and
delete-point
, or use the macro with-point
.
(with-point ((p3 expr1)
(p1 expr2 :left-inserting)
(p2 expr3 :right-inserting))
...)
Prompts are these interactive pop-up windows that allow you to search and TAB-complete commands, files, etc.
Some prompt functions are already defined for a diversity of use-cases:
prompt-for-y-or-n-p
prompt-for-string
prompt-for-integer
prompt-for-buffer
prompt-for-file
prompt-for-directory
prompt-for-command
prompt-for-library
prompt-for-encodings
Each may define a prompt string, an initial value, a completion
function, a :test-function
(as in the Lisp built-ins :test
key
parameter), and a history-symbol
Their signature are usually:
(prompt-for-string string
:initial-value string
:history-symbol '*symbol*
:completion-function #'function
:test-function #'function)
See:
This is enough to prompt for a string:
(prompt-for-string "New string: ")
A use in the wild might look like:
(prompt-for-string (or prompt "Branch: ")
:initial-value current-branch
:history-symbol '*legit-branches-history*
:completion-function (lambda (x) (completion-strings x candidates))
:test-function (lambda (name) (member name candidates :test #'string=)))
We may use prompts that open at the current point, on the current line, and have no borders: they are not a pop-up window.
;; in directory-mode
(defun prompt-for-rename-file (point)
"Open an inlined prompt at point to rename the file."
(let ((file (current-file point)))
(save-excursion
(move-point (current-point) point)
(prompt-for-string
"New name: "
:initial-value (if file (file-namestring file) "")
:test-function (lambda (string)
(not (alexandria:emptyp string)))
:gravity :cursor
:use-border nil))))
Lem makes it easy to display buttons, with text and/or icons: place them at a given point, define an action to run on click, and voilà:
(in-package :lem)
(defun btn-clicked ()
(message "clicked!"))
(lem/button:insert-button (current-point) "hello btn" #'btn-clicked)
This “hello btn” text isn’t styled in any way, it is just text. But you can click on it and trigger an action.
Clicking might not work in your terminal, though.
The full insert-button
signature is:
(defun insert-button (point text &optional callback &rest plist)
It accepts any other argument as a plist. This can be used to give:
:button-tag
:attribute
or any other key-value pair to the make-button
constructor.
Buttons are used in the lisp inspector, in the dashboard, in the Claude Code window, and other places.
See:
You can add an icon to your button:
(lem/button:insert-button point (lem:icon-string "down-pointing-triangle") #'btn-clicked)