;;; init.el --- bandali's emacs configuration -*- lexical-binding: t -*- ;; Copyright (c) 2018-2025 Amin Bandali ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; bandali's opinionated GNU Emacs configs. I tend to use the latest ;; development trunk of emacs.git, but I try to maintain backward ;; compatibility with a few of the recent older GNU Emacs releases ;; so I could easily reuse it on machines stuck with older Emacsen. ;;; Code: (setq use-package-verbose init-file-debug use-package-expand-minimally (not init-file-debug) use-package-compute-statistics init-file-debug debug-on-error init-file-debug debug-on-quit init-file-debug) (require 'package) (when (< emacs-major-version 29) (unless (package-installed-p 'use-package) (unless package-archive-contents (package-refresh-contents)) (package-install 'use-package))) ;; whoami (setq user-full-name "Amin Bandali" user-mail-address "bandali@kelar.org") ;;; Initial setup (eval-and-compile (defsubst b/emacs.d (path) "Expand path PATH relative to `user-emacs-directory'." (expand-file-name (convert-standard-filename path) user-emacs-directory)) ;; Wrappers around the new keybinding functions, with fallback to ;; the corresponding older lower level function on older Emacsen. (defsubst b/keymap-set (keymap key definition) (if (version< emacs-version "29") (define-key keymap (kbd key) definition) (keymap-set keymap key definition))) (defsubst b/keymap-global-set (key command) (if (version< emacs-version "29") (global-set-key (kbd key) command) (keymap-global-set key command))) (defsubst b/keymap-local-set (key command) (if (version< emacs-version "29") (local-set-key (kbd key) command) (keymap-local-set key command))) (defsubst b/keymap-global-unset (key) (if (version< emacs-version "29") (global-unset-key (kbd key)) (keymap-global-unset key 'remove))) (defsubst b/keymap-local-unset (key) (if (version< emacs-version "29") (local-unset-key (kbd key)) (keymap-local-unset key 'remove))) (when (version< emacs-version "29") ;; Emacs 29 introduced the handy `setopt' macro for setting user ;; options (defined with `defcustom') with a syntax similar to ;; `setq'. So, we define it on older Emacsen that don't have it. (defmacro setopt (&rest pairs) "Set VARIABLE/VALUE pairs, and return the final VALUE. This is like `setq', but is meant for user options instead of plain variables. This means that `setopt' will execute any `custom-set' form associated with VARIABLE. \(fn [VARIABLE VALUE]...)" (declare (debug setq)) (unless (zerop (mod (length pairs) 2)) (error "PAIRS must have an even number of variable/value members")) (let ((expr nil)) (while pairs (unless (symbolp (car pairs)) (error "Attempting to set a non-symbol: %s" (car pairs))) (push `(setopt--set ',(car pairs) ,(cadr pairs)) expr) (setq pairs (cddr pairs))) (macroexp-progn (nreverse expr)))) (defun setopt--set (variable value) (custom-load-symbol variable) ;; Check that the type is correct. (when-let ((type (get variable 'custom-type))) (unless (widget-apply (widget-convert type) :match value) (warn "Value `%S' does not match type %s" value type))) (put variable 'custom-check-value (list value)) (funcall (or (get variable 'custom-set) #'set-default) variable value)))) ;; Separate custom file (don't want it mixing with init.el). (setopt custom-file (b/emacs.d "custom.el")) (with-eval-after-load 'custom (load custom-file 'noerror)) ;; Start Emacs server ;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html (run-with-idle-timer 0.5 nil #'require 'server) (with-eval-after-load 'server (declare-function server-edit "server") (b/keymap-global-set "C-c F D" #'server-edit) (declare-function server-running-p "server") (or (server-running-p) (server-mode))) ;;; Essential packages (add-to-list 'load-path (b/emacs.d "lisp")) (require 'bandali-essentials) ;; (require 'bandali-exwm) (require 'bandali-eshell) (require 'bandali-ibuffer) (require 'bandali-dired) ;;; Email with Gnus and message (require 'bandali-gnus) (require 'bandali-message) ;; (with-eval-after-load 'sendmail ;; (setopt mail-header-separator "")) ;; (with-eval-after-load 'smtpmail ;; (setopt smtpmail-queue-mail t ;; smtpmail-queue-dir (concat b/maildir "queue/"))) ;;; IRC with ERC (require 'bandali-erc) ;;; Programming modes (use-package elisp-mode :bind ("C-c e e" . eval-last-sexp)) (use-package pp :bind ("C-c e m" . pp-macroexpand-last-sexp)) (with-eval-after-load 'lisp-mode (add-hook 'lisp-interaction-mode-hook (lambda () (setq indent-tabs-mode nil)))) ;; (add-to-list 'load-path (b/lisp "alloy-mode")) ;; (autoload 'alloy-mode "alloy-mode" nil t) ;; (with-eval-after-load 'alloy-mode ;; (setq alloy-basic-offset 2) ;; ;; (defun b/alloy-simple-indent (start end) ;; ;; (interactive "r") ;; ;; ;; (if (region-active-p) ;; ;; ;; (indent-rigidly start end alloy-basic-offset) ;; ;; ;; (if (bolp) ;; ;; ;; (indent-rigidly (line-beginning-position) ;; ;; ;; (line-end-position) ;; ;; ;; alloy-basic-offset))) ;; ;; (indent-to (+ (current-column) alloy-basic-offset))) ;; (define-key alloy-mode-map (kbd "RET") #'electric-newline-and-maybe-indent) ;; ;; (define-key alloy-mode-map (kbd "TAB") #'b/alloy-simple-indent) ;; (define-key alloy-mode-map (kbd "TAB") #'indent-for-tab-command)) ;; (add-to-list 'auto-mode-alist '("\\.\\(als\\|dsh\\)\\'" . alloy-mode)) ;; (add-hook 'alloy-mode-hook (lambda nil (setq-local indent-tabs-mode nil))) ;; (eval-when-compile (defvar lean-mode-map)) ;; (run-with-idle-timer 0.4 nil #'require 'lean-mode) ;; (with-eval-after-load 'lean-mode ;; (require 'lean-input) ;; (setq default-input-method "Lean" ;; lean-input-tweak-all '(lean-input-compose ;; (lean-input-prepend "/") ;; (lean-input-nonempty)) ;; lean-input-user-translations '(("/" "/"))) ;; (lean-input-setup) ;; ;; local key bindings ;; (define-key lean-mode-map (kbd "S-SPC") #'company-complete)) (with-eval-after-load 'sgml-mode (setopt sgml-basic-offset 0)) (with-eval-after-load 'css-mode (setopt css-indent-offset 2)) (add-hook 'tex-mode-hook #'auto-fill-mode) (add-hook 'tex-mode-hook #'flyspell-mode) (autoload 'cmake-mode "cmake-mode" nil t) (add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode)) (add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode)) (with-eval-after-load 'cmake-mode (add-to-list 'load-path (b/emacs.d "lisp/cmake-font-lock")) (require 'cmake-font-lock)) ;;; Emacs enhancements & auxiliary packages ;; `debbugs' (b/keymap-global-set "C-c D d" #'debbugs-gnu) (b/keymap-global-set "C-c D b" #'debbugs-gnu-bugs) (b/keymap-global-set "C-c D e" ; bug-gnu-emacs (lambda () (interactive) (setq debbugs-gnu-current-suppress t) (debbugs-gnu debbugs-gnu-default-severities '("emacs")))) (b/keymap-global-set "C-c D g" ; bug-gnuzilla (lambda () (interactive) (setq debbugs-gnu-current-suppress t) (debbugs-gnu debbugs-gnu-default-severities '("gnuzilla")))) (with-eval-after-load 'eww (setopt eww-download-directory (file-name-as-directory (getenv "XDG_DOWNLOAD_DIR")))) (b/keymap-global-set "C-c e w" #'eww) (run-with-idle-timer 0.2 nil #'require 'display-fill-column-indicator nil 'noerror) (with-eval-after-load 'display-fill-column-indicator (global-display-fill-column-indicator-mode 1)) (with-eval-after-load 'window (setopt split-width-threshold 140)) (add-hook 'latex-mode-hook #'reftex-mode) (when (and (featurep 'completion-preview) (functionp #'completion-preview-mode)) (b/keymap-set completion-preview-active-mode-map "M-n" #'completion-preview-next-candidate) (b/keymap-set completion-preview-active-mode-map "M-p" #'completion-preview-prev-candidate) (b/keymap-set completion-preview-active-mode-map "M-i" #'completion-preview-insert) (add-hook 'prog-mode-hook #'completion-preview-mode) (add-hook 'text-mode-hook #'completion-preview-mode) (with-eval-after-load 'comint (add-hook 'comint-mode-hook #'completion-preview-mode))) (run-with-idle-timer 0.5 nil #'require 'delight) (with-eval-after-load 'delight (delight 'auto-fill-function " f" "simple") (delight 'abbrev-mode "" "abbrev") (delight 'mml-mode " mml" "mml")) (require 'bandali-po) (add-to-list 'load-path (b/emacs.d "lisp/ffs")) (run-with-idle-timer 0.5 nil #'require 'ffs) (with-eval-after-load 'ffs (setopt ffs-default-face-height 250) (global-set-key (kbd "C-c f s") #'ffs)) (add-hook 'ffs-start-hook (lambda () (mapc (lambda (mode) (funcall mode 1)) ; enable '(ffs--no-mode-line-minor-mode ffs--no-cursor-minor-mode)) (mapc (lambda (mode) (funcall mode -1)) ; disable '(show-paren-local-mode display-battery-mode display-fill-column-indicator-mode flyspell-mode tool-bar-mode menu-bar-mode scroll-bar-mode)) (fringe-mode 0))) (add-hook 'ffs-quit-hook (lambda () (mapc (lambda (mode) (funcall mode -1)) ; disable '(ffs--no-mode-line-minor-mode ffs--no-cursor-minor-mode)) (mapc (lambda (mode) (funcall mode 1)) ; enable '(show-paren-local-mode display-battery-mode display-fill-column-indicator-mode flyspell-mode tool-bar-mode menu-bar-mode scroll-bar-mode)) (fringe-mode nil))) (add-to-list 'load-path (b/emacs.d "lisp/debian-el")) (run-with-idle-timer 0.5 nil #'require 'debian-el) (with-eval-after-load 'debian-el (require 'apt-sources) (require 'apt-utils) (require 'debian-bug) (require 'deb-view) (require 'gnus-BTS) (require 'preseed)) (add-to-list 'load-path (b/emacs.d "lisp/dpkg-dev-el")) (run-with-idle-timer 0.5 nil #'require 'dpkg-dev-el) (with-eval-after-load 'dpkg-dev-el (require 'debian-changelog-mode) (require 'debian-bts-control) (require 'debian-changelog-mode) (require 'debian-control-mode) (require 'debian-copyright) (require 'readme-debian)) ;;; init.el ends here