https://github.com/vindarel/lem-init
My Lem editor's musings
https://github.com/vindarel/lem-init
Last synced: 5 months ago
JSON representation
My Lem editor's musings
- Host: GitHub
- URL: https://github.com/vindarel/lem-init
- Owner: vindarel
- Created: 2023-06-15T10:52:12.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2024-08-28T22:42:16.000Z (almost 2 years ago)
- Last Synced: 2025-02-12T04:17:09.648Z (over 1 year ago)
- Language: Common Lisp
- Size: 40 KB
- Stars: 10
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# My Lem init file
> For Lem 2.0
Written in literate programing with [erudite](https://github.com/mmontone/erudite) (see below).
* new website: https://lem-project.github.io/
* all keys: https://lem-project.github.io/usage/keybindings/ (missing some modes, like Lisp mode)
Impressions:
* Lem has a shitload of features! paredit, tabs, treeview, tetris…
* very nice to discover commands in Lem itself.
* very nice for CL, only a few keys missing. Advanced debugger.
* **Lem has a new git interface!** that I started developping. See changes, stage a file, the hunk of a diff, interactive rebase for the best scenario… cool but needs loads of work.
Installation:
copy or symlink to ~/.config/lem/init.lisp (since Lem 2.2) or ~/.lem/init.lisp.
I start Lem like this:
```
$ cd lem/
CL_SOURCE_REGISTRY=$(pwd)// ros -s lem-sdl2 -e '(progn (lem:lem) (uiop:quit))'
```
## The keys I miss so far
* M-j newline with comment (if inside comment)
* marks (m and ' (quote))
* as of 2025 there is now `mark-set` (C-space) and `exchange-point-mark` (C-x C-x)
* (fixed) M-h select paragraph
* since Jan, 2025, there are paragraphs objects in vi-mode, we can do "vip" to select the current paragraph, and delete it.
* (fixed) imenu functionality => see very poor but useful snippet below. See M-x detective-all, doing alright.
* (fixed) C-x C-j to open directory mode on the current file => added upstream.
* (fixed) project-aware commands => the basics added upstream.
In Lisp mode:
* C-c C-y call function at point in the REPL, with package prefix.
* little issue with highlighting of parens in vi insert mode, on the last paren.
* (fixed) C-~ sync file package with REPL => done in main, <2024-07-30>
* (fixed) REPL: C-c C-p go to previous prompt => done in main (end of June, 2023) (it's C-c p)
* (fixed) Inspecting literal objects => done in main (end of June, 2023)
In vi-mode:
* OK now, I had to send some keys upstream, there have been many more contributions, the vi-mode is now very complete to my taste.
Issues in Lem 2.0:
* (fixed!) I can't type backslash or any key with Alt Gr (right Alt key), such as ~, and I can't have my undo on C-_ => fixed upstream => un-fixed so it works best for more people. => fixed again, somewhere 2024.
## Things than Lem does better than Emacs
* default auto-completion UI (but not the algorithm yet)
* ease of development for CL, commands discovery
* sorting files by name, mtime AND by size (with human-readable format) in directory-mode
* interactive markdown mode with multiple-major-modes (evaluate Lisp or C code inside code blocks), interactive markdown preview in a browser built-in
## Load more CL libraries
We want some more CL libraries.
```lisp
(pushnew "/path/to/lisp/project/" asdf:*central-registry* :test #'equal)
```
## Starting up
Start this config in Lem's CL package
```lisp
(in-package :lem-user)
```
Evaluating Lisp in M-: we want to be in Lem' package.
```lisp
(lem-lisp-mode/internal::lisp-set-package "LEM")
```
Start in vi-mode
```lisp
(lem-vi-mode:vi-mode)
```
Start the Lisp REPL in vi insert mode (commit 23-8-8)
(add-hook lem-lisp-mode:*lisp-repl-mode-hook* 'lem-vi-mode/commands:vi-insert)
## Some helper functions, bound to keys below.
I want quick movement functions to go to the previous or next definition (function, or anything really).
Lem has beginning- and end-of-defun, but a repetitive call doesn't go outside the function definition.
```lisp
(define-command beginning-of-defun-on-function (n) (:universal)
"Go to the beginning of defun, the point on the function name."
; if n < 0, go to end of defun.
(previous-line)
(lem/language-mode::beginning-of-defun-1 n)
(lem-vi-mode/word:forward-word-end (current-point) n t)
(skip-whitespace-forward (current-point) t))
(define-key lem-vi-mode:*normal-keymap*
"g a"
'beginning-of-defun-on-function)
```
'lem/detective:detective-search) ;; <2023-07-05 Wed> detective doesn't currently work with define-command and define-key definitions.
```lisp
(define-command end-of-defun-on-function (n) (:universal)
"Go to the next defun, the point on the function name."
(lem/language-mode::end-of-defun n)
(search-forward-regexp (current-point) "^\\(")
(lem-vi-mode/word:forward-word-end (current-point) n t)
(skip-whitespace-forward (current-point) t))
(define-key lem-vi-mode:*normal-keymap*
"g e"
'end-of-defun-on-function)
```
<2023-05-25 Thu>
vi-mode doesn't have "+" and "-" keys => sent upstream.
dev note: we could find the variables and command names easily thanks to Lem's autocompletion.
```lisp
(define-key lem-vi-mode:*normal-keymap*
"-"
'lem-vi-mode/commands:vi-previous-line)
(define-key lem-vi-mode:*normal-keymap*
"+"
'lem-vi-mode/commands:vi-next-line)
(define-key lem-vi-mode:*normal-keymap*
"q"
'kill-buffer)
(define-key lem-vi-mode:*normal-keymap*
"Space"
'lem-vi-mode/commands:vi-forward-char)
(define-key lem-vi-mode:*normal-keymap*
"("
'backward-paragraph)
```
sent upstream.
```lisp
(define-key lem-vi-mode:*normal-keymap*
")"
'forward-paragraph)
```
## Abbrev completion
TIL Lem has completion with abbrev.
C-p is bound to abbrev
As suggested by its example, we define C-n to display a list of suggestions.
It is originally bound to next-line, in vi-mode too.
```lisp
(define-key lem-vi-mode:*insert-keymap* "C-n" 'lem/abbrev:abbrev-with-pop-up-window)
(define-key *global-keymap* "C-n" 'lem/abbrev:abbrev-with-pop-up-window)
```
## More keys of my liking (bépo keybboard)
Most make sense for a bépo keyboard only.
```lisp
(define-key lem-vi-mode:*normal-keymap*
"' b"
'select-buffer)
```
switch buffers
```lisp
(define-key lem-vi-mode:*normal-keymap*
"' «"
'previous-buffer)
(define-key lem-vi-mode:*normal-keymap*
"' »"
'next-buffer)
```
go to end of buffer
```lisp
(define-key lem-vi-mode:*normal-keymap*
">"
'move-to-end-of-buffer)
(define-key lem-vi-mode:*normal-keymap*
"<"
'move-to-beginning-of-buffer)
(define-key lem-vi-mode:*normal-keymap*
"M-»"
'move-to-end-of-buffer)
(define-key lem-vi-mode:*normal-keymap*
"M-«"
'move-to-beginning-of-buffer)
```
vi visual mode
```lisp
(define-key lem-vi-mode/visual::*visual-keymap*
"x"
'lem:kill-region)
(define-key *global-keymap*
"C-x \""
'lem-core/commands/window:delete-other-windows)
(define-key *global-keymap*
"C-x »"
'lem-core/commands/window:split-active-window-horizontally)
(define-key *global-keymap*
"C-x «"
'lem-core/commands/window:split-active-window-vertically)
(define-key *global-keymap*
"F6"
'lem/language-mode::comment-or-uncomment-region)
(define-key *global-keymap*
"M-s"
'previous-line)
(define-key *global-keymap*
"M-t"
'next-line)
```
don't work or not in the terminal?
```lisp
(define-key *global-keymap* "C-PageDown"
'lem/frame-multiplexer:frame-multiplexer-next)
(define-key *global-keymap* "C-PageUp"
'lem/frame-multiplexer:frame-multiplexer-prev)
```
yes:
```lisp
(define-key lem-vi-mode:*normal-keymap* "C-n" 'lem/frame-multiplexer:frame-multiplexer-next)
(define-key lem-vi-mode:*normal-keymap* "C-p" 'lem/frame-multiplexer:frame-multiplexer-prev)
```
nope?
```lisp
(define-key *global-keymap* "C-p" 'lem/frame-multiplexer:frame-multiplexer-prev)
(define-key *global-keymap* "C-n" 'lem/frame-multiplexer:frame-multiplexer-next)
```
Undo:
this doesn't work, it understands C-space.
```lisp
(define-key *global-keymap*
"C-_"
'undo)
```
## imenu
A very poor man's imenu.
```lisp
(defun buffer-headings (txt)
(loop for line in (str:lines txt)
for parts = (str:split " " line)
for i = 1 then (incf i)
if (str:starts-with-p "(def" line)
; collect (list line i))
collect line))
(defun prompt-for-heading ()
(let ((candidates (buffer-headings (buffer-text (current-buffer)))))
(if candidates
(prompt-for-string "Heading: "
:history-symbol '*imenu*
:completion-function (lambda (x)
(completion-strings x candidates))
:test-function (lambda (name)
(member name candidates :test #'string=))))))
(define-command imenu () ()
(let ((candidate (prompt-for-heading)))
(move-to-beginning-of-buffer)
(search-forward (current-point) candidate)
(message "~a" candidate)))
(define-key *global-keymap* "C-x i" 'imenu)
```
## Misc
```lisp
(define-command open-init-file () ()
; thx @sasanidas
(lem:find-file
(merge-pathnames "init.lisp" (lem-home))))
(define-command oif () ()
(open-init-file))
```
### Open PDF files with an external program
```lisp
(defmethod lem-core/commands/file:execute-find-file :around (executor mode pathname)
(if (find (pathname-type pathname) '("pdf" "mp4" ".mp4") :test #'equal)
(open-external-file pathname)
(call-next-method)))
```
### Transparent background! ^^
```lisp
; (sdl2-ffi.functions:sdl-set-window-opacity (lem-sdl2/display::display-window lem-sdl2/display::*display*) 0.9)
#+lem-sdl2
;(sdl2-ffi.functions:sdl-set-window-opacity (lem-sdl2/display::display-window lem-sdl2/display::*display*) 1.0)
```
I want to see my logs on the terminal output:
```lisp
(log:config :info)
```
### Insert a file name (with completion)
```lisp
(define-command insert-file-name (filename) ((:file "Insert file name:"))
"Inserts a filename at point."
(insert-string (current-point) filename))
```
### Configure legit
Legit is now included and loaded by default.
I only set one parameter: don't prompt for confirmation when aborting a commit message.
```lisp
(setf (config :prompt-for-commit-abort) nil)
```
Fix a slow down for me, see
https://github.com/lem-project/lem/issues/1092
This crashes Lem: (so, keep a patch in my local git…)
```lisp
; (defun my/lem-sdl2--call-with-renderer (function)
; (uiop:format! t "~%running Lem with my SDL2 slowdown fix, issue 1092…~%")
; (bt:with-recursive-lock-held ((display-mutex *display*))
; (funcall function)))
```
(setf (symbol-function 'lem-sdl2::call-with-renderer) #'my/lem-sdl2--call-with-renderer)
### Suspend ncurses Lem (C-z)
```lisp
#+lem-ncurses
(define-command suspend-lem () ()
; @fukamachi
; https://github.com/lem-project/lem/issues/306
(when (find-package 'charms/ll)
(uiop:symbol-call 'charms/ll 'endwin) ; the package doesn't exist in the SDL version.
(sb-posix:kill (sb-posix:getpid) sb-posix:sigtstp)))
(define-key *global-keymap* "C-z C-z" 'suspend-lem)
```
### Other settings - timestamps
Load a utility from another file, too short for a PR:
```lisp
(load "~/dotfiles/lem/time-stamp.lisp")
```
Now you can do `M-x time-stamp` to print the timestamp of the day, in the org-mode format:
"<2023-07-05 Wed>"
### Choose the position of the completion prompt (new in May, 2024)
```lisp
;(setf lem-core::*default-prompt-gravity* :bottom-display)
;(setf lem/prompt-window::*prompt-completion-window-gravity* :horizontally-above-window)
;(setf lem/prompt-window::*fill-width* t)
```
and show the completion list directly, without a first press on TAB:
```lisp
;(add-hook *prompt-after-activate-hook*
; (lambda ()
; (call-command 'lem/prompt-window::prompt-completion nil)))
;(add-hook *prompt-deactivate-hook*
; (lambda ()
; (lem/completion-mode:completion-end)))
```
### Terminal commands
(by cxxxr on Discord)
(in-package :lem-terminal/terminal-mode)
(defun send-string-and-newline (terminal string)
(loop :for character :across string
:do (terminal:input-character terminal
character))
(terminal:input-key terminal ffi::vterm_key_enter))
(define-command terminal-send-command (string) ((:string "String: "))
(let ((buffer (terminal:find-terminal-buffer)))
(when buffer
(send-string-and-newline (buffer-terminal buffer) string))))
## media-player (POC)
I started playing with a media player: rely on mpv and its inter-process communication to load files and send commands to the player. It's easy, it works great.
We have commands from Lem to play files or directories: M-x media-player-play-directory RET select directory RET.
Or, from a directory mode listing, M-x media-player-play or right-click -> play file.
I'm trying to add context menu entries to access player controls from anywhere.
We'll need a buffer with controls and hotkeys too.
See https://github.com/vindarel/lem-media-player and https://dev.to/vindarel/common-lisp-a-command-line-interactive-terminal-application-in-2-lines-2gnb
WARN: hardcoded paths below.
```lisp
(format t "---- loading media-player…~&")
(load "~/dotfiles/lem/media-player/media-player.lisp")
(load "~/dotfiles/lem/media-player/lem-media-player.lisp")
```
## Erudite: produce this README.md
The command required to produce the readme is:
```
(erudite:erudite #p"README.md" "lem-init.lisp" :output-type :markdown :syntax :erudite)
```
NB: I had to tweak cl-template's .asd definition to `:cl-template` instead of `#:cl-template`.
## See also
* https://gitlab.com/sasanidas/lem-config/-/blob/master/init.lisp
* https://github.com/garlic0x1/.lem