;; Hey, Emacs: -*- Indent-tabs-mode: nil -*- ;; This config requires Emacs 27. ;; With some differences in package.el initialization it should work with Emacs 24.4(+?). ;; Without (advice-add) it should work in older versions of Emacs 24. ;;;; Use XDG-ish locations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let ((xdg-cache-home (file-name-as-directory (or (getenv "XDG_CACHE_HOME") "~/.cache"))) (xdg-data-home (file-name-as-directory (or (getenv "XDG_DATA_HOME") "~/.local/share")))) (setq wl-init-file (concat user-emacs-directory "wl.el") eshell-directory-name (concat xdg-data-home "emacs/eshell/") ;; actually should be split between config and data ido-save-directory-list-file (concat xdg-data-home "emacs/ido.last.el") package-user-dir (concat xdg-cache-home "emacs/elpa") el-get-dir (concat xdg-cache-home "emacs/el-get/") wl-score-files-directory (concat xdg-cache-home "emacs/wl-score-files/") elmo-msgdb-directory (concat xdg-cache-home "emacs/elmo-msgdb/") elmo-cache-directory (concat xdg-cache-home "emacs/elmo-cache/") auto-save-list-file-prefix (concat xdg-cache-home "emacs/auto-save-list/saves-") tramp-persistency-file-name (concat xdg-cache-home "emacs/tramp-cache.el") )) (setq custom-file (concat user-emacs-directory "custom.el")) (load custom-file 'noerror) ;;;; The basics that I can't use Emacs without ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (show-paren-mode 1) (column-number-mode 1) (line-number-mode 1) (ido-mode 1) (unless (daemonp) (server-mode 1)) (when (require 'whitespace) (setq whitespace-style '( tab-mark space-mark newline-mark empty )) (global-set-key "\C-cw" 'global-whitespace-mode)) (setq minibuffer-prompt-properties '( read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt )) ;;;; Early settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; This isn't particularly important, but set it before doing a whole ;; lot (loading packages), so there isn't a weird change in text size. (set-face-attribute 'default nil :family "DejaVu Sans Mono" :height 80 ;:weight 'semi-bold ) (when (fboundp 'tool-bar-mode) (tool-bar-mode -1)) (when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1)) (setq inhibit-startup-screen t) ;;;; Package management ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; package.el (when (require 'package) ;; Don't clutter custom.el with `package-selected-packages`; I want ;; to exclusively use use-package for this. (defun package--save-selected-packages--nocustom (&optional value) "Set `package-selected-packages' to VALUE." (when value (setq package-selected-packages value))) (advice-add 'package--save-selected-packages :override #'package--save-selected-packages--nocustom) ;; Add Melpa repos. (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) ;; Always prefer versions from GNU > Melpa-Stable > Melpa, even if the ;; later have higher version numbers. (setq package-archive-priorities '(("gnu" . 99) ("melpa-stable" . 5) ("melpa" . 0))) ;; Finally, initialize the package manager. Pass the `t` argument ;; to cause it to not go ahead and load all downloaded packages; and ;; wait for (use-package) to request them. (package-initialize t)) ;; use-package.el (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (package-activate 'use-package) (eval-when-compile (require 'use-package) ;; Have (use-package) calls eagerly install things. (setq use-package-always-ensure t) ;; Patch (use-package-ensure-elpa) to call (package-activate ;; package) if the package is already installed, which is needed to ;; make it work with the the `t` argument in ;; `(package-initialize t)`. ;; ;; Apparently back in 2015 hacking (use-package-ensure-lazyelpa) ;; wasn't necessary (https://emacs.stackexchange.com/a/16852), and ;; I'm not sure what changed. (defun use-package-ensure-lazyelpa (name args _state &optional _no-refresh) (dolist (ensure args) (let ((package (or (and (eq ensure t) (use-package-as-symbol name)) ensure))) (when package (require 'package) (when (consp package) (use-package-pin-package (car package) (cdr package)) (setq package (car package))) (if (package-installed-p package) (package-activate package) (condition-case-unless-debug err (progn (when (assoc package (bound-and-true-p package-pinned-packages)) (package-read-all-archive-contents)) (unless (assoc package package-archive-contents) (package-refresh-contents) (when (assoc package (bound-and-true-p package-pinned-packages)) (package-read-all-archive-contents)) (package-install package)) (package-install package) t) (error (display-warning 'use-package (format "Failed to install %s: %s" name (error-message-string err)) :error)))))))) (setq use-package-ensure-function 'use-package-ensure-lazyelpa)) ;; Load bind-key so that (use-package :bind) works. (require 'bind-key) ;; Themes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (use-package modus-themes :config (progn ;; Modus makes MMM regions almost impossible to see (add-hook 'modus-themes-after-load-theme-hook (lambda () (modus-themes-with-colors (custom-set-faces `(mmm-cleanup-submode-face ((,class :background ,yellow-refine-bg))) `(mmm-code-submode-face ((,class :background ,bg-active))) `(mmm-comment-submode-face ((,class :background ,blue-refine-bg))) `(mmm-declaration-submode-face ((,class :background ,cyan-refine-bg))) `(mmm-default-submode-face ((,class :background ,bg-alt))) `(mmm-init-submode-face ((,class :background ,magenta-refine-bg))) `(mmm-output-submode-face ((,class :background ,red-refine-bg))) `(mmm-special-submode-face ((,class :background ,green-refine-bg))))) )) ;; Load the theme. Use this function instead of ;; load-theme in order to get the above hook. (modus-themes-load-vivendi) )) ;; Minor modes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (use-package editorconfig :config (progn (editorconfig-mode 1) (add-to-list 'editorconfig-indentation-alist '(protobuf-mode c-basic-offset)) (add-to-list 'editorconfig-indentation-alist '(terraform-mode terra-indent-level terraform-indent-level hcl-indent-level)) )) ;; (use-package eglot) (use-package jq-format :commands (jq-format-json-buffer jq-format-json-region jq-format-json-on-save-mode jq-format-jsonlines-buffer jq-format-jsonlines-region jq-format-jsonlines-on-save-mode)) ;; (use-package company) ;; (use-package flycheck) ;; (use-package yasnippet) ;; (use-package lsp-mode :commands lsp) ;; (use-package lsp-ui :commands lsp-ui-mode) (use-package multiple-cursors :bind (("C-c m l" . mc/edit-lines) ("C-c m n" . mc/insert-numbers))) (use-package outline-magic :commands (outline-cycle outline-next-line outline-move-subtree-up outline-move-subtree-down outline-promote outline-demote)) (use-package page-break-lines ;; Display form-feeds pretty :init (advice-add 'page-break-lines-mode-maybe :override #'page-break-lines-mode) :config (global-page-break-lines-mode 1)) (use-package smart-tabs-mode ;; Indent with tabs, align with spaces :config (progn (smart-tabs-mode 1) (apply 'smart-tabs-insinuate (mapcar 'car smart-tabs-insinuate-alist)))) ;; Major modes (non-HTML-related) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (use-package bats-mode :mode "\\.bats\\'") (use-package bazel :mode (((rx "/" (or "BUILD" "BUILD.bazel") eos) . bazel-build-mode) ((rx "/" (or "WORKSPACE" "WORKSPACE.bazel") eos) . bazel-workspace-mode) ((rx "/" (+ nonl) ".bzl" eos) . bazel-starlark-mode) ((rx "/" (or "bazel.bazelrc" ".bazelrc") eos) . bazelrc-mode) ((rx "/.bazelignore" eos) . bazelignore-mode))) (use-package bison-mode :mode (("\\.l\\'" . bison-mode) ("\\.y\\'" . bison-mode) ("\\.jison\\'" . jison-mode))) (use-package cmake-mode :mode ("CMakeLists\\.txt\\'" "\\.cmake\\'")) (use-package dockerfile-mode :mode "Dockerfile\\(?:\\..*\\)?\\'" :commands (dockerfile-build-buffer dockerfile-build-no-cache-buffer)) (use-package erc :config (add-hook 'erc-mode-hook (lambda () (define-key erc-mode-map (kbd "C-c C-u") 'erc-cmd-QUERY) ))) (use-package glsl-mode :mode ("\\.vert\\'" "\\.frag\\'" "\\.geom\\'" "\\.glsl\\'")) (use-package go-mode :mode ("\\.go\\'" "go\\.mod") :config (progn (when (fboundp 'eglot-ensure) (add-hook 'go-mode-hook (lambda () (set (make-local-variable 'eglot-workspace-configuration) '(:gopls (:codelenses (:generate nil :gc_details t) :annotations (:bounds t :escape t :inline t :nil t)))) (eglot-ensure)))) (when (fboundp 'lsp) (add-hook 'go-mode-hook 'lsp-deferred)))) (use-package graphql-mode :mode ("\\.graphql\\'" "\\.gql\\'")) (use-package graphviz-dot-mode :mode ("\\.dot\\'" "\\.gv\\'") :config (add-hook 'graphviz-dot-mode-hook (lambda () (set (make-local-variable 'graphviz-dot-auto-indent-on-semi) nil) ))) (use-package jq-mode :mode "\\.jq\\'" :interpreter "jq") (use-package js :interpreter (("node" . js-mode) ("nodejs" . js-mode) ("gjs" . js-mode) ("rhino" . js-mode)) :config (add-hook 'js-mode-hook (lambda () (hs-minor-mode t) (local-set-key [C-tab] 'hs-toggle-hiding) ))) (use-package lua-mode :mode "\\.lua\\'" :interpreter "lua") (use-package markdown-mode :mode ("\\.markdown\\'" "\\.md\\'" "\\.ronn\\'")) (use-package meson-mode :mode "/meson\\(\\.build\\|_options\\.txt\\)\\'") (use-package nginx-mode :mode ("nginx\\.conf\\'" "/nginx/.+\\.conf\\'")) (use-package protobuf-mode :mode "\\.proto\\'") (use-package python :mode ("\\.py[iw]?\\'" . python-mode) :interpreter ("python[0-9.]*" . python-mode) :config (add-hook 'python-mode-hook (lambda () (hs-minor-mode t) (local-set-key [C-tab] 'hs-toggle-hiding) ))) (use-package ruby-mode :mode "\\(?:\\.\\(?:rbw?\\|ru\\|rake\\|thor\\|jbuilder\\|rabl\\|gemspec\\|podspec\\)\\|/\\(?:Gem\\|Rake\\|Cap\\|Thor\\|Puppet\\|Berks\\|Brew\\|Vagrant\\|Guard\\|Pod\\)file\\)\\'" :interpreter ("ruby" "rbx" "jruby" "ruby1.9" "ruby1.8") :config (add-hook 'ruby-mode-hook (lambda () (set (make-local-variable 'indent-tabs-mode) t) (set (make-local-variable 'ruby-indent-level) 4) (set (make-local-variable 'tab-width) 4) ))) (use-package terraform-mode :mode "\\.tf\\(vars\\)?\\'") (use-package yaml-mode :mode "\\.\\(e?ya?\\|ra\\)ml\\'" :config (add-hook 'yaml-mode-hook (lambda () (outline-minor-mode t) (set (make-local-variable 'outline-regexp) "[ \t]*[A-Za-z-]") ))) ;; Major modes (HTML-related) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (use-package coffee-mode :mode ("\\.coffee\\'" "\\.iced\\'" "\\Cakefile\\'" "\\.cson\\'") :interpreter "coffee" :config (add-hook 'coffee-mode-hook (lambda () (set (make-local-variable 'tab-width) 2) (set (make-local-variable 'indent-tabs-mode) nil) ))) (use-package haml-mode :mode "\\.haml\\'") (use-package less-css-mode :mode "\\.less\\'") (use-package php-mode :mode ("\\.php[s345t]?\\'" "/\\.php_cs\\(\\.dist\\)?\\'" "\\.phtml\\'" "/Amkfile\\'" "\\.amk\\'") :interpreter "php\\(?:-?[3457]\\(?:\\.[0-9]+\\)*\\)?") (use-package scss-mode :mode "\\.scss\\'") (use-package typescript-mode :mode "\\.tsx?\\'") (use-package web-mode :mode ("\\.html\\'" "\\.vue\\'") :config (add-hook 'web-mode-hook (lambda () (setq web-mode-enable-auto-closing nil) (setq web-mode-enable-auto-pairing nil) (setq web-mode-enable-auto-opening nil) (setq web-mode-enable-auto-quoting nil) (setq web-mode-enable-auto-expanding nil) (setq web-mode-enable-auto-indentation nil) ))) (use-package mmm-mode :load mmm-auto :config (progn ;; Define the class (mmm-add-classes '((js-graphql :submode graphql-mode :face mmm-code-submode-face :front "[^a-zA-Z]graphql`" :back "`"))) ;; Add too mmm-mode-ext-classes-alist (mmm-add-mode-ext-class 'js-mode nil 'js-graphql) (setq ;; Enable MMM for modes/files named in ;; mmm-mode-ext-classes-alist, and only modes/files named ;; in mmm-mode-ext-classes-alist. mmm-global-mode 'maybe ;; 0 = none, 1 = low, 2 = high mmm-submode-decoration-level 2) )) ;; Email ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (if (file-exists-p "~/Maildir") (use-package wanderlust :commands (wl) :config (progn (define-mail-user-agent 'wl-user-agent 'wl-user-agent-compose 'wl-draft-send 'wl-draft-kill 'mail-send-hook) (setq mail-user-agent 'wl-user-agent) ;; Use mailcrypt to encrypt/decrypt email (when (require 'mailcrypt nil t) (mc-setversion "gpg") (add-hook 'wl-summary-mode-hook 'mc-install-read-mode) (add-hook 'wl-mail-setup-hook 'mc-install-write-mode) (defun mc-wl-verify-signature () (interactive) (save-window-excursion (wl-summary-jump-to-current-message) (mc-verify))) (defun mc-wl-decrypt-message () (interactive) (save-window-excursion (wl-summary-jump-to-current-message) (let ((inhibit-read-only t)) (mc-decrypt)))) (eval-after-load "mailcrypt" '(setq mc-modes-alist (append (quote ((wl-draft-mode (encrypt . mc-encrypt-message) (sign . mc-sign-message)) (wl-summary-mode (decrypt . mc-wl-decrypt-message) (verify . mc-wl-verify-signature)))) mc-modes-alist)))) ))) ;; Non-built-in modes that aren't available with (use-package) ;;;;;;;;;;;;;;;;; (eval-when-compile (add-to-list 'load-path (concat user-emacs-directory "go-template-mode")) (require 'go-template-mode)) ;; http://www.emacswiki.org/emacs/XModMapMode (when (not (fboundp 'xmodmap-mode)) (define-generic-mode 'xmodmap-mode '(?!) '("add" "clear" "keycode" "keysym" "pointer" "remove") nil '("[xX]modmap\\(rc\\)?\\'") nil "Simple mode for xmodmap files.")) ;; Config for built-in modes that are not exposed as ELPA :core packages ;;;;;;; (add-hook 'text-mode-hook 'turn-on-auto-fill) (add-hook 'lisp-mode-hook (lambda () (set (make-local-variable 'indent-tabs-mode) nil) )) (add-hook 'emacs-lisp-mode-hook (lambda () (set (make-local-variable 'indent-tabs-mode) nil) )) (eval-after-load "term" '(progn ;; Better buffer names ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Ideally, figuring this out should be done by uniquify, but I ;; haven't determined how to get uniquify to think that it manages the ;; term buffer. (defun term-get-short-cwd () ;; local base=$PWD ;; local suffix='' ;; # The regex here is a list of directory names ;; # that aren't really helpful, and that the ;; # parent directory should be included also. ;; if [[ $base =~ (/(src|pkg|doc|pkg-libre|src-libre|trunk|tags|branches))*$ ]]; then ;; suffix=$BASH_REMATCH ;; base=${base%$suffix} ;; fi ;; base=${base##*/} ;; echo ${base}${suffix} (directory-file-name default-directory)) (defun term-handle-ansi-terminal-messages--uniquify (args) (rename-buffer (concat (replace-regexp-in-string "<.*>$" "" (buffer-name)) "<" (term-get-short-cwd) ">") t)) (advice-add 'term-handle-ansi-terminal-messages :after #'term-handle-ansi-terminal-messages--uniquify) ;; Mode settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (add-hook 'term-mode-hook (lambda () (auto-fill-mode -1) (setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *") (set (make-local-variable 'mouse-yank-at-point) t) (setq tab-width 8 ) (setq truncate-lines nil) (set (make-local-variable 'autopair-dont-activate) t) ;; Don't let autopair break ansi-term )))) (add-hook 'tex-mode-hook (lambda () (set (make-local-variable 'tab-always-indent) nil) (set (make-local-variable 'indent-tabs-mode) t) )) (eval-after-load "sh-script" '(progn ;; Fix indentation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun sh-smie-sh-rules--fix (args) "Replace :after \"then\" with :after \"if\" because Emacs 24 sh-script.el is broken." (if (equal args (list :after "then")) (list :after "if") args)) (advice-add 'sh-smie-sh-rules :filter-args #'sh-smie-sh-rules--fix) ;; Mode settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (add-hook 'sh-mode-hook (lambda () (sh-electric-here-document-mode 0) )))) (add-hook 'outline-minor-mode-hook (lambda () (define-key outline-minor-mode-map [C-tab] 'outline-cycle))) (add-to-list 'auto-mode-alist '("PKGBUILD" . sh-mode)) (add-to-list 'auto-mode-alist '("SRCBUILD" . sh-mode)) (add-to-list 'auto-mode-alist '("\\.mak\\'" . makefile-gmake-mode)) (add-to-list 'auto-mode-alist '("\\.jad\\'" . java-mode)) (add-to-list 'auto-mode-alist '("\\.schema\\'" . js-mode)) ;; Misc. crap ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (setq-default truncate-lines t) (defun align-regexp--use-spaces (orig-fun &rest args) "Use spaces for alignment" (let ((indent-tabs-mode nil)) (apply orig-fun args))) (advice-add 'align-regexp :around #'align-regexp--use-spaces) ;; Make the mouse work in an xterm (when (fboundp 'xterm-mouse-mode) (xterm-mouse-mode 1) (add-hook 'after-make-frame-functions (lambda (frame) (if xterm-mouse-mode (xterm-mouse-mode 1)) ))) ;; Make TRAMP obey ~/.ssh/config for ControlMaster. For some reason, ;; customize doesn't correctly set this. (setq tramp-use-ssh-controlmaster-options nil) ;; Indent settings (setq-default indent-tabs-mode t tab-width 8 c-basic-offset 8 sh-basic-offset 8 sh-indent-comment t ) ;; Backup settings (setq backup-by-copying t ;; don't clobber symlinks backup-directory-alist '(("." . "~/.cache/emacs/saves")) ;; don't litter my fs tree delete-old-versions t kept-new-versions 6 kept-old-versions 2 version-control t ;; use versioned backups create-lockfiles nil ;; don't litter my fs tree ) ;; Web browser settings (setq browse-url-generic-program (executable-find "v-www-browser") browse-url-browser-function 'browse-url-generic ) ;; Ediff settings (setq ediff-window-setup-function 'ediff-setup-windows-plain ediff-split-window-function 'split-window-horizontally ) ;; Automatically load smerge mode for merge files (defun try-smerge-mode () (save-excursion (goto-char (point-min)) (when (re-search-forward "^<<<<<<< " nil t) (smerge-mode 1)))) (add-hook 'find-file-hook 'try-smerge-mode t) ;; Interactive commands (defun hex2dec-region (beg end) "Convert the region from hexadecimal to decimal." (interactive "r") (let ((hex-str (buffer-substring beg end))) (delete-region beg end) (insert (number-to-string (string-to-number hex-str 16))))) ;; Anything that gets magically appended ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil)