diff options
Diffstat (limited to 'tw/home/files/emacs-init.el')
-rw-r--r-- | tw/home/files/emacs-init.el | 1027 |
1 files changed, 0 insertions, 1027 deletions
diff --git a/tw/home/files/emacs-init.el b/tw/home/files/emacs-init.el deleted file mode 100644 index 0f20782d..00000000 --- a/tw/home/files/emacs-init.el +++ /dev/null @@ -1,1027 +0,0 @@ -;;; init.el --- Emacs configuration. -*- lexical-binding: t -*- -;;; Commentary: -;;; Code: - -(startup-redirect-eln-cache - (expand-file-name "emacs/eln" (or (getenv "XDG_CACHE_HOME") "~/.cache/"))) -(add-hook 'after-init-hook #'native-compile-prune-cache) - -;; 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 &optional create) - "Get the name of a file or directory called NAME under $XDG_<TYPE>_HOME/emacs. -If CREATE is true and the resulting directory does not exist, create it." - (let ((dir (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)))))) - (when (and create (not (file-accessible-directory-p dir))) - (make-directory dir t)) - dir)) - -;; 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.") - ;; 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.") - (pixel-scroll-precision-mode t "Enable pixel-by-pixel scrolling, e.g. to handle inline images.") - ;; Niceties. - (tramp-default-method "scpx" "ssh and scp hang forever. scpx is faster than sshx for large files.") - (global-hl-line-mode t "Highlight the current line in all buffers.") - (indicate-empty-lines t "Show a little marker in the margin for lines past EOF.") - (column-number-mode t "Show the column number in the statusline.") - (electric-pair-mode t "Auto-pair suitable characters like parentheses.") - (tab-always-indent complete "Enable completion-on-tab.") - (completion-cycle-threshold 6 "Allow cycling through completions if there are 6 or fewer of them.") - (completion-styles (basic partial-completion) "Enable fast completion styles.") - (shell-kill-buffer-on-exit t "Kill *shell* buffers as soon as their shell session exits.") - ;; Indentation, formatting. - (indent-tabs-mode nil "Always use spaces to indent.") - (sentence-end-double-space nil "Use a single space after a sentence.") - (fill-column 78 "Make hard-wrapped text a bit wider.") - (require-final-newline t "Always add a final newline on save, if there is none.") - ;; Make Emacs a good Guix citizen. - (package-archives nil "Don't fetch packages from the internet; only get them from Guix.") - ;; Default mode. - (major-mode text-mode "Use `text-mode' by default in new buffers, not `fundamental-mode'.") - ;; Recent files and history. Keep them out of ~/.config. - (package-user-dir ,(tw/xdg-emacs-subdir 'data "elpa") "Save ELPA-related files here.") - (auto-save-list-file-prefix ,(tw/xdg-emacs-subdir 'data "auto-save-list/saves-") "Put auto-save lists here.") - (make-backup-files nil "Don't create backup files. They're annoying.") - (backup-directory-alist (("." . ,(tw/xdg-emacs-subdir 'data "backup"))) "Put backup files in a sensible place.") - (backup-by-copying t "Avoid breaking hardlinks when making backup files.") - (auto-save-file-name-transforms - ;; `file-name-as-directory' is important, since Emacs takes the directory part when UNIQUIFY is t. - ((".*" ,(file-name-as-directory (tw/xdg-emacs-subdir 'data "auto-save" t)) t)) - "Put auto-save #files# in a sensible directory.") - (recentf-max-saved-items 1000 "Save lots of recently-opened files.") - (recentf-save-file ,(tw/xdg-emacs-subdir 'data "recentf.el") "Save recently-opened files here.") - (recentf-mode t "Save recently-opened files.") - (savehist-file ,(tw/xdg-emacs-subdir 'data "savehist.el") "Save minibuffer history here.") - (savehist-mode t "Save minibuffer history on quit."))) - -(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) . bash-ts-mode) - (,(rx ".install" eos) . bash-ts-mode) - (,(rx (or bos "/") "COMMIT_EDITMSG" eos) . diff-mode) ; useful for `git commit -v' - (,(rx bos "/tmp/neomutt-") . mail-mode) - (,(rx ".eml" eos) . 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) - -(defun tw/show-trailing-whitespace () - "Highlight trailing spaces in the current buffer." - (setq-local show-trailing-whitespace t)) - -(mapc (lambda (hook) - (add-hook hook #'tw/show-trailing-whitespace)) - '(prog-mode-hook conf-mode-hook yaml-mode-hook alidist-mode-hook)) - -(defun tw/enable-word-wrap () - "Enable word wrapping." - (toggle-word-wrap +1)) -(add-hook 'markdown-mode-hook #'tw/enable-word-wrap) -(add-hook 'org-mode-hook #'tw/enable-word-wrap) - -;; `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. - -(use-package gcmh ; "garbage collector magic hack": run GC when not in focus - :config (gcmh-mode +1) - :diminish gcmh-mode) - -;; Look and feel -(set-face-attribute 'default nil :family "Hermit" :height 100) -;; For some reason, Emacs doesn't detect italic support, and falls back to -;; underlining. Stop it from doing this and use italics instead. -(set-face-attribute 'italic nil :slant 'italic :underline nil) - -(use-package catppuccin-theme - :load-path "./" - :custom - (catppuccin-flavor 'mocha "Use the darkest Catppuccin theme.") - (catppuccin-italic-comments t "Make comments italic. It looks nicer.") - (catppuccin-italic-variables t "Make variable names italic. It looks nicer.") - :config (catppuccin-reload)) - -(use-package smart-mode-line - :hook (after-init . sml/setup) - :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.")) - -;; General editor behaviour. -;; TODO: Move from ivy + counsel to vertico + orderless + consult + marginalia -;; (+ embark?), to integrate better with vanilla Emacs and `completing-read'. -;; https://github.com/minad/vertico -- light completion engine -;; https://github.com/minad/vertico#child-frames-and-popups -;; https://github.com/minad/vertico#complementary-packages -;; https://github.com/minad/marginalia -- docstrings in M-x menu -;; https://github.com/oantolin/orderless -- regex search for vertico -;; https://github.com/minad/consult -- collection of commands using vertico -;; https://github.com/oantolin/embark -- make vertico better depending on thing at point - -(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 <leader> - :bind (("<leader>SPC" . counsel-M-x) ; <leader><leader> doesn't work - ("<leader>fr" . counsel-buffer-or-recentf) - :map evil-visual-state-map - ("<leader>SPC" . counsel-M-x)) - :commands (counsel-mode) :demand t - :config (counsel-mode +1) - :diminish counsel-mode) - -(use-package dash-docs - :custom - (dash-docs-docsets-path - (file-name-as-directory (tw/xdg-emacs-subdir 'data "dash-docsets" t)) - "Store docsets in the XDG data directory.") - (dash-docs-browser-func 'eww "Open documentation pages using `eww' instead of an external browser.") - (dash-docs-enable-debugging nil "Disable popping up useless warnings.")) - -(defun tw/counsel-dash-is-help () - "Install `counsel-dash-at-point' as `evil-lookup-func'." - ;; Note: `evil-lookup-func' is already set to something else by - ;; `tw/help-is-eldoc' for `eglot-mode'. - (setq-local evil-lookup-func #'counsel-dash-at-point - counsel-dash-docsets - (cl-case major-mode - (lisp-mode '("Common Lisp")) - ((python-mode python-ts-mode) '("Python 3")) - (c++-mode '("C++")) - (cmake-mode '("CMake")) - (puppet-mode '("Puppet")) - (yaml-mode '("Ansible")) - (tcl-mode '("Tcl")) - (html-mode '("HTML" "CSS")) - ((css-mode css-ts-mode) '("CSS")) - (web-mode '("HTML" "CSS"))))) - -(use-package counsel-dash - :after (dash-docs which-key) - :commands (counsel-dash-at-point) :demand t - :init (which-key-add-key-based-replacements - "<leader>d" '("docs" . "Documentation")) - :bind (("<leader>K" . counsel-dash-at-point) - ("<leader>dK" . counsel-dash) - ("<leader>di" . counsel-dash-install-docset) - ("<leader>da" . counsel-dash-activate-docset) - ("<leader>dd" . counsel-dash-deactivate-docset)) - :hook (( lisp-mode python-mode python-ts-mode cmake-mode c++-mode puppet-mode yaml-mode - tcl-mode html-mode css-mode css-ts-mode web-mode) - . tw/counsel-dash-is-help) - :config - ;; Activate all installed docsets by default. - (setq counsel-dash-common-docsets (dash-docs-installed-docsets))) - -(use-package rainbow-mode - :after (evil) - :bind (("<leader>tR" . rainbow-mode))) - -(use-package form-feed - :commands (global-form-feed-mode) :demand t - :config (global-form-feed-mode +1) - :diminish form-feed-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 yaml-mode alidist-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 (("<leader>U" . undo-tree-visualize)) - :custom - (undo-tree-history-directory-alist - `(("." . ,(file-name-as-directory (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) - :diminish undo-tree-mode) - -;; IDE-like features. -(use-package project - :after (which-key evil) - :init - (which-key-add-key-based-replacements - "<leader>p" '("project" . "Project")) - (evil-define-key '(normal visual) 'global - (kbd "<leader>fp") #'project-find-file) ; also <leader>pf - :bind-keymap ("<leader>p" . project-prefix-map)) - -(use-package vc - :after (which-key evil) - :init (which-key-add-key-based-replacements - "<leader>v" '("vc" . "Version control") - "<leader>vM" '("merge" . "Version control merging")) - :bind-keymap ("<leader>v" . vc-prefix-map)) - -(use-package log-edit - :after (evil vc) - :config - (evil-define-key '(normal visual) log-edit-mode-map - (kbd "<localleader>\\") #'log-edit-done - (kbd "<localleader>a") #'log-edit-insert-changelog - (kbd "<localleader>d") #'log-edit-show-diff - (kbd "<localleader>f") #'log-edit-show-files - (kbd "<localleader>k") #'log-edit-kill-buffer - (kbd "<localleader>w") #'log-edit-generate-changelog-from-diff)) - -(use-package company - :config (global-company-mode +1) - :diminish company-mode) - -(use-package company-quickhelp - :after (company) - :config (company-quickhelp-mode +1) - :diminish company-quickhelp-mode) - -(use-package company-posframe - :after (company) - :config (company-posframe-mode +1) - :diminish company-posframe-mode) - -(use-package flyspell - :hook mail-mode) - -(use-package flymake - :after (evil which-key) - :demand t ; needed for `flymake-collection' - :hook (prog-mode yaml-mode alidist-mode) - :init (which-key-add-key-based-replacements - "<leader>e" '("errors" . "Flymake")) - :bind (("<leader>eb" . flymake-start) - ("<leader>ec" . 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. - ; `flymake-show-diagnostic' only says "Nothing at point". - ("<leader>el" . flymake-show-buffer-diagnostics) - ("<leader>ep" . flymake-show-project-diagnostics) - ("<leader>en" . flymake-goto-next-error) - ("<leader>eN" . flymake-goto-prev-error) - ("<leader>ev" . flymake-running-backends) - ("<leader>eV" . flymake-disabled-backends)) - :custom - (flymake-suppress-zero-counters nil "Show severity counters even when they are zero.")) - -(use-package flymake-collection - :after (flymake) - :demand t ; we need it loaded now - ;; 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." - (when (eglot-managed-p) - (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 python-ts-mode c-mode c++-mode c-or-c++-ts-mode) . eglot-ensure) - (eglot-managed-mode . tw/help-is-eldoc)) - :commands (eglot) - :functions (eglot-managed-p) - :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.")) - -;; Tree-sitter -;; TODO: Try any/all of the following new tree-sitter-based major modes. -;; Enable them using the following, replacing the relevant "old" major mode: -;; (add-to-list 'major-mode-remap-alist '(ruby-mode . ruby-ts-mode)) -;; New major mode 'css-ts-mode'. -;; New major mode 'dockerfile-ts-mode'. -;; New major mode 'ruby-ts-mode'. - -(mapc (lambda (dir) - (add-to-list 'treesit-extra-load-path (file-name-as-directory (expand-file-name dir)))) - '("/run/current-system/profile/lib/tree-sitter" - "~/.guix-home/profile/lib/tree-sitter" - "~/.guix-profile/lib/tree-sitter")) - -(use-package treesit - :custom - (treesit-font-lock-level 4 "Enable Angry Fruit Salad mode.")) - -;; Non-LSP language modes. -(use-package c-ts-mode - :init - (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode)) - (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode)) - (add-to-list 'major-mode-remap-alist '(c-or-c++-mode . c-or-c++-ts-mode))) - -(use-package cmake-ts-mode - :mode (rx (or (: (or bos "/") "CMakeLists.txt") ".cmake") eos)) - -(use-package json-ts-mode - :mode (rx ".json" eos) - :config - (evil-define-key '(normal visual) json-ts-mode-map - (kbd "<localleader>==") #'json-pretty-print - (kbd "<localleader>=b") #'json-pretty-print-buffer - (kbd "<localleader>=o") #'json-pretty-print-ordered - (kbd "<localleader>=B") #'json-pretty-print-buffer-ordered)) - -(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 haskell-mode - :mode (rx (or ".hs" ".lhs" ".hsc" ".cpphs" ".c2hs") eos)) - -(use-package hcl-mode - :mode (rx "." (or "hcl" "nomad") eos)) - -(use-package mmm-mode - :commands (mmm-mode) - ;; Don't highlight submodes specially at all. The default background is annoying. - :custom-face (mmm-default-submode-face ((t (:background unspecified))))) - -(use-package puppet-mode - :mode (rx ".pp" eos)) - -(use-package python - :after (flymake-collection) - :commands (python-mode python-ts-mode) - :mode (((rx ".py" (? (or ?\i ?\w)) eos) . python-ts-mode) - ((rx ".aurora" eos) . python-ts-mode)) - :config - ;; Disable all flymake-collection linters in Python modes, since eglot/pylsp - ;; should take care of it. It doesn't do type checking, so enable mypy. - (cl-dolist (mode '(python-ts-mode python-mode)) - (add-to-list 'flymake-collection-config `(,mode flymake-mypy)))) - -(use-package rec-mode - :mode (rx ".rec" eos)) - -(use-package sh-script ; built-in - ;; Use `bash-ts-mode' instead of `sh-mode' if possible. - ;; `bash-ts-mode' falls back to `sh-mode' if necessary. - ;; Manually configuring :mode etc would be annoying, since there are a lot of entries. - :config (add-to-list 'major-mode-remap-alist '(sh-mode . bash-ts-mode)) - :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) - :custom - (web-mode-css-indent-offset 2 "Indent CSS by two spaces.")) - -(use-package yaml-mode - :mode (rx (or (seq ".y" (? "a") "ml") - (seq "aliPublish" (* (not ?/)) ".conf")) - eos)) - -(defun tw/ledger-format-on-save () - "Re-indent the entire file." - ;; Subset of `ledger-mode-clean-buffer'. That also sorts the buffer, which I don't want. - (save-excursion - (let ((start (point-min-marker)) - (end (point-max-marker))) - (untabify start end) - (ledger-post-align-postings start end) - (ledger-mode-remove-extra-lines)))) - -(defun tw/enable-ledger-format-on-save () - "Enable reformating the open file on save." - (add-hook 'before-save-hook #'tw/ledger-format-on-save 0 t)) - -(use-package ledger-mode - :after (evil) - :commands (ledger-mode) - :mode (rx ".journal" eos) - :hook (ledger-mode . tw/enable-ledger-format-on-save) - :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.") - :config - (evil-define-key 'normal ledger-mode-map - (kbd "TAB") #'ledger-indent-line)) - -(use-package lisp - :init (which-key-add-key-based-replacements - "<leader>k" '("sexp-nav" . "S-expression navigation")) - :bind (("<leader>kl" . forward-sexp) - ("<leader>kh" . backward-sexp) - ("<leader>kL" . forward-list) - ("<leader>kH" . backward-list) - ("<leader>kj" . down-list) - ("<leader>kk" . up-list) - ("<leader>kK" . backward-up-list) - ("<leader>kd" . kill-sexp) - ("<leader>kD" . backward-kill-sexp) - ("<leader>kb" . beginning-of-defun) - ("<leader>kB" . beginning-of-defun-comments) - ("<leader>ke" . end-of-defun) - ("<leader>kv" . mark-sexp) - ("<leader>kV" . mark-defun) - ("<leader>kN" . narrow-to-defun) - ("<leader>ks" . insert-pair) - ("<leader>kr" . raise-sexp) - ("<leader>kc" . check-parens))) - -(defun tw/resize-repl-window () - "Make the REPL window small, so it stays out of the way." - (shrink-window (- (window-height) 5))) - -(use-package geiser - :after (evil) - :commands (geiser - geiser-eval-buffer geiser-eval-definition geiser-eval-region - geiser-eval-last-sexp geiser-mode-switch-to-repl - geiser-mode-switch-to-repl-and-enter) - :hook ((scheme-mode . geiser-autodoc-mode) - (geiser-repl-mode . tw/resize-repl-window)) - :config - (evil-define-key '(normal visual) scheme-mode-map - (kbd "<localleader>z") #'geiser-mode-switch-to-repl - (kbd "<localleader>Z") #'geiser-mode-switch-to-repl-and-enter - (kbd "<localleader>eb") #'geiser-eval-buffer - (kbd "<localleader>ef") #'geiser-eval-definition - (kbd "<localleader>er") #'geiser-eval-region - (kbd "<localleader>el") #'geiser-eval-last-sexp) - :defines scheme-mode-map) - -(use-package geiser-guile - :after (geiser)) - -(use-package sly - :after (evil) - :hook ((lisp-mode . sly-mode) ; `common-lisp-mode' is `lisp-mode'. - (sly-mrepl-mode . tw/resize-repl-window)) - :config - (evil-define-key '(normal visual) lisp-mode-map - (kbd "<localleader>C-c") #'sly-interrupt - (kbd "<localleader>z") #'sly - (kbd "<localleader>Z") #'sly-mrepl-sync - (kbd "<localleader>i") #'sly-inspect - (kbd "<localleader>D") #'sly-disassemble-symbol - (kbd "<localleader>E") #'sly-edit-value - (kbd "<localleader>eT") #'sly-list-threads ; eval requests get a new thread each - (kbd "<localleader>e:") #'sly-interactive-eval - (kbd "<localleader>el") #'sly-eval-last-expression - (kbd "<localleader>ep") #'sly-pprint-eval-last-expression - (kbd "<localleader>eb") #'sly-eval-buffer - (kbd "<localleader>ef") #'sly-eval-defun - (kbd "<localleader>er") #'sly-eval-region - (kbd "<localleader>eF") #'sly-compile-defun - (kbd "<localleader>eB") #'sly-compile-file - (kbd "<localleader>eL") #'sly-compile-and-load-file - (kbd "<localleader>eR") #'sly-compile-region - (kbd "<localleader>eU") #'sly-undefine-function - (kbd "<localleader>eM") #'sly-remove-method - (kbd "<localleader>dd") #'sly-describe-symbol - (kbd "<localleader>df") #'sly-describe-function - (kbd "<localleader>da") #'sly-apropos - (kbd "<localleader>dA") #'sly-apropos-all - (kbd "<localleader>dg") #'sly-edit-definition - (kbd "<localleader>dC-o") #'sly-pop-find-definition-stack - (kbd "<localleader>dG") #'sly-edit-uses - (kbd "<localleader>dwc") #'sly-who-calls - (kbd "<localleader>dwC") #'sly-calls-who - (kbd "<localleader>dwr") #'sly-who-references - (kbd "<localleader>dwb") #'sly-who-binds - (kbd "<localleader>dws") #'sly-who-sets - (kbd "<localleader>dwm") #'sly-who-macroexpands - (kbd "<localleader>dwS") #'sly-who-specializes - (kbd "<localleader>dhs") #'hyperspec-lookup ; hyperspec.el is bundled with sly; opens in browser - (kbd "<localleader>dhf") #'hyperspec-lookup-format - (kbd "<localleader>dhm") #'hyperspec-lookup-reader-macro - (kbd "<localleader>cl") #'sly-list-connections - (kbd "<localleader>cn") #'sly-next-connection - (kbd "<localleader>cp") #'sly-prev-connection - (kbd "<localleader>m1") #'sly-expand-1 - (kbd "<localleader>mm") #'sly-macroexpand-all - (kbd "<localleader>mf") #'sly-format-string-expand - (kbd "<localleader>tt") #'sly-trace-dialog-toggle-trace - (kbd "<localleader>ts") #'sly-trace-dialog - (kbd "<localleader>tf") #'sly-toggle-trace-fdefinition - (kbd "<localleader>tF") #'sly-untrace-all - (kbd "<localleader>ss") #'sly-stickers-dwim ; an ephemeral `print' around the thing at point - (kbd "<localleader>sr") #'sly-stickers-replay - (kbd "<localleader>sb") #'sly-stickers-toggle-break-on-stickers - (kbd "<localleader>sf") #'sly-stickers-fetch - (kbd "<localleader>sn") #'sly-stickers-next-sticker - (kbd "<localleader>sp") #'sly-stickers-prev-sticker - (kbd "<localleader>ta") #'sly-autodoc-mode) - :custom - (sly-mrepl-history-file-name (tw/xdg-emacs-subdir 'data "sly-mrepl-history"))) - -;; Org-mode -(use-package org - :commands (org-mode) - :mode ((rx ".org" eos) . org-mode) - :custom - (org-latex-src-block-backend 'minted "Colourise source code.") - (org-latex-packages-alist - '(("" "svg") - ("" "minted")) - "Use svg and syntax highlighting packages.") - (org-latex-pdf-process - '("latexmk -shell-escape -f -pdf -%latex -interaction=nonstopmode -output-directory=%o %f") - "Allow -shell-escape needed by svg and minted packages.")) - -(use-package ob ; org-babel - :after (org) - :custom - (org-confirm-babel-evaluate nil "Allow running code blocks without confirmation.") - ;; List of supported languages: - ;; https://orgmode.org/worg/org-contrib/babel/languages/index.html - (org-babel-load-languages - '((emacs-lisp . t) - (lisp . t) - (dot . t) - (python . t) - (gnuplot . t) - (rec . t)) ; see `ob-rec' below - "Load bindings for more languages for use in #+begin_src blocks.")) - -(defun tw/latex-section-commands (name) - "Create a pair of section commands like (\"\\NAME{%s}\" . \"\\NAME*{%s}\"). -For use in `org-latex-classes'." - (cons (format "\\%s{%%s}" name) (format "\\%s*{%%s}" name))) -(defconst tw/latex-part (tw/latex-section-commands "part") - "Part LaTeX commands for `org-latex-classes'.") -(defconst tw/latex-chapter (tw/latex-section-commands "chapter") - "Chapter LaTeX commands for `org-latex-classes'.") -(defconst tw/latex-section-and-below - (mapcar #'tw/latex-section-commands - '("section" "subsection" "subsubsection" "paragraph" "subparagraph")) - "Section to subparagraph LaTeX commands for `org-latex-classes'.") - -(use-package ox-latex ; org-export-latex - :after (org) - :custom - (org-latex-classes - `(("paperlike" "\\documentclass{paperlike}" . ,tw/latex-section-and-below) - ("examtext" "\\documentclass{examtext}" . ,tw/latex-section-and-below) - ("minutes" "\\documentclass{minutes}" . ,tw/latex-section-and-below) - ("mapreport" "\\documentclass{mapreport}" ,tw/latex-chapter . ,tw/latex-section-and-below) - ("pt3report" "\\documentclass{pt3report}" ,tw/latex-chapter . ,tw/latex-section-and-below) - ("article" "\\documentclass{article}" . ,tw/latex-section-and-below) - ("scrartcl" "\\documentclass{scrartcl}" . ,tw/latex-section-and-below) - ("report" "\\documentclass{report}" ,tw/latex-part ,tw/latex-chapter . ,tw/latex-section-and-below) - ("report-noparts" "\\documentclass{report}" ,tw/latex-chapter . ,tw/latex-section-and-below) - ("book" "\\documentclass{book}" ,tw/latex-part ,tw/latex-chapter . ,tw/latex-section-and-below) - ("book-noparts" "\\documentclass{book}" ,tw/latex-chapter . ,tw/latex-section-and-below) - ("checklist" "\\documentclass{checklist}" . ,tw/latex-section-and-below)) - "Define more documentclasses for org-latex.")) - -(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 "<localleader>@") "Use localleader for `outline-minor-mode' keybindings.")) - -;; My own custom packages, and stuff that isn't on MELPA. -(use-package actionlint - :after (flymake) - :load-path "include/" - :hook ((yaml-mode yaml-ts-mode) . actionlint-setup)) - -(use-package alidist-mode - :after (flymake) - :load-path "include/" - :commands (alidist-mode) - :mode (rx (or bot "/") "alidist/" (1+ (not ?\/)) ".sh" eot)) - -(use-package flymake-guile - :after (flymake) - :load-path "include/" - :hook (scheme-mode . flymake-guile-enable)) - -(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 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-echo-state nil "Don't show the '--- INSERT ---' string in the echo area on evil state changes.") - (evil-undo-system 'undo-tree "Use `undo-tree' for evil's undo-redo function.") - (evil-search-module 'evil-search "Use evil's built-in search function, for search history support.") - (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.") - (evil-symbol-word-search t "Always search by full variable names when using * and #.") - :config - (evil-mode +1) - (evil-set-leader '(normal visual) (kbd "SPC")) ; <leader> - (evil-set-leader '(normal visual) (kbd "\\") t) ; <localleader> - (evil-define-key '(normal motion) diff-mode-shared-map ; not `diff-mode-map', else toggling `read-only-mode' destroys the binding - (kbd "<localleader>\\") #'read-only-mode) ; mirror default binding from evil-collection - (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 - ;; <localleader> 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 "<leader>:") #'eval-expression - (kbd "<leader>TAB") #'tw/switch-to-other-buffer - (kbd "<leader>bb") #'switch-to-buffer - (kbd "<leader>bd") #'kill-current-buffer - (kbd "<leader>bn") #'tw/new-buffer - (kbd "<leader>br") #'revert-buffer-quick - (kbd "<leader>bs") #'scratch-buffer - (kbd "<leader>bw") #'read-only-mode - (kbd "<leader>bx") #'kill-buffer-and-window - (kbd "<leader>fd") #'tw/delete-current-buffer-file - (kbd "<leader>ff") #'find-file - (kbd "<leader>fR") #'rename-visited-file - (kbd "<leader>fs") #'save-buffer - (kbd "<leader>h") help-map - (kbd "<leader>hw") #'which-key-show-top-level - (kbd "<leader>sc") #'evil-ex-nohighlight - (kbd "<leader>td") #'toggle-debug-on-error - (kbd "<leader>tf") #'auto-fill-mode - (kbd "<leader>tl") #'toggle-truncate-lines - (kbd "<leader>tn") #'display-line-numbers-mode - (kbd "<leader>u") #'universal-argument - (kbd "<leader>w") evil-window-map - (kbd "<leader>wd") #'evil-window-delete ; analogous to "<leader>bd" - (kbd "<leader>wx") #'kill-buffer-and-window) ; analogous to "<leader>bx" - (which-key-add-key-based-replacements - ;; Names are a `cons' of a short name and a long name. - ;; E.g. for <leader>b, "buffer" is shown under "b" in the "<leader>" menu, - ;; while "Buffers" is shown as the title in the "<leader>b" menu. - "<leader>b" '("buffer" . "Buffers") - "<leader>f" '("file" . "Files") - "<leader>h" '("help" . "General help and documentation") - "<leader>q" '("quit" . "Finish editing the current buffer in emacsclient") - "<leader>s" '("search" . "Search operations and options") - "<leader>t" '("toggle" . "Toggles and quick settings") - "<leader>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) - :diminish evil-collection-unimpaired-mode - :custom - ;; Without `evil-collection-key-blacklist', in `diff-mode', space isn't - ;; assigned to the leader key automatically, unlike in other modes. - (evil-collection-key-blacklist '("SPC" "\\") "Don't bind to our leader keys at all.") - (evil-collection-setup-minibuffer t "Use evil-collection in minibuffer to match `evil-want-minibuffer'.")) - -(use-package evil-org - :after (evil org) - :hook org-mode - :config - (evil-define-key '(normal visual) org-mode-map - (kbd "<localleader>\\") #'org-ctrl-c-ctrl-c - (kbd "<localleader>ib") #'org-insert-structure-template - (kbd "<localleader>id") #'org-insert-drawer - (kbd "<localleader>iD") #'org-insert-time-stamp - (kbd "<localleader>ih") #'org-insert-heading - (kbd "<localleader>iH") #'org-insert-subheading - (kbd "<localleader>it") #'org-insert-todo-heading - (kbd "<localleader>iT") #'org-insert-todo-subheading - (kbd "<localleader>ii") #'org-insert-item - (kbd "<localleader>il") #'org-insert-link - (kbd "<localleader>p") #'org-set-property - (kbd "<localleader>t") #'org-set-tags - ;; Source code block editing - (kbd "<localleader>'") #'org-edit-src-code - (kbd "<localleader>e") #'org-export-dispatch) - (evil-define-key '(normal visual) org-src-mode-map - (kbd "<localleader>'") #'org-edit-src-exit - (kbd "<localleader>\\") #'org-edit-src-save - (kbd "<localleader>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 ; e.g. "gcc" / "gcap" to comment out blocks of text - :after (evil) - :commands (evil-commentary-mode) :demand t - :config (evil-commentary-mode +1) - :diminish evil-commentary-mode) - -(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 smartparens ; required by evil-cleverparens - ;; :custom - ;; (sp-sexp-prefix '() "Set up Guix gexp-related sexp prefixes.") - ) - -(use-package evil-cleverparens - :after (evil smartparens) - :hook ((lisp-mode lisp-data-mode scheme-mode) . evil-cleverparens-mode) - :custom - (evil-cleverparens-use-additional-movement-keys nil "Disable overriding of standard vim bracket navigation keys.")) - -(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 (("<leader>+" . evil-numbers/inc-at-pt) - ("<leader>-" . 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) - -;; Lots of useful text objects and keybinds: -;; https://github.com/iyefrat/evil-tex#incomplete-showcase -(use-package evil-tex - :after (evil tex-mode) - :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 lisp-mode sh-mode bash-ts-mode)) - -(defun tw/find-asd-systems (directory) - "Return a list of Common Lisp .asd systems found in DIRECTORY." - (let ((asd-rx (rx ".asd" eos))) - ;; `locate-dominating-file' will call this function once with the original - ;; file name as DIRECTORY, but `directory-files' fails if its argument is - ;; a regular file, so protect against this. - (and (directory-name-p directory) - (mapcar (lambda (file) - (string-trim-right file asd-rx)) - (directory-files directory nil asd-rx))))) - -(defun tw/lisp-project-setup () - "Set up a Lisp REPL for the current project." - (when-let ((fname (buffer-file-name)) - (project-directory - (or (locate-dominating-file fname "guix.scm") - (locate-dominating-file fname #'tw/find-asd-systems) - (project-current nil (file-name-directory fname))))) - (cd project-directory) - (setq-local - inferior-lisp-program - `(;; If a guix.scm file exists, run Lisp in a Guix shell to get dependencies. - ,@(and (file-exists-p (file-name-concat project-directory "guix.scm")) - '("guix" "shell" "-Df" "guix.scm" "--")) - "sbcl" "--noinform" - ;; Load all defined asdf systems. - ,@(mapcan (lambda (system) - (list "--load" (format "%s.asd" system) - "--eval" (format "(require '%s)" system))) - ;; Heuristic: shorter names are earlier in the dependency tree. - ;; For example, X-test.asd depends on X.asd. - (sort (tw/find-asd-systems project-directory) - (lambda (s1 s2) - (< (length s1) (length s2))))) - ;; Assume the project directory name is the name of the main package. - "--eval" ,(format "(in-package #:%s)" - (file-name-base - (directory-file-name project-directory))))))) - -(use-package inf-lisp - :after (lisp-mode) - :hook (lisp-mode . tw/lisp-project-setup) - :custom - (inferior-lisp-program "sbcl")) - -(defun tw/lisp-evil-setup () - "Set up evil in general `lisp-mode' buffers." - ;; https://github.com/wcsmith/evil-args#customization - (setq-local 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 "<localleader>eb") #'eval-buffer - (kbd "<localleader>ef") #'eval-defun - (kbd "<localleader>er") #'eval-region - (kbd "<localleader>el") #'eval-last-sexp - (kbd "<localleader>ep") #'eval-print-last-sexp) - -;; Guix-related .dir-locals.el entries. These are fine; don't prompt every time. -(add-to-list 'safe-local-variable-values '(geiser-repl-per-project-p . t)) -(add-to-list 'safe-local-variable-values '(geiser-guile-binary . ("guix" "repl"))) -(mapc (apply-partially #'add-to-list 'safe-local-eval-forms) - '((modify-syntax-entry 126 "'") - (modify-syntax-entry 36 "'") - (modify-syntax-entry 43 "'") - (let ((root-dir-unexpanded (locate-dominating-file default-directory ".dir-locals.el"))) - (when root-dir-unexpanded - (let* ((root-dir (file-local-name (expand-file-name root-dir-unexpanded))) - (root-dir* (directory-file-name root-dir))) - (unless (boundp 'geiser-guile-load-path) - (defvar geiser-guile-load-path 'nil)) - (make-local-variable 'geiser-guile-load-path) - (require 'cl-lib) - (cl-pushnew root-dir* geiser-guile-load-path :test #'string-equal)))) - (progn - (require 'lisp-mode) - (defun emacs27-lisp-fill-paragraph (&optional justify) - (interactive "P") - (or (fill-comment-paragraph justify) - (let ((paragraph-start - (concat paragraph-start "\\|\\s-*\\([(;\"]\\|\\s-:\\|`(\\|#'(\\)")) - (paragraph-separate - (concat paragraph-separate "\\|\\s-*\".*[,\\.]$")) - (fill-column - (if (and (integerp emacs-lisp-docstring-fill-column) - (derived-mode-p 'emacs-lisp-mode)) - emacs-lisp-docstring-fill-column - fill-column))) - (fill-paragraph justify)) - t)) - (setq-local fill-paragraph-function #'emacs27-lisp-fill-paragraph)) - - ;; Forms used by Guix upstream. - (add-to-list 'completion-ignored-extensions ".go") - (setq-local guix-directory (locate-dominating-file - default-directory ".dir-locals.el")) - (with-eval-after-load 'yasnippet - (let ((guix-yasnippets (expand-file-name - "etc/snippets/yas" - (locate-dominating-file - default-directory ".dir-locals.el")))) - (unless (member guix-yasnippets yas-snippet-dirs) - (add-to-list 'yas-snippet-dirs guix-yasnippets) - (yas-reload-all)))) - (let ((root-dir-unexpanded (locate-dominating-file - default-directory ".dir-locals.el"))) - (when root-dir-unexpanded - (let* ((root-dir (expand-file-name root-dir-unexpanded)) - (root-dir* (directory-file-name root-dir))) - (unless (boundp 'geiser-guile-load-path) - (defvar geiser-guile-load-path 'nil)) - (make-local-variable 'geiser-guile-load-path) - (require 'cl-lib) - (cl-pushnew root-dir* geiser-guile-load-path - :test #'string-equal)))))) - -;;; init.el ends here |