From 0f308b1c90444f5f0293bf40aa1ef177bc51036c Mon Sep 17 00:00:00 2001 From: Timo Wilken Date: Fri, 30 Dec 2022 10:54:17 +0100 Subject: Reorganise Guix home declaration into module tree --- tw/home/files/emacs-init.el | 565 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 tw/home/files/emacs-init.el (limited to 'tw/home/files/emacs-init.el') diff --git a/tw/home/files/emacs-init.el b/tw/home/files/emacs-init.el new file mode 100644 index 00000000..daf6f737 --- /dev/null +++ b/tw/home/files/emacs-init.el @@ -0,0 +1,565 @@ +;;; init.el --- Emacs configuration. -*- lexical-binding: t -*- +;;; Commentary: +;;; Code: + +;; Load settings set through Custom. +;; (setq custom-file (locate-user-emacs-file "custom.el")) +;; (when (file-readable-p custom-file) +;; (load custom-file)) + +(defun tw/xdg-emacs-subdir (type name) + "Get the name of a subdirectory called NAME under $XDG__HOME/emacs." + (expand-file-name + (concat "emacs/" (string-trim-right name "/") "/") + (pcase type + ('cache (or (getenv "XDG_CACHE_HOME") "~/.cache/")) + ('config (or (getenv "XDG_CONFIG_HOME") "~/.config/")) + ('data (or (getenv "XDG_DATA_HOME") "~/.local/share/")) + ;; The following two are Guix/GuixSD extensions. + ('log (or (getenv "XDG_LOG_HOME") "~/.local/var/log/")) + ('state (or (getenv "XDG_STATE_HOME") "~/.local/var/lib/")) + (_ (error "Unknown XDG directory type: %S" type))))) + +;; Global/built-in Custom settings +;; Apply these as early as possible so that e.g. the native-comp files go to the right place. +(mapc (apply-partially #'apply #'customize-set-variable) + `((native-comp-async-report-warnings-errors silent "Don't pop up Warnings buffer for native compilation.") + (native-compile-target-directory ,(tw/xdg-emacs-subdir 'cache "eln") "Put native-compiled binaries in the cache.") + ;; Emacs GUI customization. + (inhibit-startup-screen t "Don't show the startup screen with help links.") + (menu-bar-mode nil "Hide the menu bar globally.") + (tool-bar-mode nil "Hide the tool bar globally.") + (tooltip-mode nil "Show tooltips in the echo area instead.") + (max-mini-window-height 3 "Let the echo area grow to a maximum of 3 lines, e.g. when using `eldoc-mode'.") + (scroll-up-aggressively 0.0 "Don't recenter the window if the point moves off the page.") + (scroll-down-aggressively 0.0 "Don't recenter the window if the point moves off the page.") + ;; Indentation. + (indent-tabs-mode nil "Always use spaces to indent.") + ;; Niceties. + (global-hl-line-mode t "Highlight the current line in all buffers.") + (column-number-mode t "Show the column number in the statusline.") + (electric-pair-mode t "Auto-pair suitable characters like parentheses.") + (completion-cycle-threshold 6 "Allow cycling through completions if there are 6 or fewer of them."))) + +(defalias 'yes-or-no-p #'y-or-n-p + "Always use `y-or-n-p' when asking for confirmation.") + +;; Custom modes depending on file names. +(mapc (apply-partially #'add-to-list 'auto-mode-alist) + `((,(rx (or bos "/") "PKGBUILD" eos) . sh-mode) + (,(rx ".install" eos) . sh-mode) + (,(rx bos "/tmp/neomutt-") . mail-mode) + (,(rx "." (1+ anything) "rc" eos) . conf-unix-mode))) + +(add-to-list 'magic-mode-alist + `(,(rx "#!" (* (not space)) + (? "env" (+ space) (? "-S" (+ space))) + (or "guile" "racket")) + . scheme-mode)) + +(add-hook 'mail-mode-hook #'auto-fill-mode) + +;; `use-package' requirements. +(require 'package) +(package-initialize t) +(eval-when-compile + (require 'use-package)) +(use-package diminish) ; for using :diminish later +(use-package bind-key) ; for using :bind later + +;; Some packages below have `:commands (...) :demand t'. +;; We need :commands for the byte-compiler, but we want to load the package immediately. + +;; Look and feel +(set-face-attribute 'default nil :family "Hermit" :height 100) + +(use-package autothemer + ;; The "catppuccin" theme is linked to the appropriate variant by guix home. + :config (load-theme 'catppuccin-mocha t)) + +(defun tw/get-catppuccin-color (name) + "Get the hex code of the Catppuccin color named NAME." + (cl-loop for color in (autothemer--theme-colors autothemer-current-theme) + when (string= (autothemer--color-name color) name) + return (autothemer--color-value color))) + +(custom-set-faces + `(cursor ((default :background ,(tw/get-catppuccin-color "rosewater"))) t + "Make the cursor \"rosewater\", as recommended by Catppuccin upstream, overriding catppuccin/emacs.")) + +(use-package smart-mode-line + :commands (sml/setup) :demand t + :custom + (sml/no-confirm-load-theme t "Stop Emacs from constantly asking for user confirmation.") + (sml/mode-width 'right "Move the minor-mode list to the right of the modeline.") + (sml/theme 'respectful "Make `smart-mode-line' blend in with the active theme.") + :config (sml/setup)) + +;; Built-in Emacs stuff. +(use-package package + :custom + (package-archives nil "Don't fetch packages from the internet; only get them from Guix.")) + +(use-package recentf + :custom + (recentf-max-saved-items 1000 "Save lots of recently-opened files.") + (recentf-save-file ,(tw/xdg-emacs-subdir 'data "recentf") "Save recently-opened files here.") + (recentf-mode t "Save recently-opened files.")) + +(use-package savehist + :custom + (savehist-file ,(tw/xdg-emacs-subdir 'data "history") "Save minibuffer history here.") + (savehist-mode t "Save minibuffer history on quit.")) + +(use-package files + :custom + (backup-directory-alist `(("." . ,(tw/xdg-emacs-subdir 'data "backup"))) + "Save all backup files in one place to avoid clutter.")) + +;; General editor behaviour. +(use-package ivy + :commands (ivy-mode) :demand t + :custom + (ivy-use-selectable-prompt t "Allow selecting the ivy input as-is.") + (ivy-use-virtual-buffers t "Show recentf and bookmarks in buffers list.") + :config (ivy-mode +1) + :diminish ivy-mode) + +(use-package counsel ; extra niceties for `ivy-mode' + :after (ivy evil) ; evil for :bind-ing to + :bind (("SPC" . counsel-M-x) ; doesn't work + ("fr" . counsel-buffer-or-recentf) + :map evil-visual-state-map + ("SPC" . counsel-M-x)) + :commands (counsel-mode) :demand t + :config (counsel-mode +1) + :diminish counsel-mode) + +(defun tw/counsel-dash-is-help () + "Install `counsel-dash-at-point' as `evil-lookup-func'." + (setq-local evil-lookup-func #'counsel-dash-at-point)) + +(use-package counsel-dash + :bind (("K" . counsel-dash-at-point) ; TODO: just install as `evil-lookup-func'? + ("dK" . counsel-dash) + ("di" . counsel-dash-install-docset) + ("da" . counsel-dash-activate-docset) + ("dd" . counsel-dash-deactivate-docset)) + :hook (python-mode . tw/counsel-dash-is-help) + :custom + (counsel-dash-docsets-path (tw/xdg-emacs-subdir 'data "dash-docsets") "Store docsets in the XDG data directory.") + (counsel-dash-browser-func 'eww "Open documentation pages using `eww' instead of an external browser.") + (counsel-dash-enable-debugging nil "Disable popping up useless warnings.")) + +(use-package rainbow-mode + :after (evil) + :bind (("tR" . rainbow-mode))) + +(use-package display-line-numbers + ;; Included in Emacs >= 26. Better than `linum-mode'. + ;; There is also `global-display-line-numbers-mode', but that also + ;; enables line numbers in help windows, which I don't want. + :hook (prog-mode conf-mode)) + +(use-package which-key + :commands (which-key-mode) :demand t + :config (which-key-mode +1) + :diminish which-key-mode) + +(use-package undo-tree + :after (evil) ; for our :bind-ing + :bind (("U" . undo-tree-visualize)) + :custom + (undo-tree-history-directory-alist + `(("." . ,(tw/xdg-emacs-subdir 'data "undo-tree-history"))) + "Store all `undo-tree' history in a single directory, instead of next to the associated file.") + :commands (global-undo-tree-mode) + :demand t ; this is required so that the :config stanza is actually run asap despite :bind + :config (global-undo-tree-mode +1)) + +;; IDE-like features. +(use-package vc + :after (which-key evil) + :init (which-key-add-key-based-replacements + "g" '("vc" . "Version control") + "gM" '("merge" . "Version control merging")) + :bind-keymap ("g" . vc-prefix-map)) + +(use-package corfu ; https://github.com/minad/corfu + :hook (prog-mode ledger-mode shell-mode eshell-mode) + :custom + (corfu-auto t "Show completion popup after a few seconds automatically.") + :diminish corfu-mode) + +(use-package corfu-doc + :after (corfu) + :hook (corfu-mode) + :diminish corfu-doc-mode) + +(use-package flyspell + :hook mail-mode) + +(use-package flymake + :after (evil which-key) + :hook prog-mode + :init (which-key-add-key-based-replacements + "e" '("errors" . "Flymake")) + :bind (("eb" . flymake-start) + ("el" . flymake-show-buffer-diagnostics) + ("ep" . flymake-show-project-diagnostics) + ("e." . display-local-help) ; Show the error message at point in the minibuffer. + ; `flymake' also shows it using `eldoc', but documentation + ; seems to override error messages. + ("en" . flymake-goto-next-error) + ("eN" . flymake-goto-prev-error)) + :custom + (flymake-suppress-zero-counters nil "Show severity counters even when they are zero.")) + +(use-package flymake-collection + :after (flymake) + ;; This needs to be called in `after-init-hook' so that all other + ;; packages' `:flymake-hook's are processed before f-c-hook-setup is + ;; called. See https://github.com/mohkale/flymake-collection. + :hook (after-init . flymake-collection-hook-setup)) + +;; Language Server Protocol. +(defun tw/help-is-eldoc (&rest _) + "Set up `evil-lookup-func' to display the `eldoc' buffer." + (setq-local evil-lookup-func #'eldoc-doc-buffer)) + +(use-package eglot + ;; I have clang (for clangd) and python-lsp-server installed. + ;; `:hook' adds `-mode' to the package name, but `eglot-mode' doesn't exist. + :hook ((python-mode c-mode c++-mode) . eglot) + :custom + (eglot-autoshutdown t "Shut down language servers after deleting their last associated buffer.") + (eglot-sync-connect 0.1 "Wait for the language server in the background if it takes longer than 100ms.") + :config + ;; TODO: only run `tw/help-is-eldoc' if `eglot-managed-p' is true. + (add-hook 'eglot-managed-mode-hook #'tw/help-is-eldoc)) + +;; Non-LSP language modes. +(use-package cmake-mode + :mode (rx (or (: (or bos "/") "CMakeLists.txt") ".cmake") eos)) + +(use-package gnuplot + :commands (gnuplot-mode gnuplot-make-buffer) + :mode ((rx ".gnuplot" eos) . gnuplot-mode)) + +(use-package graphviz-dot-mode + :mode (rx ".dot" eos) + :custom (graphviz-dot-view-command "xdot %s" "Use xdot for previewing graphviz files.")) + +(use-package hcl-mode + :mode (rx "." (or "hcl" "nomad") eos)) + +(use-package mmm-mode + :commands (mmm-mode)) + +(use-package puppet-mode + :mode (rx ".pp" eos)) + +(use-package python + :commands (python-mode) + :mode (((rx ".py" (? (or ?\i ?\w)) eos) . python-mode) + ((rx ".aurora" eos) . python-mode))) + +(use-package rec-mode + :mode (rx ".rec" eos)) + +(use-package sh-script ; built-in + :custom (sh-basic-offset 2 "Use 2 spaces for `sh-mode' indents.")) + +(use-package tcl + :mode ((rx ".tcl" eos) . tcl-mode) + :magic ((rx "#%Module1.0") . tcl-mode)) + +(use-package web-mode + :mode (rx "." (or "htm" "html" "js" "css" "scss") eos)) + +(use-package yaml-mode + :mode (rx ".y" (? "a") "ml" eos)) + +(use-package ledger-mode + :mode (rx ".journal" eos) + :custom + (ledger-default-date-format ledger-iso-date-format "Use hledger-style dates.") + (ledger-reconcile-default-date-format ledger-iso-date-format "Use hledger-style dates.") + (ledger-reconcile-default-commodity "€" "Make euros the default currency.") + (ledger-post-account-alignment-column 2 "Use 2-space indents.") + (ledger-post-amount-alignment-at :decimal "Align amounts at decimal points/commas.") + (ledger-post-amount-alignment-column 52 "Align amounts' decimal points to the 52nd column.") + (ledger-highlight-xact-under-point nil "Don't highlight the transaction at point.")) + +(use-package geiser + :after (evil) + :commands (geiser geiser-eval-buffer geiser-eval-definition geiser-eval-region geiser-eval-last-sexp) + :hook (scheme-mode . geiser-autodoc-mode) + :config + (evil-define-key '(normal visual) scheme-mode-map + (kbd "i") #'geiser + (kbd "eb") #'geiser-eval-buffer + (kbd "ef") #'geiser-eval-definition + (kbd "er") #'geiser-eval-region + (kbd "el") #'geiser-eval-last-sexp) + :defines scheme-mode-map) + +(use-package geiser-racket + :after (geiser)) +(use-package geiser-guile + :after (geiser)) + +;; Org-mode +(use-package org) +(use-package ob ; org-babel + :after (org)) + +(use-package outline + :commands (outline-mode outline-minor-mode) + :custom + ;; Mirror the default "C-c @" binding for `outline-minor-mode'. + (outline-minor-mode-prefix (kbd "@") "Use localleader for `outline-minor-mode' keybindings.")) + +;; My own custom packages, and stuff that isn't on MELPA. +;; (use-package actionlint ; TODO: port to flymake +;; :after (flycheck) +;; :load-path "include/") + +(use-package alidist-mode + :after (flymake mmm-mode yaml-mode) + :mode (rx (or bos "/") "alidist/" (1+ anything) ".sh" eos) + :load-path "include/") + +(use-package bemscript-mode + :load-path "include/" + :mode (rx ".bem" eos)) + +(use-package ifm-mode + :load-path "include/" + :mode (rx ".ifm" eos)) + +(use-package pam-env-mode + :load-path "include/" + :mode (rx (or bos "/") (or "pam_env.conf" ".pam_environment") eos)) + +(use-package environmentd-mode + :load-path "include/" + :mode (rx (or bos "/") + (or (: (? "etc/") "environment") + (: ".environment.d/" (1+ (not ?\/)) ".conf")) + eos)) + +(use-package org-latex-classes + :after (ox-latex) + :load-path "include/") + +(use-package ob-rec + ;; `org-babel' hooks for `rec-mode' + :after (org ob rec-mode) + :load-path "include/") + +(use-package vcard-mode + :load-path "include/" + :mode (rx "." (or "vcf" "vcard") eos)) + +;; Vim keybindings. +(defun tw/switch-to-other-buffer () + "Switch to the last-used buffer." + (interactive) + (switch-to-buffer (other-buffer))) + +(defun tw/new-buffer () + "Open a new, empty buffer." + (interactive) + (switch-to-buffer (generate-new-buffer "untitled"))) + +(defun tw/delete-current-buffer-file () + "Ask for confirmation, then delete the file associated with the current buffer." + (interactive) + (let ((buffer (current-buffer))) + (when (yes-or-no-p (concat "Delete `" (buffer-file-name buffer) "'?")) + (delete-file (buffer-file-name buffer)) + (kill-buffer buffer)))) + +(use-package evil + :after (which-key) + :commands (evil-mode evil-ex-nohighlight) + :init (setq evil-want-keybinding nil) ; evil-collection needs this + :custom + (evil-undo-system 'undo-tree "Use `undo-tree' for evil's undo-redo function.") + (evil-want-minibuffer t "Use evil bindings in the minibuffer too.") + (evil-want-C-u-scroll t "Scroll on C-u in normal mode, not `universal-argument'.") + (evil-want-C-u-delete t "Delete line on C-u in insert mode, not `universal-argument'.") + (evil-want-Y-yank-to-eol t "Yank from point to end-of-line on Y.") + :config + (evil-mode +1) + (evil-set-leader '(normal visual) (kbd "SPC")) ; + (evil-set-leader '(normal visual) (kbd "\\") t) ; + (evil-define-key '(normal insert visual replace) 'global + (kbd "C-s") #'save-buffer) + ;; Global major-mode-independent keys should be defined here. Major + ;; mode-dependent keys (e.g. for launching a REPL) should go under + ;; instead. Use `use-package' `:bind' for those. + (evil-define-key '(normal visual) 'global + ;; These keybindings mirror the default Spacemacs ones because I have + ;; muscle memory of those. + (kbd ":") #'eval-expression + (kbd "TAB") #'tw/switch-to-other-buffer + (kbd "bb") #'switch-to-buffer + (kbd "bd") #'kill-current-buffer + (kbd "bn") #'tw/new-buffer + (kbd "br") #'revert-buffer-quick + (kbd "bx") #'kill-buffer-and-window + (kbd "fd") #'tw/delete-current-buffer-file + (kbd "ff") #'find-file + (kbd "fs") #'save-buffer + (kbd "h") help-map + (kbd "sc") #'evil-ex-nohighlight + (kbd "tf") #'auto-fill-mode + (kbd "tl") #'toggle-truncate-lines + (kbd "tn") #'display-line-numbers-mode + (kbd "u") #'universal-argument + (kbd "w") evil-window-map + (kbd "wd") #'evil-window-delete ; analogous to "bd" + (kbd "wx") #'kill-buffer-and-window) ; analogous to "bx" + (which-key-add-key-based-replacements + ;; Names are a `cons' of a short name and a long name. + ;; E.g. for b, "buffer" is shown under "b" in the "" menu, + ;; while "Buffers" is shown as the title in the "b" menu. + "b" '("buffer" . "Buffers") + "f" '("file" . "Files") + "h" '("help" . "General help and documentation") + "q" '("quit" . "Finish editing the current buffer in emacsclient") + "s" '("search" . "Search operations and options") + "t" '("toggle" . "Toggles and quick settings") + "w" '("window" . "Windows")) + :functions (evil-define-key evil-set-leader + evil-define-key* evil-window-delete evil-delay) + :defines (evil-visual-state-map)) + +(use-package evil-collection + :after (evil) + :commands (evil-collection-init) :demand t + :config (evil-collection-init)) + +(use-package evil-org + :after (evil org) + :hook org-mode + :config + (evil-define-key '(normal visual) org-mode-map + (kbd "\\") #'org-ctrl-c-ctrl-c + (kbd "'") #'org-edit-src-code + (kbd "e") #'org-export-dispatch) + (evil-define-key '(normal visual) org-src-mode-map + (kbd "'") #'org-edit-src-exit + (kbd "\\") #'org-edit-src-save + (kbd "a") #'org-edit-src-abort)) + +(use-package evil-replace-with-register + :after (evil) + :commands (evil-replace-with-register-install) :demand t + ;; :custom (evil-replace-with-register-key "gR" "Use the default key.") + :config (evil-replace-with-register-install)) + +(use-package evil-commentary + :after (evil) + :commands (evil-commentary-mode) :demand t + :config (evil-commentary-mode +1)) + +(use-package evil-expat ; for :reverse, :remove, :rename, :colo, :g*, ... ex commands + :after (evil)) + +(use-package evil-surround + :after (evil) + :commands (global-evil-surround-mode) :demand t + :config (global-evil-surround-mode +1)) + +(use-package evil-smartparens + :after (evil smartparens) + :hook smartparens-enabled-hook) + +(use-package evil-multiedit + ;; See: https://github.com/hlissner/evil-multiedit#usage + :commands (evil-multiedit-default-keybinds) :demand t + :config (evil-multiedit-default-keybinds)) + +(use-package evil-args + :after (evil) + :config + ;; Bind evil-args text objects only. + ;; See https://github.com/wcsmith/evil-args for more bindings. + (define-key evil-inner-text-objects-map "a" 'evil-inner-arg) + (define-key evil-outer-text-objects-map "a" 'evil-outer-arg)) + +(use-package evil-numbers + :after (evil) + :bind (("+" . evil-numbers/inc-at-pt) + ("-" . evil-numbers/dec-at-pt))) + +(use-package evil-goggles ; visual previews for edit operations + :after (evil) + :commands (evil-goggles-mode evil-goggles-use-diff-faces) :demand t + :config + (evil-goggles-mode +1) + (evil-goggles-use-diff-faces) + :diminish evil-goggles-mode) + +(use-package evil-traces ; visual previews for :ex commands + :after (evil) + :commands (evil-traces-mode evil-traces-use-diff-faces) :demand t + :config + (evil-traces-mode +1) + (evil-traces-use-diff-faces) + :diminish evil-traces-mode) + +(use-package evil-markdown + :after (evil markdown-mode) + :hook markdown-mode) + +(use-package evil-tex + :after (evil tex) + :hook tex-mode) + +(use-package evil-text-object-python + :after (evil python) + :hook (python-mode . evil-text-object-python-add-bindings)) + +;; Lisp features +(use-package aggressive-indent + :hook (; scheme-mode + emacs-lisp-mode common-lisp-mode)) + +(defun tw/lisp-evil-setup () + "Set up evil in general `lisp-mode' buffers." + (setq-local evil-symbol-word-search t + ;; https://github.com/wcsmith/evil-args#customization + evil-args-delimiters '(" "))) + +;; Sadly, not all Lisp modes derive from `lisp-mode'. +(add-hook 'lisp-mode-hook #'tw/lisp-evil-setup) +(add-hook 'lisp-data-mode-hook #'tw/lisp-evil-setup) ; for elisp +(add-hook 'scheme-mode-hook #'tw/lisp-evil-setup) + +;; buffer-locally set `evil-lookup-func' (used on K keys) for +;; languages where something better than man pages is available +;; (e.g. `describe-symbol' for elisp). +(defun tw/elisp-lookup-func () + "Show help in `emacs-lisp-mode' buffers." + (let ((sym (symbol-at-point))) + (if sym (describe-symbol sym) + (call-interactively #'describe-symbol)))) + +(defun tw/emacs-lisp-evil-setup () + "Set up evil in `emacs-lisp-mode' buffers." + (setq-local evil-lookup-func #'tw/elisp-lookup-func)) + +(add-hook 'emacs-lisp-mode-hook #'tw/emacs-lisp-evil-setup) + +(evil-define-key '(normal visual) emacs-lisp-mode-map + (kbd "eb") #'eval-buffer + (kbd "ef") #'eval-defun + (kbd "er") #'eval-region + (kbd "el") #'eval-last-sexp + (kbd "ep") #'eval-print-last-sexp) + +;;; init.el ends here -- cgit v1.2.3