Lisp Machine was a computer designed to be a Lisp heaven. All of its application and systems software were written in Lisp and was fully customizable, on-the-fly. Its hardware was specifically designed to carry out Lisp code efficiently, with CAR and CDR baked into the processors’ instruction set.
Now that non-Lisp machines became more than good enough to execute Lisp code fast, Lisp machines, together with the full customizability they offered became a thing of the past. However, there is a free software that still embodies that hacker’s spirit of freedom: Emacs. With sufficient knowledge of ELisp and the boldness to dive into source codes when necessary, one can ride any type of system if those systems support Emacs. And Emacs is one of the most ported software to date; it’s almost a dark power to manipulate any type of system one may encounter in his/her hacker journey.
While I’m too young to have used a full Lisp Machine (like, from LMI or Symbolics), I’m about to reach 3 full years of using Emacs, starting from my own humble =init.el`. However, as an undergraduate who was also interning part-time in a graduate school lab, I lacked the time to hack my init script with too many things to do. So briefly after I jumped to Spacemacs.
While Spacemacs provided the power of fully customized Emacs with minimum effort on the user’s side, adding my own features and replacing Spacemacs’ default package slowly but steadily became a hassle. While one could do that since Emacs is fully introspectable, the call chain of triggering a command would often go down deep through Spacemacs’ own layer of abstractions, with functions, macros, and variables.
Therefore comes my set of config, the ELisp-Machine (sorry, I don’t have a better name yet). This config aims to:
- Clone Spacemacs’ behavior completely (so that it’s transparent to the user), while
- Maintaining relatively fast startup time (< 6s), and
- Avoid unnecessary visual elements like a fancy modeline or all-the-icons.
Spacemacs is outstandingly good at maintaining prefix trees on every major mode as well as a global one that makes keybindings memorable and very easy to press, so I clone their packages.el in every layer imaginable as well as the core configs. I do it in a very vanilla way: not by using some esoteric custom functions or macros to do something (e.g. defining a local keybinding), but by using built-in functionalities from use-package, general.el and straight.el. The result is a Spacemacs-like config that is both lean and idiomatic.
While this is a work in progress as for now, it’s my daily driver. Enjoy!
This is a literate Org document intended to read as-is. However, in order for Emacs to understand and execute, one should open it and create a init.el file by calling org-babel-tangle. To avoid problems like going out of sync of the init.el file and this document, the .el is not tracked by Git.
I am forever indebted to the hacker community for helping others pick up power tools like Emacs. The homeage goes to, but not limited to, the following people/organizations:
- [ ] Port
init.elto this README.org - [ ] Generate a TOC for this README.org
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 6))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)(use-package dash
:config
(function-put '-> 'lisp-indent-function nil)
(function-put '->> 'lisp-indent-function nil))
(use-package s)
(use-package ts)Variables/functions/macros are too small to be dedicated a space for their own
(defmacro plaintext (&rest body)
"Write whatever you want in the BODY!"
(string-join
(-interpose " "
(mapcar (lambda (elem)
(cond
((stringp elem) elem)
((and (symbolp elem)
(string= (symbol-name elem) "//")) "\n")
((symbolp elem) (symbol-name elem))
(t (error (format "Unrecognized string: %s" elem))))) body))))
(defmacro comment (&rest args)
"Rich comment: ignore whatever that is in ARGS."
nil)
(defun minor-mode-activated-p (minor-mode)
"Is the given MINOR-MODE activated?"
(let ((activated-minor-modes (mapcar #'car minor-mode-alist)))
(memq minor-mode activated-minor-modes)))
(defun straight-from-github (package repo)
"Make a straight.el specification to locate the PACKAGE from github REPO."
(list package :type 'git :host 'github :repo repo))
(defalias 'assert 'cl-assert)
(defun keyword-to-string (keyword)
"Convert the KEYWORD to string."
(assert (symbolp keyword))
(->> keyword
intern-soft
symbol-name
(s-chop-prefix ":")))
(defvar macOS-p (equal system-type 'darwin)
"Am I in macOS?")
(defvar linux-p (equal system-type 'gnu/linux)
"Am I in a generic Linux distro?")
(defvar chromeOS-p (string= (system-name) "penguin")
"Am I in chromeOS?")
(defvar GUI-p (window-system)
"Am I in a GUI Client?")
(defvar terminal-p (not GUI-p)
"Am I in a tty?")
(defvar work-machine-p (getenv "WORK_MACHINE")
"Am I in a work machine?")
(defmacro use-package-from-github (package repo &rest body)
"Extended form of use-package to pull PACKAGE from REPO. Use the BODY as ordinary use-package."
(let ((github-form (straight-from-github package repo)))
`(use-package ,package :straight ,github-form ,@body)))
(defun toggle-debug-on-error ()
"Toggle `debug-on-error`."
(interactive)
(if debug-on-error
(progn
(setq debug-on-error nil)
(message "%s" "Now disabling stacktrace on error."))
(setq debug-on-error t)
(message "%s" "Now showing stacktrace on error.")))
(defun visit-init-dot-el ()
"Visit `~/.emacs.d/init.el'."
(interactive)
(find-file "~/.emacs.d/init.el"))
(defun eval-init-dot-el ()
"Evaluate the contents of `~/.emacs.d/init.el'."
(interactive)
(with-temp-buffer
(insert-file-contents "~/.emacs.d/init.el")
(eval-buffer)))Ah Evil! Giving Emacs the best editor it deserves.
I always type :Q instead of :q, :W instead of :w, and so on. Who has time for retyping those? This is Emacs, and we’re going to change that (actually, (neo)vim can do this, too, but that’s out of the point). Also, we rebind C-w h and friends to work without lifting the control key, that is, rebind their commands to C-w C-h.
(setq evil-undo-system 'undo-tree)
(use-package evil
:init
(setq evil-want-keybinding nil
evil-disable-insert-state-bindings t
evil-want-C-u-scroll t
evil-want-integration t
evil-vsplit-window-right t
evil-split-window-below t)
:config
(evil-mode 1)
(evil-set-leader 'normal (kbd "SPC"))
(evil-set-leader 'normal "," t)
(setq evil-motion-state-cursor 'box
evil-visual-state-cursor 'box
evil-normal-state-cursor 'box
evil-insert-state-cursor 'bar
evil-emacs-state-cursor 'bar)
(evil-ex-define-cmd "q" 'kill-this-buffer)
(evil-ex-define-cmd "Q" 'kill-this-buffer)
(evil-ex-define-cmd "W" 'save-buffer)
(evil-ex-define-cmd "Wq" 'evil-save-and-close)
(evil-ex-define-cmd "WQ" 'evil-save-and-close)
(evil-ex-define-cmd "E" 'evil-edit)
(when macOS-p
(evil-define-key 'normal 'global (kbd "C-w DEL") 'evil-window-left))
(evil-define-key 'normal 'global (kbd "C-w C-h") 'evil-window-left)
(evil-define-key 'normal 'global (kbd "C-w C-j") 'evil-window-down)
(evil-define-key 'normal 'global (kbd "C-w C-k") 'evil-window-up)
(evil-define-key 'normal 'global (kbd "C-w C-l") 'evil-window-right)
(unbind-key (kbd "C-@"))
(unbind-key (kbd "M-SPC"))
;; make evil-search-word look for symbol rather than word boundaries
(defalias #'forward-evil-word #'forward-evil-symbol)
(setq-default evil-symbol-word-search t)
(defun evil-toggle-input-method ()
"when toggle on input method, switch to evil-insert-state if possible.
when toggle off input method, switch to evil-normal-state if current state is evil-insert-state"
(interactive)
(if (not current-input-method)
(if (not (string= evil-state "insert"))
(evil-insert-state))
(if (string= evil-state "insert")
(evil-normal-state)))
(toggle-input-method)))
(use-package evil-collection
:after (evil)
:config
(evil-collection-init)
(setq evil-collection-calendar-want-org-bindings t))
(use-package evil-surround
:after evil
:config (global-evil-surround-mode 1))
(use-package evil-anzu
:after evil)
(use-package evil-commentary
:after evil
:config (evil-commentary-mode))(setq custom-lisp-directory (concat user-emacs-directory "lisp/"))
(setq global-cache-directory (concat user-emacs-directory "cache/"))
(dolist (dir (list custom-lisp-directory global-cache-directory))
(unless (file-directory-p dir)
(make-directory dir))
(add-to-list 'load-path dir))
(add-to-list 'load-path
(concat user-emacs-directory
"straight/repos/vertico/extensions/"))
(defun cache: (subpath)
"Concatenate the SUBPATH to the global-cache-directory."
(concat global-cache-directory (s-chop-prefix "/" subpath)))(setq epg-gpg-program "gpg")
(when terminal-p
(setq epg-pinentry-mode 'loopback))(set-language-environment "Korean")
(prefer-coding-system 'utf-8)
(global-set-key (kbd "<f6>") 'toggle-korean-input-method)
(unbind-key (kbd "C-d"))
(unbind-key (kbd "C-d C-d"))
(unbind-key (kbd "C-d C-l"))
(global-set-key (kbd "C-d C-d") 'toggle-input-method)
(global-set-key (kbd "C-d C-l") 'toggle-input-method)
(global-set-key (kbd "C-\\") 'toggle-input-method) (use-package no-littering
:config
(setq auto-save-file-name-transforms
`((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(when (fboundp 'startup-redirect-eln-cache)
(startup-redirect-eln-cache
(convert-standard-filename
(expand-file-name "var/eln-cache/" user-emacs-directory)))))general.el is used to define spacemacs-like keybindings and is very well integrated with use-package. In contrast to which-key which makes it very difficult to declare custom prefix labels (you need to do some acrobatics to set the prefix label), declaring custom keybindings using :general is a joy to use.
We define five different definers:
- Definers with leader keys
global-leader- A major-mode-agnostic trie with global leader as its prefix.
- Just pressing the global leader shows a string
"Global".
local-leader- A major-mode-specific trie with local leader as its prefix.
- Just pressing the local leader shows the current major mode without the
"-mode".
- Definers without leader keys
normal-mode-major-mode- Extends evil state for a major mode.
insert-mode-major-mode- Extends insert state for a major mode.
agnostic-key- Adds keybindings that works irrelevant of evil state.
Since typing out '(:ignore t :which-key "some prefix") is cumbersome, we make a simple function for that whose calls can be inlined inside the definers.
(use-package general
:config
(general-override-mode)
(general-auto-unbind-keys)
(setq general-use-package-emit-autoloads t)
(general-create-definer global-leader
:keymaps 'override
:states '(insert emacs normal hybrid motion visual operator)
:prefix "SPC"
:non-normal-prefix "S-SPC"
"" '(:ignore t :which-key "Global"))
(general-create-definer local-leader
:keymaps 'override
:states '(emacs normal hybrid motion visual operator)
:prefix ","
"" '(:ignore t :which-key
(lambda (arg)
(cons
(cadr (split-string (car arg) " "))
(replace-regexp-in-string
"-mode$" ""
(symbol-name major-mode))))))
(general-create-definer normal-mode-major-mode
:keymaps 'override
:states '(normal visual operator)
:prefix "")
(general-create-definer insert-mode-major-mode
:keymaps 'override
:states '(insert)
:prefix "")
(general-create-definer agnostic-key
:keymaps 'override
:states '(insert emacs normal hybrid motion visual operator)
:prefix ""
"" '(:ignore t)))
(defun which-key-prefix (label)
"Create a which-key prefix with LABEL."
(list
:ignore t
:which-key (if (keywordp label) (keyword-to-string label) label)))