source/config.el

272 lines
10 KiB
EmacsLisp

;;; config -- personal Emacs configuration
;;;
;;; Commentary:
;;;
;;; This is another attempt to recover from yet another Emacs bankruptcy and
;;; do it right (TM) this time. I don't feel comfortable dumping all changes
;;; that are about to happen on the master branch, so I am creating an orphan
;;; branch just for this Emacs configuration.
;;;
;;; If I decide to switch from vim/dvtm workflow to Emacs I will merge this
;;; branch into the master and "git log --follow" will be able to trace the
;;; history. If I decide against the switch -- one extra branch won't hurt
;;; anything.
;;;
;;; Code:
(let ((min-version 29))
(unless (>= emacs-major-version min-version)
(user-error "Unsupported Emacs version: %s < %s" emacs-major-version min-version)))
;; Workaround for a bug in `use-package'. The `:bind' keyword expands into
;; call to `bind-key' macro, which when expanded, reads `personal-keybindings'
;; variable. Nothing in the expanded code forces loading of `bind-key'
;; package,
;;
;; so when byte-compiled, this variable is never defined, resulting in
;; "void-variable" error.
(defvar personal-keybindings nil)
(eval-when-compile
(setopt use-package-expand-minimally t)
;; It is easier to notice that some feature is deferred when it should
;; not be, since it won't be available.
;;
;; Noticing that something is loaded for no good reason is harder, it
;; just makes things slow with no clear indication why.
(setopt use-package-always-defer t))
;; Nix derivations for Emacs packages are great if I know what exactly I want,
;; but experimenting with packages I am not familiar with is inconvenient.
;;
;; At best, I would have to context-switch to nix and install necessary
;; package into user profile, and at worst -- rebuild Emacs derivation and
;; restart Emacs.
;;
;; Instead, I just keep rsync snapshot of whole GNU and MELPA repositories,
;; and use regular `package-install'.
;;
;; Nice side effect is that I can play with whatever packages I want even
;; without networking.
(let ((package-archives-value
; Compiler might not be smart enough to do constant folding on its own;
; Be explicit that this value should be computed and compile time.
(eval-when-compile
(let ((prefix "~/data/active/mirror/emacs-packages/2024-01-31"))
(cl-flet ((repository (name) (cons name (format "%s/%s" prefix name))))
(list
(repository "gnu")
(repository "melpa")))))))
(use-package package
; The `:custom' keyword of `use-package' expects a literal value,
; not bound variable.
:config (setopt package-archives package-archives-value)))
(use-package evil :demand t :ensure t
:init
(evil-mode)
:custom
(evil-mouse-word 'evil-WORD)
(evil-want-C-u-scroll t)
(evil-want-C-w-in-emacs-state t)
(evil-symbol-word-search t)
(evil-want-minibuffer t))
; Make byte-compiler happy. It does not understand that call to `evil-mode'
; loads definition of `evil-define-minor-mode-key'.
(require 'evil-core)
(require 'evil-common)
(require 'evil-states)
(use-package alect-themes :ensure t
:init (load-theme 'alect-black :no-confirm)
;; This number is figured experimentally. It is small enough so I have full
;; window width of 168 characters, yet big enough so I am comfortable reading
;; it.
:config (set-face-attribute 'default nil :height 225)
:custom
;; I don't like how multiple font sizes look together.
(alect-header-height 1.0)
(alect-single-title-height 1.0)
(alect-multiple-titles-height 1.0))
(use-package macrostep
:load-path "~/devel/kaction-emacs/macrostep"
;; Without this hook, separate buffer with macro expansion starts in "emacs"
;; state (despite having "<N>" marker in status line), so regular navigation
;; "j" and "k" do not work.
;;
;; That does not happen if `macrostep-expand-in-separate-buffer' is nil, but
;; I find separate buffer much cleaner.
:hook (macrostep-mode . turn-on-evil-mode)
:config
;; Since `macrostep-mode' is read-only, I am unlikely to need evil keyboard
;; macroses. Being able to quickly close the buffer is more useful.
(evil-define-minor-mode-key 'normal 'macrostep-mode "q" 'macrostep-collapse-all)
:custom (macrostep-expand-in-separate-buffer t))
;; FIXME: According to the documentation, `use-macro' is supposed to
;; infer the "paredit-mode" name itself, but for some reason it does
;; not and tries to put non-existent `paredit' function instead of
;; expected `paredit-mode' into the hook variable.
(use-package paredit :ensure t
:hook (lisp-data-mode . paredit-mode)
:config
(evil-define-minor-mode-key 'normal 'paredit-mode
(kbd "; (") 'paredit-wrap-round
(kbd "; S") 'paredit-split-sexp
(kbd "; J") 'paredit-join-sexps
(kbd "; s") 'paredit-splice-sexp
(kbd "; >") 'paredit-forward-slurp-sexp
(kbd "; .") 'paredit-forward-barf-sexp
(kbd "; <") 'paredit-backward-slurp-sexp
(kbd "; ,") 'paredit-backward-barf-sexp))
; Paredit mode ensures that all parensis are closed and refuses to
; execute native Emacs editing commands that would cause mismatching
; parensis. Extra "evil-paredit-mode" puts same safeguards on Evil
; editing commands, e.g on "x" in normal mode.
(use-package evil-paredit :ensure t
:hook (lisp-data-mode . evil-paredit-mode))
(use-package elisp-mode
:bind (:map emacs-lisp-mode-map
("C-c e" . macrostep-expand)
("C-c C-c" . emacs-lisp-byte-compile-and-load)))
(use-package yasnippet-snippets :ensure t)
(use-package yasnippet :ensure t
:config
(evil-define-minor-mode-key 'insert 'yas-minor-mode
(kbd "C-- C--") 'yas-insert-snippet
(kbd "TAB") 'yas-expand)
(evil-define-minor-mode-key 'normal 'yas-minor-mode
(kbd "C-- C-=") 'yas-new-snippet)
:hook
(text-mode . yas-minor-mode)
(prog-mode . yas-minor-mode)
:custom
(yas-fallback-behavior 'call-other-command))
(use-package gemini-mode :ensure t
:custom-face
(gemini-heading-face-1 ((t (:height 1.0))))
(gemini-heading-face-2 ((t (:height 1.0))))
(gemini-heading-face-3 ((t (:height 1.0)))))
(use-package notmuch :ensure t
:custom
(notmuch-show-logo nil)
:bind
(:map notmuch-hello-mode-map
("/" . notmuch-search)
:map notmuch-search-mode-map
("j" . evil-next-line)
("k" . evil-previous-line)
:map notmuch-tree-mode-map
("j" . evil-next-line)
("k" . evil-previous-line)
:map notmuch-show-mode-map
("j" . evil-next-line)
("k" . evil-previous-line)))
(use-package notmuch-mua
:custom
;; msmtp provides "sendmail" shim
(send-mail-function 'sendmail-send-it)
;; Need to specify this one explicitly, otherwise Emacs will generate
;; envelope from hostname of my laptop, and that makes disroot.org mail
;; server unhappy.
(mail-envelope-from "kaction@disroot.org"))
(use-package prog-mode
:config
(evil-define-key 'insert prog-mode-map (kbd "RET") #'evil-ret-and-indent))
(use-package text-mode
:config
(evil-define-key 'insert text-mode-map (kbd "RET") #'evil-ret-and-indent))
(use-package window
:config
;; This variable does not really belong to any individual package; like with
;; `auto-mode-alist', configuration of each package may have reasons to modify
;; it to adjust layout of windows associated with the package. Unfortunately,
;; this one is much more likely to be tweaked.
;;
;; Changing configuration in one call to `use-package' and re-evaluating
;; won't produce the correct result. Here I use one instance of `setq' and
;; everything below is supposed to use `push' so re-evaluating this whole file
;; will do the right thing.
(setq display-buffer-alist
'(("\\*Help\\*"
(display-buffer-reuse-window display-buffer-in-side-window)
(side . bottom)
(window-height . 15))
("\\*Completions\\*"
(display-buffer-reuse-window display-buffer-in-side-window)
(side . right)
(window-width . 45))))
:custom
(switch-to-buffer-obey-display-actions t)
(switch-to-buffer-in-dedicated-window 'pop)
(window-sides-slots '(1 1 1 1)))
(use-package haskell-snippets :ensure t)
(use-package haskell-mode :ensure t
:config
(defun @haskell-run-watcher ()
"Create buffer running a process rebuilding the project on the source change.
In case of Yesod projects, \"yesod devel\" is used; \"ghcid\" otherwise. The buffer
created is named \"*haskell-build*\".
This function assumes that root of the project contains either \"shell.nix\" or
\"default.nix\" that provide the environment to run the builder command."
(interactive)
(let* ((current-buffer (current-buffer))
(file-name (buffer-file-name current-buffer))
(default-directory (locate-dominating-file (or file-name default-directory) "package.yaml"))
;; What is the best way to identify yesod project?
(build-command (if (file-exists-p "config/settings.yml")
"yesod-devel"
"ghcid -c \"cabal repl\" -o errors.err --lint=hlint"))
;; Maybe `comint-exec' would be better?
(build-command* (format "cached-nix-shell --run '%s'" build-command))
;; `ansi-term' function adds asterisks on its own.
(term-buffer-name (format "haskell-build::%s" default-directory))
(term-buffer (or (get-buffer (format "*%s*" term-buffer-name))
(ansi-term build-command* term-buffer-name)))
(watcher-process (get-buffer-process term-buffer)))
;; Disable the confirmation query on whether I am okay with killing the process.
(when (processp watcher-process)
(set-process-query-on-exit-flag watcher-process nil))
;; Useful to switch there and resize the window.
(with-current-buffer term-buffer
(evil-normal-state))
(display-buffer term-buffer)
;; No idea why, but `save-current-buffer' does not work here: ocus
;; stays in the ansi-term window.
(switch-to-buffer current-buffer)))
(push
'("\\*haskell-build::.*\\*"
(display-buffer-in-side-window)
(side . top)
(window-height . 12)
(inhibit-same-window . t)
(window-parameters
(no-delete-other-windows . t)))
display-buffer-alist)
:bind
(:map haskell-mode-map
("C-c C-e" . @haskell-run-watcher)))
(use-package python-mode)
; Useless, but makes `emacs-lisp-checkdoc` happy.
(provide 'config)
;;; config.el ends here