;;; -*- 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". ;; Emacsβ‰₯27 automatically calls package-initialize before loading the ;; user's init file, unless package-enable-at-startup is set to nil in ;; the early init file. (unless (>= emacs-major-version 27) (package-initialize)) (setq custom-file "~/.emacs-custom.el") (load custom-file) (if (fboundp 'setopt) (defalias 'my/setopt 'setopt) (defmacro my/setopt (&rest pairs) (while pairs (customize-set-variable (pop pairs) (pop pairs))))) ;; Some list variables I don't set via Custom. I can't tell Custom ;; "add this element, take those two away": I need to "set in stone" ;; an exhaustive list that will make me (1) scratch my head a few ;; months later when I try to remember which of those items I added, ;; and which were part of the default list (2) miss out on additions ;; to the default list, unless I cautiously audit every release. ;; ;; Examples: erc-modules, git-commit-setup-hook, package-archives. (defun my/list-update (l to-add &optional to-remove) (declare (indent 1)) (thread-first l (seq-union to-add) (seq-difference to-remove))) (load-theme 'eighters t) ;;; 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. (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) (when (< emacs-major-version 28) (defun my/other-window (count &optional all-frames) (interactive "p") (let ((repeat-map (make-sparse-keymap))) (define-key repeat-map [?o] #'other-window) (set-transient-map repeat-map t) (other-window count all-frames))) (global-set-key (kbd "C-x o") #'my/other-window)) ;; Hopefully these will be easier to remember than TeX commands: (quail-define-package "my/symbols" "UTF-8" "𝒰" t "Input arbitrary Unicode symbols with other arbitrary symbols.") (pcase-dolist (`(,key ,translation) '(;; Punctuation ("..." ?…) ;; Math symbols ("~~" ?β‰ˆ) ("~~=" ?β‰Š) ("~==" ?β‰…) ("~=" ?≃) ("==" ?≑) ("^=" ?≙) (":=" ?≔) ("<=" ?≀) (">=" ?β‰₯) ("-->" ?β†’) ("-/>" ?↛) ("==>" ?β‡’) ("=/>" ?⇏) ("<--" ?←) ("" ?↔) ("<=>" ?⇔) ;; Emojis ("\\o/" ?πŸ™Œ) ("\\m/" ?🀘) ;; Pictograms ("/!\\" ?⚠))) (quail-defrule key translation "my/symbols")) (defmacro my/make-input-toggle (input-method) (let ((fsym (intern (format "my/toggle-input-%s" input-method))) ;; Unfortunately, by default `help-make-xrefs' does not try to ;; cross-reference input methods, as `help-xref-mule-regexp' ;; is nil. This can be worked around by setting this variable ;; to `help-xref-mule-regexp-template'. (doc (format "Toggle `%s' input method." input-method))) `(defun ,fsym () ,doc (interactive) ;; `current-input-method' is a string; if INPUT-METHOD is a ;; symbol, neither eq, eql nor equal would return t. (if (string= current-input-method ',input-method) (deactivate-input-method) (set-input-method ',input-method t))))) (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))) (defvar-local my/centered-width 'fill-column) (defvar-local my/centered-set-right-margin nil) (defun my/centered--before-split (&optional _size window-to-split) (let ((windows (if (frame-root-window-p window-to-split) (window-list) (list window-to-split)))) (dolist (w windows) (when (buffer-local-value 'my/centered-mode (window-buffer w)) (set-window-margins w nil nil))))) (defun my/centered--around-splittable (splittable window &optional horizontal) (if (and horizontal (buffer-local-value 'my/centered-mode (window-buffer window))) (let ((margins (window-margins window))) (set-window-margins window nil nil) (prog1 (funcall splittable window horizontal) (apply 'set-window-margins window margins))) (funcall splittable window horizontal))) (advice-add 'split-window-right :before 'my/centered--before-split) (advice-add 'window-splittable-p :around 'my/centered--around-splittable) (define-minor-mode my/centered-mode "Update margins to keep content centered." :init-value nil (if my/centered-mode (progn (add-hook 'window-state-change-functions 'my/centered-set-margins nil t) (dolist (win (get-buffer-window-list)) (my/centered-set-margins win))) (remove-hook 'window-state-change-functions 'my/centered-set-margins t) (dolist (win (get-buffer-window-list)) (set-window-margins win nil)))) (defun my/centered-set-margins (window) (with-current-buffer (window-buffer window) (let* ((target-body-width (cond ((symbolp my/centered-width) (symbol-value my/centered-width)) ((integerp my/centered-width) my/centered-width))) (adjustable-width (- (window-total-width window) (+ (fringe-columns 'left) (fringe-columns 'right)))) (left-margin (when (> adjustable-width target-body-width) (/ (- adjustable-width target-body-width) 2))) (right-margin (and my/centered-set-right-margin left-margin))) (set-window-margins window left-margin right-margin)))) (defun my/kill (stuff) (kill-new stuff) (message stuff)) ;; TODO: my/kill-where ;; * filename ;; * absolute, project-relative (w/o project), namespace-relative, base ;; * function ;; * line number ;; * public URL ;; TODO: my/kill-cite ;; * prefix: nil, >, | ;; * indent ;; * attribution: see my/kill-where ;; * concise: "(manual) Node", "manual(7)" ;; * executable: (info "(manual) Node"), "man 7 manual" ;; * (defun my/kill-filename () (interactive) (my/kill (or (buffer-file-name) default-directory))) (defun my/kill-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) (my/kill (buffer-string))))) (defun my/kill-shell (command) (interactive (list (read-shell-command "Shell command: "))) (with-temp-buffer (call-process-shell-command command nil t) (my/kill (buffer-string)))) (defun my/shell-command-help (command) (interactive (list (read-shell-command "Show --help for: "))) (let* ((command--help (concat command " --help")) (help-buf (get-buffer-create (format "*%s*" command--help)))) (shell-command (concat command--help) help-buf) (display-buffer help-buf))) (defun my/magit-project () (interactive) (require 'project) (magit-status (project-prompt-project-dir))) (defun my/magit-toggle-margin-date () (interactive) (let ((do-message (lambda (old new) (message "%s β‡’ %s" (propertize old 'face 'shadow) (propertize new 'face 'bold))))) (apply do-message (if magit-log-margin-show-committer-date '("commit" "author") '("author" "commit"))) (setq magit-log-margin-show-committer-date (not magit-log-margin-show-committer-date)) (revert-buffer))) (defmacro my/define-prefix-command (name doc bindings) (declare (indent defun)) `(defvar ,name (let ((map (define-prefix-command ',name))) (pcase-dolist (`(,key ,fun) ,bindings) (define-key map key fun)) map) ,doc)) (my/define-prefix-command my/buffer-map "Keymap for buffer manipulation commands." '(("b" bury-buffer) ("g" revert-buffer) ("r" rename-buffer))) (my/define-prefix-command my/display-map "Keymap for display-related commands." '(("c" my/centered-mode) ("l" hl-line-mode) ("n" display-line-numbers-mode) ("t" toggle-truncate-lines) ("v" visual-line-mode))) (my/define-prefix-command my/editing-map "Keymap for toggling editing features." '(("f" auto-fill-mode))) (my/define-prefix-command my/magit-map "Keymap for Magit commands." '(("d" my/magit-toggle-margin-date) ("f" magit-file-dispatch) ("g" magit-status) ("p" my/magit-project) ("x" magit-dispatch) ("\C-f" magit-find-file))) (my/define-prefix-command my/input-map "Keymap for input methods shortcuts." `(("e" ,(my/make-input-toggle emoji)) ("t" ,(my/make-input-toggle TeX)) ("u" ,(my/make-input-toggle my/symbols)))) (my/define-prefix-command my/kill-map "Keymap for adding things to the kill ring." '(("f" my/kill-filename) ("|" my/kill-pipe-region) ("!" my/kill-shell))) (my/define-prefix-command my/manual-map "Keymap for reading manuals." '(("h" my/shell-command-help) ("i" info-display-manual) ("m" man) ("s" shortdoc-display-group))) (my/define-prefix-command my/whitespace-map "Keymap for whitespace-related commands." '(("c" whitespace-cleanup) ("f" page-break-lines-mode) ("m" whitespace-mode) ("t" my/set-tab-width))) ;; C-c [[:alpha:]] is reserved for users - let's make good use of it. (global-set-key (kbd "C-c b") 'my/buffer-map) (global-set-key (kbd "C-c c") 'compile) (global-set-key (kbd "C-c d") 'my/display-map) (global-set-key (kbd "C-c e") 'my/editing-map) (global-set-key (kbd "C-c g") 'my/magit-map) (global-set-key (kbd "C-c i") 'my/input-map) (global-set-key (kbd "C-c k") 'my/kill-map) (global-set-key (kbd "C-c m") 'my/manual-map) (global-set-key (kbd "C-c w") 'my/whitespace-map) (rg-enable-default-bindings) ; Uses the C-c s prefix. ;; What's life without a little risk? (setq disabled-command-function nil) ;;; Window management. ;; Bindings ala Terminator (when window-system (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)) ;;; Lighters. (defun my/symbol-as-icon (c) ;; By default, Emacs 28 uses color fonts for characters from (1) the ;; 'emoji script (2) the 'symbol script, when followed by VS-16. ;; Meanwhile, Emacs 27 knows how to display color fonts, but (1) it ;; has no 'emoji script (2) it doesn't know what to do with VS-16. ;; Bottomline: on Emacs 28, explicitly ask for the emoji ;; presentation with VS-16; on older emacsen, just use the ;; character, and rely on a blanket fontset rule to prefer color ;; fonts for the whole 'symbol script. (apply 'string `(,c ,@(when (>= emacs-major-version 28) '(?\N{VARIATION SELECTOR-16}))))) ;; 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 'footnote-mode "ΒΉ" 'footnote) (delight 'flyspell-mode (propertize (my/symbol-as-icon ?πŸ–‹) 'face 'flyspell-incorrect) 'flyspell) (delight 'hi-lock-mode nil 'hi-lock) (delight 'hs-minor-mode "…" 'hideshow) (delight 'mml-mode "πŸ“§" 'mml) (delight 'page-break-lines-mode nil 'page-break-lines) (delight 'scroll-lock-mode "πŸ“œ" 'scroll-lock) (delight 'text-scale-mode '(:eval (if (>= text-scale-mode-amount 0) "πŸ—š" "πŸ—›")) 'face-remap) (delight 'visual-line-mode nil t) (delight 'with-editor-mode "⸎" 'with-editor) ;; TODO: Narrow (βŒ–, β›Ά) (if (< emacs-major-version 27) (delight 'compilation-in-progress (propertize "βš™" 'face 'compilation-mode-line-run) 'compile) (let* ((indicator (alist-get 'compilation-in-progress mode-line-modes)) (old-props (text-properties-at 0 (car indicator))) (face '(:inverse-video t :inherit compilation-mode-line-run)) (new-props (append `(face ,face) old-props)) (icon (my/symbol-as-icon ?βš™))) (setcar indicator (concat (apply #'propertize icon new-props) " ")))) (setq eglot-menu-string "🦻") (with-eval-after-load 'flymake (let ((indicator (propertize (my/symbol-as-icon ?βš’) 'face 'flymake-error))) ;; Prefer customizing the string instead delight'ing, as flymake ;; slaps a bunch of helpful properties on top of the lighter, ;; which delight would strip. (if (boundp 'flymake-mode-line-lighter) (setq flymake-mode-line-lighter indicator) (delight 'flymake-mode indicator 'flymake)))) ;;; Version control. (add-hook 'git-commit-setup-hook 'git-commit-turn-on-flyspell) (setq magit-process-finish-apply-ansi-colors t) (defun my/revision-at-point () (cond ((derived-mode-p 'magit-mode) (magit-branch-or-commit-at-point)) ((derived-mode-p 'vc-git-log-view-mode) (log-view-current-tag)) ((derived-mode-p 'vc-annotate-mode) (car (vc-annotate-extract-revision-at-line))))) (defun my/describe-revision (rev) "Format a Git revision in a format suitable for changelogs." (interactive (list (let ((rev (my/revision-at-point))) (read-string (format-prompt "Revision" rev) nil nil rev)))) (my/kill (string-trim (shell-command-to-string (format "git show --no-patch --date=short --format='%s' %s" "%cd \"%s\" (%h)" rev))))) ;;; 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/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/make-tabless (f) "Make a function which will run F with `indent-tabs-mode' disabled." (lambda () (:documentation (format "Run `%s' with `indent-tabs-mode' set to nil." f)) (interactive) (let ((indent-tabs-mode nil)) (call-interactively f)))) (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 recenter-positions '(top middle bottom))) (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)) ("Messaging" (or (mode . erc-mode) (mode . message-mode) (derived-mode . gnus-mode))) ("Documentation" (or (mode . Info-mode) (mode . Man-mode) (mode . help-mode))) ("Version control" (or (derived-mode . magit-mode) (name . "\\`\\*vc"))))) (add-hook 'ibuffer-mode-hook (lambda () (ibuffer-switch-to-saved-filter-groups "my/ibuffer-groups"))) ;;; Helper functions and miscellaneous settings. ;;;; French quick toggle. (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))) ;;;; Mailing lists utilities. (defun my/kill-message-id () (interactive) (my/kill (mail-header-message-id (gnus-summary-article-header)))) (defun my/describe-message (id url) (my/kill (format "%s\n%s\n" (if (string-prefix-p "<" id) id (format "<%s>" id)) url))) (defun my/describe-message-id (list id) "Format references from the Message-ID of a gnu.org list." (interactive (list (read-string "List: ") ; TODO: default to current list. (let ((default-id (mail-header-message-id (gnus-summary-article-header)))) (read-string (format-prompt "Message-ID" default-id) nil nil default-id)))) (with-current-buffer (url-retrieve-synchronously (concat ;; For some reason, literal "+" chars cause the search to fail. ;; Escape them. "https://lists.gnu.org/archive/cgi-bin/namazu.cgi" "?query=%2Bmessage-id:" (replace-regexp-in-string "\\+" "%2B" id) "&submit=Search!" "&idxname=" list)) (search-forward-regexp (rx "")) (let ((url (concat "https://lists.gnu.org" (match-string 1)))) (my/describe-message id url)))) (defun my/describe-message-url (url) "Format references from an article archived on MHonArc." (interactive (list (let ((default (or (thing-at-point 'url) (and (derived-mode-p 'eww-mode) (shr-url-at-point nil))))) (read-string (format-prompt "URL" default) nil nil default)))) (with-current-buffer (url-retrieve-synchronously url) (search-forward-regexp "^$") (let ((id (xml-substitute-numeric-entities (match-string 1)))) (my/describe-message id url)))) ;;;; Font stuff 🀷🀦. (when (= emacs-major-version 27) ;; Emacs 27 added support for color fonts, but the default fontset ;; did not use any such font for emoji. (set-fontset-font t 'symbol "Noto Color Emoji" nil 'prepend) ;; Make sure the default font does not get overzealous: βš βš™. ;; For Emacs 28, prefer VS-16: βš οΈβš™οΈ. (setq use-default-font-for-symbols nil)) ;;;; Frame title. (defun my/project-root () (when-let ((project (project-current))) (car (project-roots project)))) (defun my/project-name () (when-let ((root (my/project-root))) (when (not (file-equal-p root "~")) (file-name-nondirectory (string-trim-right root "/"))))) (defun my/connection-name () (when-let ((method (file-remote-p default-directory 'method))) (if (string-match-p "sudo" method) method (format "%s:%s" method (file-remote-p default-directory 'host))))) (defun my/frame-title-format () (let ((prefix ;; Messing with match data during redisplay is dangerous ;; (cf. bug#33697). (save-match-data ;; For some reason, calling filename-parsing functions ;; while TRAMP is busy opens the gates to Infinite ;; Minibuffer Recursion Hell. Cautiously side-step that. (or (my/connection-name) (my/project-name))))) (concat (when prefix (format "[%s] " prefix)) "%b"))) (setq frame-title-format '(:eval (my/frame-title-format))) ;;;; Clipboard interaction. (defun my/kill-as-html (text markup) (interactive (list (buffer-substring (region-beginning) (region-end)) (or (alist-get major-mode '((markdown-mode . "markdown") (org-mode . "org") (rst-mode . "rst"))) (let ((default "plain")) (read-string (format-prompt "Convert from:" default) nil nil default))))) ;; TODO: make this a transient to easily (un)set pandoc extensions. (with-temp-buffer (call-process-region text nil "pandoc" nil t nil "--from" markup "--to" "html") ;; TODO: could `gui-set-selection' help here? The docstring makes ;; it sound like passing a value with a 'text/html property set to ;; the HTML string should work, but empirically it doesn't. ;; Maybe look into `selection-converter-alist'. (call-process-region nil nil "xclip" nil nil nil "-selection" "clipboard" "-target" "text/html"))) (defun my/yank-from-html (html markup) (interactive (list (gui-get-selection 'CLIPBOARD 'text/html) (or (alist-get major-mode '((markdown-mode . "markdown") (org-mode . "org") (rst-mode . "rst"))) (let ((default "plain")) (read-string (format-prompt "Convert to:" default) nil nil default))))) ;; TODO: make this a transient to easily (un)set pandoc extensions. (let* ((disabled-html-extensions '("native_divs" "native_spans")) (html-spec (funcall 'string-join `("html" ,@disabled-html-extensions) "-")) (disabled-markup-extensions '("smart")) (markup-spec (funcall 'string-join `(,markup ,@disabled-markup-extensions) "-"))) (call-process-region html nil "pandoc" nil t t "--from" html-spec "--to" markup-spec))) ;;;; Miscellany. (setq-default paragraph-start (concat "[ ]*- \\|" paragraph-start)) (defun my/screenshot (output) (interactive (list (let ((default (format-time-string "/tmp/Emacs-Screenshot-%F-%T.pdf"))) (read-file-name (format-prompt "Output?" default) nil default)))) (let ((data (x-export-frames)) (buf (find-file output))) (insert data) (save-buffer) (kill-buffer buf))) ;; Trying out use-package. (use-package use-package :custom (use-package-always-defer t)) (use-package package :custom (package-selected-packages (append '(auctex debbugs delight diff-hl elisp-benchmarks gnus-mock magit markdown-mode page-break-lines rg rust-mode which-key wgrep) (when (<= emacs-major-version 29) '(eglot use-package)))) :config (my/setopt package-archives (my/list-update package-archives '(("melpa" . "https://melpa.org/packages/"))))) (use-package diff-hl :custom (diff-hl-flydiff-mode t) (global-diff-hl-mode t) ;; FIXME: Adding to these hooks _here_ clobbers them, i.e. they end ;; up containing (a) the diff-hl functions (b) whatever functions ;; their libraries add dynamically (c) *none* of the functions ;; included in the defcustom's default value. ;; ;; Therefore, set these hooks up in the :config form _for the ;; libraries that define these hooks_, so that (presumably) the ;; default values for these hooks are loaded *before* adding the ;; diff-hl functions. ;; ;; :hook ;; ((dired-mode . diff-hl-dired-mode-unless-remote) ;; (magit-pre-refresh . diff-hl-magit-pre-refresh) ;; (magit-post-refresh . diff-hl-magit-post-refresh)) ) (use-package dired :custom (dired-kill-when-opening-new-dired-buffer t) (dired-listing-switches "-al -Fhv --group-directories-first") :config (add-hook 'dired-mode-hook 'diff-hl-dired-mode-unless-remote)) (use-package ediff :custom (ediff-merge-split-window-function 'split-window-vertically) (ediff-split-window-function 'split-window-horizontally) (ediff-window-setup-function 'ediff-setup-windows-plain)) (use-package eldoc :delight "πŸ“–") (use-package erc :custom (erc-log-channels-directory (concat user-emacs-directory "erc/logs")) (erc-log-write-after-insert t) (erc-log-write-after-send t) (erc-notifications-icon (concat data-directory "images/icons/hicolor/scalable/apps/emacs.svg")) (erc-notifications-mode t) (erc-prompt-for-nickserv-password nil) (erc-prompt-for-password nil) (erc-use-auth-source-for-nickserv-password t) (erc-user-full-name 'user-full-name) :config (if (version< erc-version "5.6-git") (my/setopt erc-modules (my/list-update erc-modules '(log notifications) '(fill))) (my/setopt erc-fill-function 'erc-fill-wrap erc-log-filter-function 'erc-stamp-prefix-log-filter erc-modules (my/list-update erc-modules '(log notifications)))) (my/setopt erc-track-exclude-types (my/list-update erc-track-exclude-types '("JOIN" "PART" "QUIT")))) (use-package generic-x :demand t :custom (generic-extras-enable-list '(etc-fstab-generic-mode etc-modules-conf-generic-mode etc-passwd-generic-mode etc-services-generic-mode etc-sudoers-generic-mode hosts-generic-mode pkginfo-generic-mode resolve-conf-generic-mode x-resource-generic-mode))) (use-package isearch :delight "πŸ”" :custom (isearch-allow-scroll t) (isearch-lazy-count t) (search-default-mode 'char-fold-to-regexp)) (use-package magit :custom (magit-define-global-key-bindings nil) (magit-diff-refine-hunk t) (magit-ediff-dwim-show-on-hunks t) (magit-revision-show-gravatars t) :config ;; See `diff-hl' form for rationale. (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh) (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)) (use-package magit-blame :delight "πŸ‘‰") (use-package markdown-mode :custom (markdown-asymmetric-header t) (markdown-command "pandoc -s") (markdown-enable-math t) (markdown-header-scaling t) (markdown-indent-on-enter 'indent-and-new-item)) ;; Gripes: ;; - underused keys: C-M-i, C-j ;; - (minibuffer-)choose-completion ignore completion-no-auto-exit ;; when the candidate is a directory: the candidate is inserted in ;; the minibuffer and the user does *not* exit the minibuffer. ;; ;; In minibuffer: ;; - TAB complete, or show/update completions ;; - TABΒ² jump to completions ;; - C-M-n, C-M-p highlight candidate (without changing minibuffer) ;; - RET, C-j accept minibuffer input ;; - M-RET accept highlighted candidate ;; - C-u M-RET insert highlighted candidate (without accepting) ;; ;; In completions: ;; - n, TAB, p highlight candidate (without changing minibuffer) ;; - RET accept highlighted candidate ;; - C-u RET insert highlighted candidate in minibuffer (without accepting) ;; - C-g, q back to minibuffer (use-package minibuffer :config (setq completion-ignore-case t) (define-key completion-in-region-mode-map (kbd "C-M-n") 'minibuffer-next-completion) (define-key completion-in-region-mode-map (kbd "C-M-p") 'minibuffer-previous-completion) (define-key minibuffer-mode-map (kbd "C-M-n") 'minibuffer-next-completion) (define-key minibuffer-mode-map (kbd "C-M-p") 'minibuffer-previous-completion) :custom (completion-auto-help 'visible) (completion-auto-select 'second-tab) (completion-show-help nil) (completions-detailed t) (completions-format 'one-column) (completions-group t) (completions-max-height 10) (minibuffer-completion-auto-choose nil) (read-buffer-completion-ignore-case t) (read-file-name-completion-ignore-case t)) (use-package org :config (when (version< org-version "9.4") (define-key org-mode-map (kbd "C-j") 'org-return) (define-key org-mode-map (kbd "RET") 'org-return-indent)) :custom (org-edit-src-content-indentation 0) (org-ellipsis "…") (org-fontify-done-headline nil) (org-fontify-quote-and-verse-blocks t) (org-goto-interface 'outline-path-completion) (org-startup-indented t) (org-use-extra-keys t) (org-use-speed-commands t) ;; Make org-refile a bit more eager. (org-outline-path-complete-in-steps nil) (org-refile-targets '((nil . (:maxlevel . 10)))) (org-refile-use-outline-path t)) (use-package org-indent :delight "Β»") (use-package paren :custom (show-paren-mode t) (show-paren-predicate t)) (use-package python :custom (python-fill-docstring-style 'pep-257-nn) (python-forward-sexp-function nil) (python-indent-def-block-scale 1)) (use-package shell :config (setq shell-font-lock-keywords nil) (add-hook 'shell-mode-hook 'my/shell-hook)) (use-package which-key :custom (which-key-idle-delay 0.5) (which-key-mode t) :delight) (use-package whitespace :config (my/setopt whitespace-style (my/list-update whitespace-style nil '(lines missing-newline-at-eof))) :delight ;; FIXME: without :demand t, enabling whitespace-mode in a diff ;; buffer first causes diff-mode's settings to be applied globally. :demand t) ;;; TODO: ;; * decruftify mode-line (e.g. remove superflous parens). ;; * teach some modes to give better names to their buffers to reduce ;; clobbering: info, occur