#+title: Thoom Emacs #+PROPERTY: header-args:emacs-lisp :tangle ./init.el :mkdirp yes #+STARTUP: content * Prelude ** Utility constants for checking operating system #+begin_src emacs-lisp ;; Check the system used (defconst ON-LINUX (eq system-type 'gnu/linux)) (defconst ON-MAC (eq system-type 'darwin)) (defconst ON-BSD (or ON-MAC (eq system-type 'berkeley-unix))) (defconst ON-WINDOWS (memq system-type '(cygwin windows-nt ms-dos))) #+end_src ** Auto-tangle this file #+begin_src emacs-lisp (defun thoom/org-babel-tangle-config () (when (string-equal (file-truename (buffer-file-name)) (expand-file-name "~/.dotfiles/thoom-emacs/ThoomEmacs.org")) ;; Dynamic scoping to the rescue (let ((org-confirm-babel-evaluate nil)) (org-babel-tangle)))) (add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'thoom/org-babel-tangle-config))) #+end_src * Package Management ** straight.el #+BEGIN_SRC emacs-lisp (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 6)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) #+END_SRC ** use-package #+BEGIN_SRC emacs-lisp (straight-use-package 'use-package) #+END_SRC * Basic UI Tweaks ** Theme #+begin_src emacs-lisp (use-package doom-themes :straight t :config ;; Global settings (defaults) (setq doom-themes-enable-bold t ; if nil, bold is universally disabled doom-themes-enable-italic t) ; if nil, italics is universally disabled (load-theme 'doom-one t) ;; Enable flashing mode-line on errors (doom-themes-visual-bell-config) ;; Corrects (and improves) org-mode's native fontification. (doom-themes-org-config)) #+end_src ** Hiding Stuff Disable a bunch of stuff we don't want to see. #+BEGIN_SRC emacs-lisp (setq inhibit-startup-message t use-dialog-box nil) (tool-bar-mode -1) (scroll-bar-mode -1) ;; On a Mac, the menu bar doesn't take up screen real-estate, so leave it on (if (not ON-MAC) (menu-bar-mode -1)) #+END_SRC ** General Settings Set some common settings, like global-auto-revert-mode. #+BEGIN_SRC emacs-lisp (setq-default scroll-error-top-bottom t) ;; Revert buffers when the underlying file has changed (global-auto-revert-mode 1) ;; Remember recent files (recentf-mode 1) ;; Save what you enter into minibuffer prompts (setq history-length 25) (savehist-mode 1) ;; Visually mark the line the cursor is on (global-hl-line-mode 1) ;; TODO - Decide whether to enable this ;; (defalias 'yes-or-no-p 'y-or-n-p) #+END_SRC ** hl-todo #+begin_src emacs-lisp (use-package hl-todo :straight t :init (global-hl-todo-mode)) #+end_src ** Repeat Mode #+begin_src emacs-lisp (repeat-mode 1) (defmacro thoom/repeat-map (keymap-sym &rest bindings) "A helper macro for defining repeat maps. keymap-sym is a symbol to name the keymap with. bindings are a sequence of cons cells containing a string to be passed to kbd and a function for that key to be mapped to." (declare (indent 1)) `(progn (defvar ,keymap-sym (let ((map (make-sparse-keymap))) ,@(mapcar (lambda (binding) `(define-key map (kbd ,(car binding)) ',(cdr binding))) bindings) map)) (map-keymap (lambda (_key cmd) (when (symbolp cmd) (put cmd 'repeat-map ',keymap-sym))) ,keymap-sym))) #+end_src ** Tab Bar #+begin_src emacs-lisp (tab-bar-mode) ;; TODO - keybindings #+end_src ** Custom File Set a location for the custom file so it doesn't pollute this file. #+BEGIN_SRC emacs-lisp (setq custom-file (locate-user-emacs-file "custom-vars.el")) #+END_SRC * Keybindings ** Which-key #+begin_src emacs-lisp (use-package which-key :straight t :init (which-key-mode) (which-key-setup-side-window-bottom)) #+end_src ** Keybindings #+BEGIN_SRC emacs-lisp (use-package emacs :bind ;; Window management (("C-c w v" . split-window-right) ("C-c w s" . split-window-below) ("C-S-h" . windmove-left) ("C-S-j" . windmove-down) ("C-S-k" . windmove-up) ("C-S-l" . windmove-right) ;; Tab management ("C-c w t c" . tab-bar-new-tab) ("C-c w t d" . tab-bar-close-tab) ("C-c w t n" . tab-bar-switch-to-next-tab) ("C-c w t p" . tab-bar-switch-to-prev-tab) ;; Common keybindings with tab switching in browsers, for use with my mouse bindings ("C-" . tab-bar-switch-to-next-tab) ("C-" . tab-bar-switch-to-prev-tab) ;; Buffer management ("C-c b d" . kill-this-buffer) ("C-c b b" . consult-buffer) ("C-c b p" . previous-buffer) ("C-c b n" . next-buffer) ("C-o" . pop-global-mark) ("M-DEL" . backward-kill-word) ("C-:" . execute-extended-command-for-buffer))) #+END_SRC * Editing ** Fancy Indenting When in indent-rigidly mode (=+= in normal state), use =H/L= to adjust indent by single character increments and =h/l= to adjust by tab stops. #+begin_src emacs-lisp ;; TODO - define keys to move region up and down by lines (define-key indent-rigidly-map (kbd "H") 'indent-rigidly-left) (define-key indent-rigidly-map (kbd "L") 'indent-rigidly-right) (define-key indent-rigidly-map (kbd "h") 'indent-rigidly-left-to-tab-stop) (define-key indent-rigidly-map (kbd "l") 'indent-rigidly-right-to-tab-stop) #+end_src ** Meow #+begin_src emacs-lisp (use-package meow :straight t :init ;; TODO - make this unnecessary (require 'meow) (defun meow-wrap-negative (command) (lambda () (interactive) (let ((current-prefix-arg -1)) (call-interactively command)))) ;; TODO - debug this. should make org headings into meow things (meow-thing-register 'heading 'heading 'heading) (setq meow-use-clipboard t meow-char-thing-table '((?\( . round) (?\) . round) (?\[ . square) (?\] . square) (?\{ . curly) (?\} . curly) (?\" . string) (?\' . string) (?h . heading) (?e . symbol) (?w . window) (?b . buffer) (?p . paragraph) (?l . line) (?d . defun) (?. . sentence)) meow-cheatsheet-layout meow-cheatsheet-layout-qwerty meow-keypad-self-insert-undefined nil) (add-to-list 'meow-mode-state-list '(eshell-mode . insert) t) (meow-motion-overwrite-define-key '("j" . meow-next) '("k" . meow-prev) '("" . ignore)) (meow-leader-define-key ;; SPC j/k will run the original command in MOTION state. '("j" . "H-j") '("k" . "H-k") ;; Use SPC (0-9) for digit arguments. '("1" . meow-digit-argument) '("2" . meow-digit-argument) '("3" . meow-digit-argument) '("4" . meow-digit-argument) '("5" . meow-digit-argument) '("6" . meow-digit-argument) '("7" . meow-digit-argument) '("8" . meow-digit-argument) '("9" . meow-digit-argument) '("0" . meow-digit-argument) '("/" . meow-keypad-describe-key) '("?" . meow-cheatsheet)) (meow-define-keys 'insert '("C-g" . meow-insert-exit) '("C-M-g" . meow-insert-exit)) (meow-normal-define-key '("0" . meow-expand-0) '("9" . meow-expand-9) '("8" . meow-expand-8) '("7" . meow-expand-7) '("6" . meow-expand-6) '("5" . meow-expand-5) '("4" . meow-expand-4) '("3" . meow-expand-3) '("2" . meow-expand-2) '("1" . meow-expand-1) '("-" . negative-argument) '(";" . meow-reverse) '("," . meow-inner-of-thing) '("." . meow-bounds-of-thing) '("[" . meow-beginning-of-thing) '("]" . meow-end-of-thing) '("a" . meow-append) '("A" . meow-open-below) '("b" . meow-back-word) '("B" . meow-back-symbol) '("c" . meow-change) ;; Potential addition '("d" . meow-delete) '("D" . meow-backward-delete) '("e" . meow-next-word) '("E" . meow-next-symbol) '("f" . meow-find) `("F" . ,(meow-wrap-negative 'meow-find)) '("g" . meow-cancel-selection) '("G" . meow-grab) '("h" . meow-left) '("H" . meow-left-expand) '("i" . meow-insert) '("I" . meow-open-above) '("j" . meow-next) '("J" . meow-next-expand) '("k" . meow-prev) '("K" . meow-prev-expand) '("l" . meow-right) '("L" . meow-right-expand) '("m" . meow-join) ;; Potential addition - M '("n" . meow-search) ;; Potential addition - N '("o" . meow-block) '("O" . meow-to-block) '("p" . meow-yank) '("P" . consult-yank-pop) ;; Potential addition - q ;; Potential addition - Q '("r" . meow-replace) '("R" . meow-swap-grab) '("s" . meow-kill) ;; Potential addition - S '("t" . meow-till) `("T" . ,(meow-wrap-negative 'meow-till)) '("u" . meow-undo) '("U" . meow-undo-in-selection) '("v" . meow-visit) `("V" . ,(meow-wrap-negative 'meow-visit)) '("w" . meow-mark-word) '("W" . meow-mark-symbol) '("x" . meow-line) `("X" . ,(meow-wrap-negative 'meow-line)) '("y" . meow-save) '("Y" . meow-sync-grab) '("z" . meow-pop-selection) ;; Potential addition - Z '("'" . repeat) '("/" . meow-comment) '(":" . execute-extended-command) '("=" . indent-region) '("+" . indent-rigidly)) (meow-global-mode 1)) #+end_src * Org #+begin_src emacs-lisp (use-package org :config ;; Add structure templates (add-to-list 'org-modules 'org-tempo t) (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")) ;; Repeat map for previous/next heading (thoom/repeat-map thoom/org-previous-next-visible-heading-repeat-map ("n" . org-next-visible-heading) ("C-n" . org-next-visible-heading) ("p" . org-previous-visible-heading) ("C-p" . org-previous-visible-heading))) (use-package org-bullets :straight t :hook org-mode) #+end_src * VMOCE ** Vertico #+begin_src emacs-lisp (use-package vertico :straight t :init (vertico-mode)) #+end_src ** Marginalia #+begin_src emacs-lisp (use-package marginalia :straight t ;; Either bind `marginalia-cycle' globally or only in the minibuffer :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) #+end_src ** Orderless #+begin_src emacs-lisp (use-package orderless :straight t :init ;; Configure a custom style dispatcher (see the Consult wiki) ;; (setq orderless-style-dispatchers '(+orderless-dispatch) ;; orderless-component-separator #'orderless-escapable-split-on-space) (setq completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))) #+end_src ** Consult #+begin_src emacs-lisp (use-package consult :straight t ;; Replace bindings. Lazily loaded due by `use-package'. :bind (;; C-c bindings (mode-specific-map) ("C-c h" . consult-history) ("C-c m" . consult-mode-command) ("C-c k" . consult-kmacro) ;; C-x bindings (ctl-x-map) ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command ("C-x C-b" . consult-buffer) ("C-x b" . consult-buffer) ;; orig. switch-to-buffer ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer ;; Custom M-# bindings for fast register access ("M-#" . consult-register-load) ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) ("C-M-#" . consult-register) ;; Other custom bindings ("M-y" . consult-yank-pop) ;; orig. yank-pop (" a" . consult-apropos) ;; orig. apropos-command ;; M-g bindings (goto-map) ("M-g e" . consult-compile-error) ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck ("M-g g" . consult-goto-line) ;; orig. goto-line ("M-g M-g" . consult-goto-line) ;; orig. goto-line ("M-g o" . consult-outline) ;; Alternative: consult-org-heading ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) ;; M-s bindings (search-map) ("M-s d" . consult-find) ("M-s D" . consult-locate) ("M-s g" . consult-grep) ("M-s G" . consult-git-grep) ("M-s r" . consult-ripgrep) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("M-s m" . consult-multi-occur) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) ;; Isearch integration ("M-s e" . consult-isearch-history) :map isearch-mode-map ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s l" . consult-line) ;; needed by consult-line to detect isearch ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch ;; Minibuffer history :map minibuffer-local-map ("M-s" . consult-history) ;; orig. next-matching-history-element ("M-r" . consult-history)) ;; orig. previous-matching-history-element ;; Enable automatic preview at point in the *Completions* buffer. This is ;; relevant when you use the default completion UI. :hook (completion-list-mode . consult-preview-at-point-mode) ;; The :init configuration is always executed (Not lazy) :init ;; Optionally configure the register formatting. This improves the register ;; preview for `consult-register', `consult-register-load', ;; `consult-register-store' and the Emacs built-ins. (setq register-preview-delay 0.5 register-preview-function #'consult-register-format) ;; Optionally tweak the register preview window. ;; This adds thin lines, sorting and hides the mode line of the window. (advice-add #'register-preview :override #'consult-register-window) ;; Use Consult to select xref locations with preview (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) ;; Configure other variables and modes in the :config section, ;; after lazily loading the package. :config ;; Optionally configure preview. The default value ;; is 'any, such that any key triggers the preview. ;; (setq consult-preview-key 'any) ;; (setq consult-preview-key (kbd "M-.")) ;; (setq consult-preview-key (list (kbd "") (kbd ""))) ;; For some commands and buffer sources it is useful to configure the ;; :preview-key on a per-command basis using the `consult-customize' macro. (consult-customize consult-theme :preview-key '(:debounce 0.2 any) consult-ripgrep consult-git-grep consult-grep consult-bookmark consult-recent-file consult-xref consult--source-bookmark consult--source-recent-file consult--source-project-recent-file :preview-key (kbd "M-.")) ;; Optionally configure the narrowing key. ;; Both < and C-+ work reasonably well. (setq consult-narrow-key "<") ;; (kbd "C-+") ;; Optionally make narrowing help available in the minibuffer. ;; You may want to use `embark-prefix-help-command' or which-key instead. ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) ;; By default `consult-project-function' uses `project-root' from project.el. ;; Optionally configure a different project root function. ;; There are multiple reasonable alternatives to chose from. ;;;; 1. project.el (the default) ;; (setq consult-project-function #'consult--default-project--function) ;;;; 2. projectile.el (projectile-project-root) ;; (autoload 'projectile-project-root "projectile") ;; (setq consult-project-function (lambda (_) (projectile-project-root))) ;;;; 3. vc.el (vc-root-dir) ;; (setq consult-project-function (lambda (_) (vc-root-dir))) ;;;; 4. locate-dominating-file ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) ) #+end_src ** Embark #+begin_src emacs-lisp (use-package embark :straight t :bind (("C-." . embark-act) ;; pick some comfortable binding ("C-;" . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init ;; Optionally replace the key help with a completing-read interface (setq prefix-help-command #'embark-prefix-help-command) :config ;; Hide the mode line of the Embark live/completions buffers (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) ;; Consult users will also want the embark-consult package. (use-package embark-consult :straight t :after (embark consult) :demand t ; only necessary if you have the hook below ;; if you want to have consult previews as you move around an ;; auto-updating embark collect buffer :hook (embark-collect-mode . consult-preview-at-point-mode)) #+end_src