about summary refs log tree commit diff
path: root/modules/workstation/emacs/init.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/workstation/emacs/init.el')
-rw-r--r--modules/workstation/emacs/init.el285
1 files changed, 285 insertions, 0 deletions
diff --git a/modules/workstation/emacs/init.el b/modules/workstation/emacs/init.el
new file mode 100644
index 000000000000..11da5bc9fc95
--- /dev/null
+++ b/modules/workstation/emacs/init.el
@@ -0,0 +1,285 @@
+;; -*- lexical-binding: t -*-
+
+;;; UI customization ;;;
+(column-number-mode)
+(electric-pair-mode)
+(global-so-long-mode)
+(menu-bar-mode -1)
+(scroll-bar-mode -1)
+(show-paren-mode)
+(tool-bar-mode -1)
+
+(setq frame-title-format "%b — Emacs")
+(setq uniquify-buffer-name-style 'forward)
+(setq-default truncate-lines t)
+
+(load-theme 'monokai t)
+
+;; <102 or >109 will make Ioveska look squished rather than tall,
+;; narrow, and beautiful.
+(set-face-attribute 'default nil :height 104)
+
+;; Disabling bidirectional text will make Emacs less slow with very
+;; long lines.
+(setq bidi-paragraph-direction 'left-to-right)
+(setq bidi-inhibit-bpa t)
+
+;;; Utility functions ;;;
+
+;; Set an environment variable value for a single buffer.
+(defun setenv-local (variable &optional value substitute-env-vars)
+  ;; If process-environment is the global one, make a copy of it.
+  (when (eq process-environment (default-value 'process-environment))
+    (make-local-variable 'process-environment)
+    (setq process-environment (mapcar 'concat process-environment)))
+  (setenv variable value substitute-env-vars))
+
+(defun enable-ansi-color ()
+  (setenv-local "TERM" "dumb-emacs-ansi"))
+
+;;; Major modes ;;;
+(add-to-list 'auto-mode-alist '("\\.adoc\\'" . adoc-mode))
+(add-to-list 'auto-mode-alist '("\\.quirks\\'" . conf-unix-mode))
+(add-to-list 'auto-mode-alist '("\\.gni?\\'" . gn-mode))
+(add-to-list 'auto-mode-alist '("/muttrc\\'" . mutt-mode))
+(add-to-list 'auto-mode-alist '("\\.tmac\\'" . nroff-mode))
+(add-to-list 'auto-mode-alist '("\\.wrap\\'" . conf-unix-mode))
+(add-to-list 'auto-mode-alist '("/Cargo\\.lock\\'" . toml-mode))
+(add-to-list 'auto-mode-alist '("/\\.clang-format\\'" . yaml-mode))
+(add-to-list 'auto-mode-alist '("/up\\'" . execline-mode))
+(add-to-list 'auto-mode-alist '("/down\\'" . execline-mode))
+
+;;; auth-source ;;;
+(auth-source-pass-enable)
+
+;;; Backups ;;;
+(let ((backup-dir
+       (concat (file-name-as-directory user-emacs-directory) "backups")))
+  (make-directory backup-dir t)
+  (setq backup-directory-alist `(("." . ,backup-dir)))
+  (setq message-auto-save-directory backup-dir))
+
+;;; CC Mode ;;;
+(with-eval-after-load 'cc-styles
+  ;; Default to kernel style.
+  (add-to-list 'c-default-style '(other . "linux")))
+
+;;; Comint ;;;
+;; This relies on an Emacs patch to apply to async-shell-command.
+(setq comint-terminfo-terminal "dumb-emacs-ansi")
+
+;;; Diff Mode ;;;
+;; Unbind M-DEL from scroll-down-command, so it does the same thing as
+;; in the rest of Emacs (backward-kill-word).
+(with-eval-after-load 'diff-mode
+  (define-key diff-mode-map (kbd "M-DEL") nil))
+
+;;; Dired ;;;
+(with-eval-after-load 'dired
+  ;; Use human-readable sizes.
+  (setq dired-listing-switches
+	(combine-and-quote-strings `(,dired-listing-switches "-h")))
+
+  ;; Don't open new windows when clicking directory entries.
+  (define-key dired-mode-map [mouse-2] 'dired-find-file))
+
+;;; Direnv ;;;
+(direnv-mode)
+(global-set-key (kbd "C-c d") #'direnv-update-environment)
+
+;;; Ediff ;;;
+;; Don't make a new frame for Ediff controls.
+(setq ediff-window-setup-function 'ediff-setup-windows-plain)
+
+;;; EditorConfig ;;;
+(editorconfig-mode)
+
+;;; Eglot ;;;
+(with-eval-after-load 'eglot
+  ;; Override default LSPs for languages.
+  (add-to-list 'eglot-server-programs '((c++-mode c-mode) "clangd"))
+  (add-to-list 'eglot-server-programs '(rust-mode "rust-analyzer"))
+  (add-to-list 'eglot-server-programs '(nix-mode "rnix-lsp"))
+
+  ;; Underline all occurrences of the symbol at point in the current
+  ;; buffer (default is to bold instead).
+  (set-face-attribute 'eglot-highlight-symbol-face nil :inherit 'underline)
+
+  ;; Add keybindings for eglot actions.
+  (define-key eglot-mode-map (kbd "C-c e a") #'eglot-code-actions)
+  (define-key eglot-mode-map (kbd "C-c e r") #'eglot-rename))
+
+;; Tell rust-analyzer not to allow colons inside Rust import.
+(setq-default eglot-workspace-configuration
+	      '((:rust-analyzer . (:assist (:importMergeBehaviour "last")))))
+
+;;; Environment ;;;
+
+;; Provide a way to turn paging back on for modes like terminal emulators.
+(let ((pager (getenv "PAGER")))
+  (defun enable-pager ()
+    (setenv-local "PAGER" pager)))
+
+;; Set PAGER to the empty string, which Git and journalctl will
+;; interpret as an explicit opt-out of paging.
+(setenv "PAGER" "")
+
+;;; Eshell ;;;
+(add-hook 'eshell-mode-hook #'enable-ansi-color)
+
+;;; gdb-mi ;;;
+(with-eval-after-load "gdb-mi"
+  ;; Hide all the copyright and documentation messages at gdb startup.
+  (setq gud-gdb-command-name (concat gud-gdb-command-name " -q")))
+
+;;; Ibuffer ;;;
+;; Open ibuffer in other window, to match behaviour of the default
+;; list-buffers.
+(global-set-key (kbd "C-x C-b") (lambda () (interactive) (ibuffer t)))
+
+;;; ispell ;;;
+;; System locale is Esperanto, but I write in English much more, and
+;; spelling in Esperanto is easy anyway. ;)
+(setq ispell-dictionary "english")
+
+;;; Magit ;;;
+(setq magit-delete-by-moving-to-trash delete-by-moving-to-trash)
+(setq magit-repository-directories
+      `((,(expand-file-name "~/src") . 1)))
+(global-set-key (kbd "C-x g") #'magit-status)
+(global-set-key (kbd "C-x M-g") #'magit-dispatch)
+(with-eval-after-load "git-commit"
+  (add-to-list 'git-commit-trailers "Change-Id")
+  (add-to-list 'git-commit-trailers "Fixes")
+  (add-to-list 'git-commit-trailers "Message-Id"))
+
+;; Don't open a second window with a diff when committing.  I have
+;; commit.verbose set in git, so the diff is shown below the commit
+;; anyway, and it's annoying to have magit take over the whole frame
+;; because it means I can't refer to something else, like an email
+;; thread discussing the commit.  And sometimes the diff shown by
+;; magit doesn't agree with the diff shown by git (and git is always
+;; in the right when this happens).
+(setq magit-commit-show-diff nil)
+
+(defun git-commit-message-pretty-ref (commit)
+  "Generate a reference to git COMMIT in the format used in the
+Linux kernel (`SHORT-HASH (\"SHORT-MESSAGE\")') and insert it at
+point."
+  (interactive
+   (list (or (nreverse (magit-region-values 'commit))
+	     (magit-read-other-branch-or-commit "Reference commit"))))
+  (call-process (magit-git-executable) nil t nil
+		"show" "--no-patch" "--pretty=format:%h (\"%s\")" commit))
+
+(with-eval-after-load "magit"
+  (define-key magit-process-mode-map (kbd "k") #'magit-process-kill)
+
+  ;; Add a --no-gpg-sign option to Magit.  This is only useful with
+  ;; commit.gpgsign=true.  It would be nice if commit.gpgsign=true just
+  ;; meant that Magit showed the --gpg-sign option as enabled by
+  ;; default, and disabling it would make Magit pass --no-gpg-sign to
+  ;; git, but that's not currently the case, so we need a seperate
+  ;; option for --no-gpg-sign.  See
+  ;; <https://github.com/magit/magit/issues/3832>.
+  (dolist (command '(magit-commit magit-merge magit-cherry-pick
+		     magit-revert magit-am magit-rebase))
+    (transient-append-suffix command ["-S"]
+      '("=S" "Don't sign using gpg" "--no-gpg-sign"))))
+
+;;; Markdown ;;;
+(setq-default markdown-hide-markup t)
+
+;;; Man ;;;
+;; Open man pages in the current window.  I don't really care what
+;; happens when I do M-x man, but I don't want my whole frame to be
+;; taken over with man buffers if I'm following a bunch of
+;; cross-references.
+(setq Man-notify-method 'pushy)
+
+;;; MML ;;;
+(setq mml-secure-openpgp-encrypt-to-self t)
+(setq mml-secure-openpgp-signers '("757356D779BBB888773E415E736CCDF9EF51BD97"))
+(add-hook 'message-setup-hook 'mml-secure-message-sign-pgpmime)
+
+;;; Nix support ;;;
+(defun browse-url-nixpkgs (attr)
+  "Open a browser to the homepage for the Nixpkgs attribute ATTR."
+  (interactive "sAttribute: ")
+  (let* ((full-attr-quoted (shell-quote-argument (concat attr ".meta.homepage")))
+	 (command (concat "nix --extra-experimental-features nix-command eval --raw -f '<nixpkgs>' " full-attr-quoted))
+	 (homepage (shell-command-to-string command)))
+    (browse-url homepage)))
+
+;;; notmuch ;;;
+(setq notmuch-search-oldest-first nil)
+(setq notmuch-fcc-dirs "Sent")
+(setq notmuch-draft-folder "Drafts")
+(setq notmuch-saved-searches
+      `((:name "direct" :query ,(concat "-tag:done -folder:Spam -from:discourse@discourse.nixos.org to:" (getenv "EMAIL")) :key "d")
+	(:name "github" :query "-tag:done -to:your_activity@noreply.github.com from:notifications@github.com" :key "g")
+	(:name "lists" :query "-tag:done to:afra@afra-berlin.de OR to:@list.skarnet.org OR wayland-devel@lists.freedesktop.org OR to:distributions@lists.linux.dev OR to:virglrenderer-devel@lists.freedesktop.org OR to:config-patches@gnu.org OR to:linux-kernel-announce@vger.kernel.org" :key "l")))
+(setq notmuch-tagging-keys '(("d" ("+done") "done")))
+
+;;; mail ;;;
+; This has to go after we set the notmuch settings, for some reason,
+; or they are not applied.
+(require 'notmuch-mua)
+(setq mail-user-agent 'notmuch-user-agent)
+
+;;; Org-mode ;;;
+(setq org-id-locations-file (concat user-emacs-directory "/org-id-locations"))
+
+;;; Org-roam ;;;
+(setq org-roam-directory "~/notes")
+(setq org-roam-v2-ack t)
+(org-roam-db-autosync-mode)
+(global-set-key (kbd "C-c o c") #'org-roam-capture)
+(global-set-key (kbd "C-c o f") #'org-roam-node-find)
+(define-key org-mode-map (kbd "C-c o i") #'org-roam-node-insert)
+(define-key org-mode-map (kbd "C-c o r") #'org-roam-ref-add)
+
+;;; Rainbow Delimiters ;;;
+(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
+
+;;; Revert buffers ;;;
+(global-set-key (kbd "C-c r") #'revert-buffer)
+
+;;; Ruby ;;;
+(setq ruby-align-to-stmt-keywords t)
+
+;;; Rust ;;;
+;; Indent with spaces in Rust code.
+(add-hook 'rust-mode-hook (lambda () (setq indent-tabs-mode nil)))
+
+(with-eval-after-load 'rust-mode
+  ;; Cargo keybindings
+  (define-key rust-mode-map (kbd "C-c c b") 'rust-compile)
+  (define-key rust-mode-map (kbd "C-c c c") 'rust-check)
+  (define-key rust-mode-map (kbd "C-c c r") 'rust-run)
+  (define-key rust-mode-map (kbd "C-c c t") 'rust-test))
+
+;;; save-some-buffers ;;;
+;; Allow reverting buffers directly from the "Save file ...?" message.
+(add-to-list 'save-some-buffers-action-alist
+  `(?r ,(lambda (buf) (with-current-buffer buf (revert-buffer t t)))
+       ,(purecopy "revert this buffer")))
+
+;;; sendmail ;;;
+;; Use the system "sendmail" program to send mail.
+(setq mail-envelope-from 'header)
+(setq mail-specify-envelope-from t)
+(setq send-mail-function 'sendmail-send-it)
+
+;;; Term ;;;
+(add-hook 'term-mode-hook #'enable-pager)
+
+;;; Transient ;;;
+;; Make all Magit options available, even those that are disabled by
+;; default because they're too obscure.
+(setq transient-default-level 7)
+
+;;; with-editor ;;;
+(add-hook 'eshell-mode-hook 'with-editor-export-editor)
+(add-hook 'shell-mode-hook 'with-editor-export-editor)
+(add-hook 'term-exec-hook 'with-editor-export-editor)