;; -*- lexical-binding: t -*- ;; Packages and Custom initialization ;; Letting Custom run *before* initializing packages seems to result ;; in packages resetting some of their variables, eg page-break-lines ;; resets global-page-break-lines-mode to nil. Cue Custom shrugging, ;; "changed outside Customize". ;; NB: starting from Emacs 27, package-initialize is automatically ;; called before loading the user's init file, unless ;; package-enable-at-startup is set to nil in the early init file. (when (version< emacs-version "27") (package-initialize)) (setq custom-file "~/.emacs-custom.el") (load custom-file) ;; Key bindings ;; C-h is a special snowflake in many situations; this is the most ;; reliable way I found to consistently get C-h to do what DEL does. ;; ;; Likewise, C-M-h is re-bound by some major modes (CC, Python, Perl), ;; so this is the simplest way I know of to make sure C-M-h sticks as ;; "backward-kill-word". ;; ;; Same story with M-h (mark-paragraph) which gets re-bound by eg ;; markdown-mode and nxml-mode. ;; ;; NB: help and mark-defun are still accessible using H instead of h, ;; except in a terminal. (define-key input-decode-map (kbd "C-h") (kbd "DEL")) (define-key input-decode-map (kbd "C-M-h") (kbd "M-DEL")) (global-set-key (kbd "C-x C-b") 'ibuffer) ;; C-c [[:alpha:]] is reserved for users - let's make good use of it. (global-set-key (kbd "C-c c") 'compile) (global-set-key (kbd "C-c f") 'auto-fill-mode) (global-set-key (kbd "C-c k f") 'my/kill-ring-filename) (global-set-key (kbd "C-c k |") 'my/kill-ring-pipe-region) (global-set-key (kbd "C-c k !") 'my/kill-ring-shell) (global-set-key (kbd "C-c m") 'man) (global-set-key (kbd "C-c p") 'electric-pair-mode) (global-set-key (kbd "C-c t") 'toggle-truncate-lines) (global-set-key (kbd "C-c v") 'visual-line-mode) (global-set-key (kbd "C-c w c") 'whitespace-cleanup) (global-set-key (kbd "C-c w f") 'page-break-lines-mode) (global-set-key (kbd "C-c w m") 'whitespace-mode) (global-set-key (kbd "C-c w t") 'my/set-tab-width) (rg-enable-default-bindings) ; Uses the C-c s prefix. ;; TODO: define my own input method, instead of overloading TeX ;; https://www.emacswiki.org/emacs/TeXInputMethod explains why the ;; with-temp-buffer shenanigans are necessary. ;; Quoth (elisp)Input Methods: ;; > How to define input methods is not yet documented in this manual, ;; > but here we describe how to use them. ;; Regardless, `register-input-method', `quail-define-rules' may help ;; achieving that. (with-temp-buffer (activate-input-method "TeX") (let ((quail-current-package (assoc "TeX" quail-package-alist))) (quail-define-rules ((append . t)) ("~~" ?≈) ("~=" ?≊) ;; would like to add ("^=" ?≙), but "^=" already exists ("..." ?…) ("-->" ?→) ("-/>" ?↛) ("==>" ?⇒) ("=/>" ?⇏) ("<--" ?←) ("" ?↔) ("<=>" ?⇔)))) ;; What's life without a little risk? (setq disabled-command-function nil) ;; Window management (when window-system (load-theme 'eighters t) ;; Bindings ala Terminator (global-set-key [C-tab] 'other-window) (global-set-key (kbd "C-S-o") 'split-window-below) (global-set-key (kbd "C-S-e") 'split-window-right) (global-set-key (kbd "C-+") 'text-scale-adjust) (global-set-key (kbd "C--") 'text-scale-adjust) (global-set-key (kbd "C-0") 'text-scale-adjust) (global-set-key (kbd "C-S-") 'enlarge-window) (global-set-key (kbd "C-S-") 'shrink-window) (global-set-key (kbd "C-S-") 'enlarge-window-horizontally) (global-set-key (kbd "C-S-") 'shrink-window-horizontally)) ;; Online packages configuration ;; So long, Will Mengarini. (delight 'abbrev-mode nil "abbrev") (delight 'auto-fill-function "⏎" t) (delight 'auto-revert-mode "⟳" "autorevert") (delight 'auto-revert-tail-mode "⤓" "autorevert") (delight 'compilation-in-progress "⚙" "compile") (delight 'eldoc-mode "📖" "eldoc") (delight 'flyspell-mode (propertize "🖋" 'face 'flyspell-incorrect) "flyspell") (delight 'hi-lock-mode nil "hi-lock") (delight 'hs-minor-mode "…" "hideshow") (delight 'isearch-mode "🔍" "isearch") (delight 'org-indent-mode "»" "org-indent") (delight 'magit-blame-mode "☞" "magit-blame") (delight 'page-break-lines-mode nil "page-break-lines") (delight 'scroll-lock-mode "📜" "scroll-lock") (delight 'visual-line-mode "⤸" t) (delight 'whitespace-mode nil "whitespace") (delight 'with-editor-mode "⸎" "with-editor") ;; TODO: Narrow (⌖, ⛶) (defun my/magit-mode-hook () (when window-system (local-set-key [C-tab] 'other-window) (local-set-key (kbd "C-i") 'magit-section-cycle) (local-set-key (kbd "") 'magit-section-toggle))) (add-hook 'magit-mode-hook 'my/magit-mode-hook) (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t) ;; Major modes configuration (defun my/c-modes-hook () (c-set-style "bsd") (c-set-offset 'arglist-close 0)) (add-hook 'c-mode-common-hook 'my/c-modes-hook) (defun my/python-hook () (setq-local forward-sexp-function nil)) (add-hook 'python-mode-hook 'my/python-hook) (defun my/compilation-notify (buffer results) (let* ((title (buffer-name buffer)) (status (if (string-equal results "finished\n") "success" "failure")) (icon (format "%s/icons/compilation-%s.png" user-emacs-directory status))) (require 'notifications) (notifications-notify :title title :body results :app-icon icon :timeout 3000))) (add-to-list 'compilation-finish-functions 'my/compilation-notify) (defun my/makefile-hook () ;; I would rather align backslashes with spaces rather than tabs; ;; however, I would also like indent-tabs-mode to remain non-nil. (local-set-key (kbd "C-c C-\\") (my/make-tabless 'makefile-backslash-region)) (local-set-key (kbd "M-q") (my/make-tabless 'fill-paragraph))) (add-hook 'makefile-mode-hook 'my/makefile-hook) (defun my/shell-hook () (setq truncate-lines nil) (setq-local font-lock-comment-face 'default) (setq-local font-lock-string-face 'default) (setq-local recenter-positions '(top middle bottom))) (add-hook 'shell-mode-hook 'my/shell-hook) ;; What I mean: ;; (defun my/erc-hook () ;; (add-to-list 'erc-modules 'log) ;; (delq 'fill erc-modules) ;; (erc-update-modules)) ;; ;; That cannot work because erc-update-modules only iterates over ;; erc-modules, so it will not act on the `fill' module. ;; ;; I do *not* want to maintain an exhaustive and manually curated list ;; of ERC modules; I just want to add/remove a few ones. Customizing ;; erc-{log,fill}-mode does not work: the contents of erc-modules ;; take precedence. ;; ;; My best attempt at solving this is thus abusing erc-modules's ;; setter function, which will iterate over items in the old value, ;; and disable those that are absent from the new one. (defun my/erc-hook () (let ((new-modules (delete-dups (remq 'fill (cons 'log erc-modules))))) (customize-set-variable 'erc-modules new-modules))) (add-hook 'erc-mode-hook 'my/erc-hook) (add-hook 'dired-mode-hook 'diff-hl-dired-mode-unless-remote) (add-to-list 'ibuffer-saved-filter-groups '("my/ibuffer-groups" ("REPL" (or (derived-mode . comint-mode) (mode . lisp-interaction-mode))) ("Programming" (derived-mode . prog-mode)) ("Folders" (mode . dired-mode)) ("Chat" (mode . erc-mode)) ("Documentation" (or (mode . Info-mode) (mode . Man-mode) (mode . help-mode))))) (add-hook 'ibuffer-mode-hook (lambda () (ibuffer-switch-to-saved-filter-groups "my/ibuffer-groups"))) ;; Helper functions and miscellaneous settings. (defun my/make-tabless (f) "Make a function which will run F with indent-tabs-mode disabled." (lambda () (interactive) ;; TODO: copy documentation from F (let ((indent-tabs-mode nil)) (call-interactively f)))) (defun my/set-tab-width (&optional arg) (interactive "P") (let ((new-width (cond (arg (prefix-numeric-value arg)) ((= tab-width 4) 8) (4))) (old-width tab-width)) ;; TODO: for some reason, set-variable takes effect immediately, ;; but setq(-local)? do not: I need to move the cursor before tabs ;; are re-drawn. (set-variable 'tab-width new-width) (message "changed from %s to %s" old-width new-width))) (defun my/kill-ring-filename () (interactive) (kill-new (or (buffer-file-name) default-directory))) (defun my/kill-ring-pipe-region (command) (interactive (list (read-shell-command "Shell command on region: "))) (let ((input (funcall region-extract-function nil))) (with-temp-buffer (insert input) (call-process-region (point-min) (point-max) shell-file-name t t nil shell-command-switch command) (kill-new (buffer-string))))) (defun my/kill-ring-shell (command) (interactive (list (read-shell-command "Shell command: "))) (with-temp-buffer (call-process-shell-command command nil t) (kill-new (buffer-string)))) (defun my/froggify () (ispell-change-dictionary "fr") (setq-local colon-double-space nil) (setq-local sentence-end-double-space nil) (setq-local fill-nobreak-predicate (cons 'fill-french-nobreak-p fill-nobreak-predicate)) (setq-local my/froggified t)) (defun my/unfroggify () (ispell-change-dictionary "default") (setq-local colon-double-space t) (setq-local sentence-end-double-space t) (setq-local fill-nobreak-predicate (remq 'fill-french-nobreak-p fill-nobreak-predicate)) (setq-local my/froggified nil)) (defun my/croak () (interactive) (if (and (boundp 'my/froggified) my/froggified) (my/unfroggify) (my/froggify))) (defun my/buffer-justify-full () (setq default-justification 'full)) ;; Font stuff (🤷 🤦) (set-fontset-font "fontset-default" nil (font-spec :name "Symbola") nil 'append) ;; TODO: fringe fun: hideshowvis, git gutter… ;; TODO: decruftify mode-line (e.g. remove superflous parens)