about summary refs log tree commit diff
path: root/modules/workstation/emacs/init.el
blob: 11da5bc9fc959881a3c09b0db03c5abb142fa617 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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)