aboutsummaryrefslogtreecommitdiff
path: root/tw/services/files/emacs-init.el
diff options
context:
space:
mode:
Diffstat (limited to 'tw/services/files/emacs-init.el')
-rw-r--r--tw/services/files/emacs-init.el1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/tw/services/files/emacs-init.el b/tw/services/files/emacs-init.el
new file mode 100644
index 00000000..0f20782d
--- /dev/null
+++ b/tw/services/files/emacs-init.el
@@ -0,0 +1,1027 @@
+;;; 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