diff options
Diffstat (limited to 'modules/workstation/emacs')
-rw-r--r-- | modules/workstation/emacs/default.nix | 82 | ||||
-rw-r--r-- | modules/workstation/emacs/early-init.el | 4 | ||||
-rw-r--r-- | modules/workstation/emacs/init.el | 284 |
3 files changed, 370 insertions, 0 deletions
diff --git a/modules/workstation/emacs/default.nix b/modules/workstation/emacs/default.nix new file mode 100644 index 000000000000..fa26e98ce64e --- /dev/null +++ b/modules/workstation/emacs/default.nix @@ -0,0 +1,82 @@ +{ config, pkgs, ... }: + +let + emacs = ((with pkgs; emacsPackagesFor (emacs29-pgtk.overrideAttrs ( + { patches ? [], ... }: + { + patches = patches ++ [ + (fetchpatch { + url = "https://git.savannah.gnu.org/cgit/emacs.git/patch/?id=e4e89e2cb663c730fd563d89228fe3a9a34e63e5"; + hash = "sha256-/7vWMFXjmmmTg6nNvEPOM3LkZ+j3IVV9W127anx5deI="; + }) + ]; + } + ))).emacsWithPackages (epkgs: with epkgs; [ + adoc-mode + cmake-mode + code-review + csharp-mode + d-mode + direnv + dockerfile-mode + dts-mode + ebuild-mode + editorconfig + eglot + elpher + execline + forge + gn-mode + gnuplot-mode + go-mode + graphql-mode + graphviz-dot-mode + groovy-mode + haskell-mode + jam-mode + just-mode + kotlin-mode + lua-mode + magit + markdown-mode + meson-mode + monokai-theme + mutt-mode + ninja-mode + nix-mode + notmuch + org-roam + pass + pod-mode + protobuf-mode + rainbow-delimiters + rust-mode + sdlang-mode + sort-words + toml-mode + tuareg + typescript-mode + udev-mode + vala-mode + yaml-mode + zig-mode + ])); +in + +{ + environment.systemPackages = [ emacs ]; + + systemd.tmpfiles.rules = [ + "d ${config.users.users.qyliss.home}/notes 0700 qyliss qyliss" + ]; + + users.users.qyliss.xdg.config.paths."emacs" = pkgs.runCommand ".emacs.d" { + nativeBuildInputs = [ emacs ]; + } '' + cp ${./early-init.el} early-init.el + cp ${./init.el} init.el + emacs -L . --batch -f batch-byte-compile *.el + install -d $out + install *.el *.elc $out + ''; +} diff --git a/modules/workstation/emacs/early-init.el b/modules/workstation/emacs/early-init.el new file mode 100644 index 000000000000..8cac0647dd4e --- /dev/null +++ b/modules/workstation/emacs/early-init.el @@ -0,0 +1,4 @@ +;; This defaults to XDG_CONFIG_HOME, which is bad because lots of +;; state gets written to it. +(setq user-emacs-directory + (concat (file-name-as-directory (getenv "XDG_DATA_HOME")) "emacs")) diff --git a/modules/workstation/emacs/init.el b/modules/workstation/emacs/init.el new file mode 100644 index 000000000000..8370fe153fd7 --- /dev/null +++ b/modules/workstation/emacs/init.el @@ -0,0 +1,284 @@ +;; -*- 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 '("/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) |