272 lines
10 KiB
EmacsLisp
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
|