Configuration
Lem loads ~/.lem/init.lisp
when starting up.
You probably want to start it with
(in-package :lem-user)
Otherwise, be sure to refer to Lem functions with the lem:
prefix.
Use the define-key
function:
(lem:define-key lem:*global-keymap*
"C-x F" 'lem-core/commands/file:find-file-recursively)
Its first argument is a keymap. Essentially, there is a keymap for
global keybindings, for each programming language mode (such as
lem-python-mode::*python-mode-keymap*
, you can find it in Lem with
the autocompletion), and for every other tool: there is a keymap for
grep
mode, for the directory mode, for the different vi mode states,
etc.
You can also use lem:define-keys
to define more than one key at once:
(define-keys *global-keymap*
("C-a b" 'describe-bindings)
("C-a k" 'describe-key)
("C-a a" 'lem-lisp-mode:lisp-apropos)
("C-a c" 'apropos-command)
("C-a p" 'lem-lisp-mode:lisp-apropos-package)
("C-a f" 'lem-lisp-mode:lisp-describe-symbol))
But wait, there is more.
Did you notice that all the above commands have
the C-a
prefix? We can refactor this.
First, we define a keymap object. Let’s take another example, the frame-multiplexer (aka tabs):
(defvar *keymap*
(make-keymap :name '*frame-multiplexer-keymap*)
"Keymap for commands related to the frame-multiplexer.")
Now we can use (define-key *keymap* key command)
(singular), like this:
(define-key *keymap* "c" 'frame-multiplexer-create-with-new-buffer-list)
(define-key *keymap* "d" 'frame-multiplexer-delete)
(define-key *keymap* "p" 'frame-multiplexer-prev)
(define-key *keymap* "n" 'frame-multiplexer-next)
(define-key *keymap* "r" 'frame-mulitplexer-rename)
And now we can define the prefix key for our keymap and all its sub-keys commands:
(define-key *global-keymap* "C-z" *keymap*)
As a result, we defined C-z c
for 'frame-mulitplexer-create-with-new-buffer-list
.
Use define-command
:
(in-package :lem)
(define-command open-init-file () ()
;; @sasanidas
(lem:find-file
(merge-pathnames "init.lisp" (lem-home))))
or copy-paste this with M-:
(lem:define-command open-init-file () ()
(lem:find-file
(merge-pathnames "init.lisp" (lem:lem-home))))
You can now call it with M-x open-init-file
.
How can we give an argument to a command, how can we run it on a region?
The define-command
examples above have two empty () ()
. This is
the place for parameters names and special argument descriptors.
We can tell it to accept a prefix argument (a number), we can make the command ask for user input (a string, an integer, a buffer, a file) and work on region delimiters.
We tell the following command that it accepts a prefix
argument. We use “p” and n
is the argument name to use in the
command body. n
defaults to 1.
(define-command hellos (n) ("p")
(dotimes (i n)
(insert-string (current-point) "hello ")))
Call this command M-x hellos
and it writes 1 “hello”.
Call it with C-u 3 M-x hellos
and it writes “hello” 3 times.
We can also use (&optional n) ("P")
or (&optional (n 3)) ("P")
to
change the default value.
“P” is similar to “p” but doesn’t default to 1.
Use “n”. This prompts the user for a number.
Use “s” for the arg-descriptor, and “sMy prompt: " to give a custom prompt.
This is how a directory-mode is done:
(define-command simple-message (s) ("sEnter a string: ")
(message s))
Run it with M-x simple-message
.
Use “b”. It defaults to the current buffer’s name.
There is also “B” that defaults to the other buffer’s name.
Use “f” to prompt for a file. It defaults to the current buffer’s directory.
There is also “F” that defaults to the buffer’s directory and must not be existing.
Next, how can we run a command on a region? A region is defined with a start and end point. We have to tell the command it operates on a region, that way it gives us the two points.
We do so by declaring two start
and end
arguments, and a special
argument descriptor: “r”. Here’s how it is done for the python-eval-region
command:
(define-command python-eval-region (start end) ("r")
(unless (alive-process-p)
(editor-error "Python process doesn't exist."))
(lem-process:process-send-input *process* (points-to-string start end)))
We can have multiple arguments, for example:
(define-command multiple-args (regex &optional arg) ("sEnter a regex:" "P")
...)
Lem uses external code formatters that can be run automatically when you save a file.
Currently formatters available by default are:
gofmt
for go-modeclang-format
for c-modeprettier
for js-mode and json-mode- and it works for lisp code of course.
Additionally, rust-mode uses rustfmt
.
To use formatting, you can either manually invoke
M-x format-current-buffer
, or enable auto-formatting like this:
(setf lem:*auto-format* t)
Formatters can be defined for major modes, they are implemented as a function that accepts a buffer as an argument. Some are built-in, but you can make your own in two ways:
register-formatter
You can register a formatter for an existing major mode like this:
(lem:register-formatter my-mode #'my-formatter)
define-major-mode
If you are creating your own major mode, you can add a formatter in the mode definition:
(define-major-mode my-mode ()
(:name "My mode"
:keymap *my-mode-keymap*
:formatter #'my-formatter)
)
Hooks allow you to run abritrary code before (or after) a command is called or a given mode is entered (or exited).
Some examples:
after-save-hook
kill-buffer-hook
*find-file-hook*
*exit-editor-hook*
*after-init-hook*
- self-insert-hook: run code before or after a character is inserted in the buffer.
- and more: checkout each mode (programming modes, vi mode…) inside Lem with the completion.
The hooks mechanism is defined in src/common/hooks.lisp
. It is made of the available functions and macros:
add-hook place callback
: add a hook- example:
(add-hook *after-init-hook* #'display-welcome)
- example:
remove-hook place callback
: remove a hookrun-hooks list
: run all hooks.
Each mode defines a hook variable name. For example:
(define-major-mode c-mode language-mode
(:name "C"
:keymap *c-mode-keymap*
:syntax-table *c-syntax-table*
:mode-hook *c-mode-hook* ;; <---------- defines our hook name
:formatter #'lem-c-mode/format:clang-format)
(setf (variable-value 'enable-syntax-highlight) t)
;; more settings here…
)
(add-hook *c-mode-hook* 'guess-offset) ;; <------ adds a hook
Start Lem in vi-mode:
;; Start in vi-mode
(lem-vi-mode:vi-mode)
We can use a hook to be in vi insert mode when we start the Lisp REPL:
;; Start the Lisp REPL in vi insert mode.
(add-hook lem-lisp-mode:*lisp-repl-mode-hook* 'lem-vi-mode/commands:vi-insert)
For inspiration, see those configuration files:
- @fukamachi
- @sasanidas
- @vindarel
- @Gavinok
- @garlic0x1
- https://gist.github.com/jason-chandler/6332e3fd753fa87e3b1cd13582df5862 getting cxxxr/valtan to work along with paredit and the monokai theme