#+Title: spl3g's Emacs config #+AUTHOR: spl3g #+STARTUP: showeverything #+OPTIONS: toc:2 * Table Of Contents :toc: - [[#progs-to-load-first][Progs to load first]] - [[#elpaca][elpaca]] - [[#exwm][Exwm]] - [[#from-minimal-emacsd][From minimal-emacs.d]] - [[#no-littering][No littering]] - [[#save-history][Save history]] - [[#general-keybindings][General keybindings]] - [[#which-key][Which key]] - [[#better-keyboard-quit][Better keyboard-quit]] - [[#gui-tweaks][GUI tweaks]] - [[#disable-gui-shit][Disable gui shit]] - [[#fonts][Fonts]] - [[#display-line-numbers][Display line numbers]] - [[#icons][Icons]] - [[#modeline][Modeline]] - [[#scroll][Scroll]] - [[#indent-guide][Indent guide]] - [[#org-mode][Org mode]] - [[#toc][TOC]] - [[#requests-from-org-mode][Requests from org-mode]] - [[#better-ux][Better UX]] - [[#delete-selection][Delete selection]] - [[#vertico][Vertico]] - [[#consult][Consult]] - [[#embark][Embark]] - [[#pairs][Pairs]] - [[#debugging][Debugging]] - [[#truncate-lines][Truncate lines]] - [[#rainbow-delimiters][Rainbow delimiters]] - [[#better-help][Better *help*]] - [[#better-other-window][Better other-window]] - [[#undo-tree][Undo Tree]] - [[#dumb-jump][Dumb jump]] - [[#command-sorting-and-filtering][Command sorting and filtering]] - [[#anzu][Anzu]] - [[#regions][Regions]] - [[#eshell][Eshell]] - [[#open-eshell][Open eshell]] - [[#select-eshell][Select eshell]] - [[#rename-eshell][Rename eshell]] - [[#fish-completions][Fish completions]] - [[#eat][Eat]] - [[#highlighting][Highlighting]] - [[#lsp][LSP]] - [[#tempel-templates][Tempel templates]] - [[#error-checking][Error checking]] - [[#corfu][Corfu]] - [[#tree-sitter][Tree-sitter]] - [[#snippets][Snippets]] - [[#formatting][Formatting]] - [[#languages][Languages]] - [[#direnv][Direnv]] - [[#better-scratch][Better scratch]] - [[#dired][Dired]] - [[#bindings][Bindings]] - [[#sql][Sql]] - [[#additional-apps][Additional apps]] - [[#magit][Magit]] * Progs to load first ** elpaca #+begin_src emacs-lisp (setq elpaca-core-date 20240622) (defvar elpaca-installer-version 0.8) (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory)) (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory)) (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory)) (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git" :ref nil :depth 1 :files (:defaults "elpaca-test.el" (:exclude "extensions")) :build (:not elpaca--activate-package))) (let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory)) (build (expand-file-name "elpaca/" elpaca-builds-directory)) (order (cdr elpaca-order)) (default-directory repo)) (add-to-list 'load-path (if (file-exists-p build) build repo)) (unless (file-exists-p repo) (make-directory repo t) (when (< emacs-major-version 28) (require 'subr-x)) (condition-case-unless-debug err (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*")) ((zerop (apply #'call-process `("git" nil ,buffer t "clone" ,@(when-let* ((depth (plist-get order :depth))) (list (format "--depth=%d" depth) "--no-single-branch")) ,(plist-get order :repo) ,repo)))) ((zerop (call-process "git" nil buffer t "checkout" (or (plist-get order :ref) "--")))) (emacs (concat invocation-directory invocation-name)) ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch" "--eval" "(byte-recompile-directory \".\" 0 'force)"))) ((require 'elpaca)) ((elpaca-generate-autoloads "elpaca" repo))) (progn (message "%s" (buffer-string)) (kill-buffer buffer)) (error "%s" (with-current-buffer buffer (buffer-string)))) ((error) (warn "%s" err) (delete-directory repo 'recursive)))) (unless (require 'elpaca-autoloads nil t) (require 'elpaca) (elpaca-generate-autoloads "elpaca" repo) (load "./elpaca-autoloads"))) (add-hook 'after-init-hook #'elpaca-process-queues) (elpaca `(,@elpaca-order)) #+end_src ** Exwm #+begin_src emacs-lisp ;; (use-package exwm ;; :ensure nil ;; :custom ;; ;; Set the initial workspace number. ;; (exwm-workspace-number 2) ;; (exwm-workspace-show-all-buffers t) ;; :hook ;; (after-init . exwm-init) ;; ;; Make class name the buffer name. ;; (exwm-update-class . (lambda () (exwm-workspace-rename-buffer exwm-class-name))) ;; :config ;; (defmacro my/map-command (name) ;; `(lambda () ;; (interactive) ;; (start-process-shell-command ,name nil ,name))) ;; ;; ;; Global keybindings. ;; (setq exwm-input-global-keys ;; `(([?\s-r] . exwm-reset) ;; s-r: Reset (to line-mode). ;; ([?\s-w] . exwm-workspace-switch) ;; s-w: Switch workspace. ;; ([?\s-&] . (lambda (cmd) ;; s-&: Launch application. ;; (interactive (list (read-shell-command "$ "))) ;; (start-process-shell-command cmd nil cmd))) ;; ([?\s-z] . ,(my/map-command "boomer")) ;; ([XF86Go] . ,(my/map-command (expand-file-name "switch_sink.py" ;; exwm--my-scripts))) ;; ([cancel] . ,(my/map-command (expand-file-name "toggle_vpn.sh" ;; exwm--my-scripts))) ;; ([XF86AudioLowerVolume] . ,(my/map-command "wpctl set-volume @DEFAULT_SINK@ 5%-")) ;; ([XF86AudioRaiseVolume] . ,(my/map-command "wpctl set-volume @DEFAULT_SINK@ 5%+")) ;; ([XF86AudioMute] . ,(my/map-command "wpctl set-mute @DEFAULT_SINK@ toggle")) ;; ;; s-N: Switch to certain workspace. ;; ,@(mapcar (lambda (i) ;; `(,(kbd (format "s-%d" i)) . ;; (lambda () ;; (interactive) ;; (exwm-workspace-switch-create ,i)))) ;; (number-sequence 0 9)))) ;; ;; Enable EXWM ;; (display-battery-mode) ;; ;; (defvar my-prev-battery nil) ;; (defun my/battery-notification (data) ;; (when (and my-prev-battery ;; (equal (alist-get ?L data) "off-line") ;; (< (string-to-number (alist-get ?p data)) 10) ;; (>= (string-to-number (alist-get ?p my-prev-battery)) 10)) ;; (my/map-command (string-join '("notify-send 'Battery is low' " "97.4")))) ;; (setq my-prev-battery data)) ;; (add-to-list 'battery-update-functions 'my/battery-notification) ;; ;; (display-time-mode) ;; (set-face-attribute 'default nil :height 113) ;; (exwm-enable)) #+end_src ** From minimal-emacs.d *** Misc #+begin_src emacs-lisp ;; switch-to-buffer runs pop-to-buffer-same-window instead (setq switch-to-buffer-obey-display-actions t) (setq show-paren-delay 0.1 show-paren-highlight-openparen t show-paren-when-point-inside-paren t show-paren-when-point-in-periphery t) (setq whitespace-line-column nil) ; whitespace-mode (setq comint-prompt-read-only t) (setq comint-buffer-maximum-size 2048) (setq compilation-always-kill t compilation-ask-about-save nil compilation-scroll-output 'first-error) (setq truncate-string-ellipsis "…") ;; Configure Emacs to ask for confirmation before exiting (setq confirm-kill-emacs 'y-or-n-p) ;; Delete by moving to trash in interactive mode (setq delete-by-moving-to-trash (not noninteractive)) #+end_src *** Files #+begin_src emacs-lisp ;; Disable the warning-suppress-types "X and Y are the same file". Ignoring this warning is ;; acceptable since it will redirect you to the existing buffer regardless. (setq find-file-suppress-same-file-warnings t) ;; Resolve symlinks when opening files, so that any operations are conducted ;; from the file's true directory (like `find-file'). (setq find-file-visit-truename t vc-follow-symlinks t) ;; Skip confirmation prompts when creating a new file or buffer (setq confirm-nonexistent-file-or-buffer nil) (setq uniquify-buffer-name-style 'forward) (setq mouse-yank-at-point t) #+end_src *** Backup files #+begin_src emacs-lisp ;; Avoid generating backups or lockfiles to prevent creating world-readable ;; copies of files. (setq create-lockfiles nil) (setq make-backup-files nil) (setq backup-directory-alist `(("." . ,(expand-file-name "backup" user-emacs-directory)))) (setq tramp-backup-directory-alist backup-directory-alist) (setq backup-by-copying-when-linked t) (setq backup-by-copying t) ; Backup by copying rather renaming (setq delete-old-versions t) ; Delete excess backup versions silently (setq version-control t) ; Use version numbers for backup files (setq kept-new-versions 5) (setq kept-old-versions 5) (setq vc-make-backup-files nil) ; Do not backup version controlled files #+end_src *** Auto save #+begin_src emacs-lisp ;; Enable auto-save to safeguard against crashes or data loss. The ;; `recover-file' or `recover-session' functions can be used to restore ;; auto-saved data. (setq auto-save-default t) ;; Do not auto-disable auto-save after deleting large chunks of ;; text. The purpose of auto-save is to provide a failsafe, and ;; disabling it contradicts this objective. (setq auto-save-include-big-deletions t) ;; Auto save options (setq kill-buffer-delete-auto-save-files t) #+end_src *** Auto revert =Auto-revert= in Emacs is a feature that automatically updates the contents of a buffer to reflect changes made to the underlying file on disk. #+begin_src emacs-lisp (setq revert-without-query (list ".") ; Do not prompt auto-revert-stop-on-user-input nil auto-revert-verbose t) ;; Revert other buffers (e.g, Dired) (setq global-auto-revert-non-file-buffers t) (add-hook 'after-init-hook #'global-auto-revert-mode) #+end_src *** Recentf =recentf= is an Emacs package that maintains a list of recently accessed files, making it easier to reopen files you have worked on recently. #+begin_src emacs-lisp (setq recentf-max-saved-items 300) ; default is 20 (setq recentf-auto-cleanup 'mode) (add-hook 'after-init-hook #'recentf-mode) #+end_src *** Save place =save-place-mode= enables Emacs to remember the last location within a file upon reopening. This feature is particularly beneficial for resuming work at the precise point where you previously left off. #+begin_src emacs-lisp (setq save-place-file (expand-file-name "saveplace" user-emacs-directory)) (setq save-place-limit 600) (add-hook 'after-init-hook #'save-place-mode) #+end_src *** Frames and windows #+begin_src emacs-lisp ; Resizing the Emacs frame can be costly when changing the font. Disable this ;; to improve startup times with fonts larger than the system default. (setq frame-resize-pixelwise t) ;; However, do not resize windows pixelwise, as this can cause crashes in some ;; cases when resizing too many windows at once or rapidly. (setq window-resize-pixelwise nil) (setq resize-mini-windows 'grow-only) #+end_src *** Scrolling #+begin_src emacs-lisp ;; Enables faster scrolling through unfontified regions. This may result in ;; brief periods of inaccurate syntax highlighting immediately after scrolling, ;; which should quickly self-correct. (setq fast-but-imprecise-scrolling t) ;; Move point to top/bottom of buffer before signaling a scrolling error. (setq scroll-error-top-bottom t) ;; Keeps screen position if the scroll command moved it vertically out of the ;; window. (setq scroll-preserve-screen-position t) #+end_src *** Cursor #+begin_src emacs-lisp (blink-cursor-mode -1) ;; Don't blink the paren matching the one at point, it's too distracting. (setq blink-matching-paren nil) #+end_src *** Annoyances #+begin_src emacs-lisp ;; No beeping or blinking (setq visible-bell nil) (setq ring-bell-function #'ignore) ;; This controls how long Emacs will blink to show the deleted pairs with ;; `delete-pair'. A longer delay can be annoying as it causes a noticeable pause ;; after each deletion, disrupting the flow of editing. (setq delete-pair-blink-delay 0.03) #+end_src *** Indent and formatting #+begin_src emacs-lisp (setq-default left-fringe-width 8) (setq-default right-fringe-width 8) ;; Do not show an arrow at the top/bottom the fringe and empty lines (setq-default indicate-buffer-boundaries nil) (setq-default indicate-empty-lines nil) ;; Continue wrapped lines at whitespace rather than breaking in the ;; middle of a word. (setq-default word-wrap t) ;; Prefer spaces over tabs. Spaces offer a more consistent default compared to ;; 8-space tabs. This setting can be adjusted on a per-mode basis as needed. (setq-default indent-tabs-mode nil tab-width 4) ;; Enable multi-line commenting which ensures that `comment-indent-new-line' ;; properly continues comments onto new lines, which is useful for writing ;; longer comments or docstrings that span multiple lines. (setq comment-multi-line t) ;; We often split terminals and editor windows or place them side-by-side, ;; making use of the additional horizontal space. (setq-default fill-column 80) ;; Disable the obsolete practice of end-of-line spacing from the ;; typewriter era. (setq sentence-end-double-space nil) ;; According to the POSIX, a line is defined as "a sequence of zero or ;; more non-newline characters followed by a terminating newline". (setq require-final-newline t) ;; Remove duplicates from the kill ring to reduce clutter (setq kill-do-not-save-duplicates t) ;; Ensures that empty lines within the commented region are also commented out. ;; This prevents unintended visual gaps and maintains a consistent appearance, ;; ensuring that comments apply uniformly to all lines, including those that are ;; otherwise empty. (setq comment-empty-lines t) ;; Eliminate delay before highlighting search matches (setq lazy-highlight-initial-delay 0) #+end_src *** Filetype #+begin_src emacs-lisp ;; Do not notify the user each time Python tries to guess the indentation offset (setq python-indent-guess-indent-offset-verbose nil) (setq sh-indent-after-continuation 'always) (setq dired-clean-confirm-killing-deleted-buffers nil dired-recursive-deletes 'top dired-recursive-copies 'always dired-create-destination-dirs 'ask) #+end_src *** Font / text scale #+begin_src emacs-lisp ;; Avoid automatic frame resizing when adjusting settings. (setq global-text-scale-adjust-resizes-frames nil) #+end_src *** Ediff #+begin_src emacs-lisp ;; Configure Ediff to use a single frame and split windows horizontally (setq ediff-window-setup-function #'ediff-setup-windows-plain ediff-split-window-function #'split-window-horizontally) #+end_src ** No littering #+begin_src emacs-lisp (use-package no-littering :demand t :config (no-littering-theme-backups)) #+end_src ** Save history #+begin_src emacs-lisp (use-package savehist :ensure nil :init (setq savehist-file "~/.config/emacs/var/savehist.el") :custom (history-length 1000) (history-delete-duplicates t) (savehist-additional-variables '(kill-ring search-ring)) :config (savehist-mode t)) #+end_src ** General keybindings #+begin_src emacs-lisp (use-package general :config ;; SPC as the global leader key (general-create-definer spl3g/leader-keys :prefix "C-c") (spl3g/leader-keys ;; Buffers "b" '(:ignore t :wk "Buffer") "bi" '(ibuffer :wk "ibuffer") "bk" '(kill-current-buffer :wk "Kill this buffer") "bn" '(next-buffer :wk "Next buffer") "bp" '(previous-buffer :wk "Previous buffer") "br" '(revert-buffer :wk "Reload buffer") "." '(find-file :wk "Find file") ;; Splits "w" '(:ignore t :wk "Splits") "wv" '(split-window-right :wk "Split vertical") "ws" '(split-window-below :wk "Split") "ww" '(other-window :wk "Cycle throug windows") "wc" '(delete-window :wk "Close window") "wd" '(delete-window :wk "Close window") "wl" '(windmove-right :wk "") "wj" '(windmove-down :wk "") "wk" '(windmove-up :wk "") "wh" '(windmove-left :wk "") "wo" '(delete-other-windows :wk "") ;; Files "f" '(:ignore t :wk "Files") "fc" '((lambda () (interactive) (find-file user-init-file)) :wk "Edit emacs config") ;; Compilation "r" '(recompile :wk "Recompile") ;; Open "o" '(:ignore t :wk "Open") "ot" '(spl3g/eshell-dwim :wk "Open eshell") "os" '(spl3g/select-eshell :wk "Select eshell to open") "or" '(spl3g/rename-current-eshell :wk "Rename active eshell"))) (elpaca-wait) #+end_src ** Which key #+begin_src emacs-lisp (use-package which-key :ensure nil :init (which-key-mode)) #+end_src ** Better keyboard-quit #+begin_src emacs-lisp (defun prot/keyboard-quit-dwim () "Do-What-I-Mean behaviour for a general `keyboard-quit'. The generic `keyboard-quit' does not do the expected thing when the minibuffer is open. Whereas we want it to close the minibuffer, even without explicitly focusing it. The DWIM behaviour of this command is as follows: - When the region is active, disable it. - When a minibuffer is open, but not focused, close the minibuffer. - When the Completions buffer is selected, close it. - In every other case use the regular `keyboard-quit'." (interactive) (cond ((region-active-p) (keyboard-quit)) ((derived-mode-p 'completion-list-mode) (delete-completion-window)) ((> (minibuffer-depth) 0) (abort-recursive-edit)) (t (keyboard-quit)))) (define-key global-map (kbd "C-g") #'prot/keyboard-quit-dwim) #+end_src * GUI tweaks ** Disable gui shit #+begin_src emacs-lisp (defun spl3g/disable-scroll-bars (frame) (modify-frame-parameters frame '((vertical-scroll-bars . nil) (horizontal-scroll-bars . nil)))) (add-hook 'after-make-frame-functions 'spl3g/disable-scroll-bars) #+end_src ** Fonts #+begin_src emacs-lisp (set-face-attribute 'default nil :font "FiraCode Nerd Font" :height 110 :weight 'medium) (set-face-attribute 'fixed-pitch nil :font "FiraCode Nerd Font" :height 110 :weight 'medium) (set-face-attribute 'variable-pitch nil :font "Noto Serif" :height 110 :weight 'medium) (set-face-attribute 'font-lock-comment-face nil :slant 'italic) (set-face-attribute 'font-lock-keyword-face nil :weight 'bold) #+end_src ** Display line numbers #+begin_src emacs-lisp (add-hook 'prog-mode-hook 'display-line-numbers-mode) #+end_src ** Icons #+begin_src emacs-lisp (use-package all-the-icons :defer t :ensure t :if (display-graphic-p)) #+end_src ** Modeline #+begin_src emacs-lisp (defvar after-load-theme-hook nil "Hook run after a color theme is loaded using `load-theme'.") (defadvice load-theme (after run-after-load-theme-hook activate) "Run `after-load-theme-hook'." (run-hooks 'after-load-theme-hook)) (defadvice consult-theme (after run-after-load-theme-hook activate) "Run `after-load-theme-hook'." (run-hooks 'after-load-theme-hook)) (defun widen-mode-line () "Widen the mode-line." (interactive) (set-face-attribute 'mode-line nil :inherit 'mode-line :box '(:line-width 8 :style flat-button)) (set-face-attribute 'mode-line-inactive nil :inherit 'mode-line-inactive :box '(:line-width 8 :style flat-button))) (add-hook 'after-load-theme-hook 'widen-mode-line) (use-package mood-line ;; Enable mood-line :config (mood-line-mode) :custom (mood-line-segment-modal-meow-state-alist '((normal "N" . mood-line-meow-normal) (insert "I" . mood-line-meow-insert) (keypad "K" . mood-line-meow-keypad) (beacon "B" . mood-line-meow-beacon) (motion "M" . mood-line-meow-motion))) (mood-line-glyph-alist mood-line-glyphs-fira-code) :custom-face (mood-line-meow-beacon ((t (:inherit 'font-lock-function-name-face :weight bold)))) (mood-line-meow-insert ((t (:inherit 'font-lock-string-face :weight bold)))) (mood-line-meow-keypad ((t (:inherit 'font-lock-keyword-face :weight bold)))) (mood-line-meow-motion ((t (:inherit 'font-lock-constant-face :weight bold)))) (mood-line-meow-normal ((t (:inherit 'font-lock-variable-use-face :weight bold))))) #+end_src ** Scroll #+begin_src emacs-lisp (setq scroll-margin 5 scroll-conservatively 101 mouse-wheel-progressive-speed nil) #+end_src ** Indent guide #+begin_src emacs-lisp (use-package indent-guide :hook (prog-mode . indent-guide-mode)) #+end_src * Org mode #+begin_src emacs-lisp (use-package org-mode :ensure nil :custom (text-mode-ispell-word-completion nil) (org-hide-emphasis-markers t) (org-startup-indented t) :mode "\\.org\\'") #+end_src ** TOC #+begin_src emacs-lisp (use-package toc-org :ghook 'org-mode-hook) #+end_src ** Requests from org-mode #+begin_src emacs-lisp (use-package verb :hook (org-mode . verb-mode) :general (:keymaps 'org-mode-map "C-c C-r" verb-command-map)) #+end_src * Better UX ** Delete selection #+begin_src emacs-lisp (use-package delsel :ensure nil :hook (after-init . delete-selection-mode)) #+end_src ** Vertico #+begin_src emacs-lisp (use-package vertico :init (vertico-mode) :general (:keymaps 'vertico-map "M-j" 'vertico-next "M-k" 'vertico-previous "RET" 'vertico-directory-enter "DEL" 'vertico-directory-delete-char "M-DEL" 'vertico-directory-delete-word)) (use-package emacs :ensure nil :init ;; Add prompt indicator to `completing-read-multiple'. ;; We display [CRM], e.g., [CRM,] if the separator is a comma. (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) ;; Emacs 28: Hide commands in M-x which do not work in the current mode. ;; Vertico commands are hidden in normal buffers. ;; (setq read-extended-command-predicate ;; #'command-completion-default-include-p) ;; Enable recursive minibuffers (setq enable-recursive-minibuffers t)) #+end_src *** Marginalia #+begin_src emacs-lisp (use-package marginalia :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) #+end_src ** Consult #+begin_src emacs-lisp (use-package consult :general ;; my binds (:prefix "C-c" "f r" 'consult-recent-file "," 'consult-buffer) ;; general (:prefix "C-c" "i" 'consult-info) ;; 'registers' and pop ("M-#" 'consult-register-load "M-'" 'consult-register-store "M-y" 'consult-yank-pop) ;; 'goto' (:prefix "M-g" "" '(:ignore t :wk "consult goto") "c" 'consult-compile-error "f" 'consult-goto-line "M-g" 'consult-goto-line "o" 'consult-outline "I" 'consult-imenu) ;; 'search' (:prefix "M-s" "" '(:ignore t :wk "consult search") "d" 'consult-fd "r" 'consult-ripgrep "l" 'consult-line "L" 'consult-line-multi "k" 'consult-keep-lines "u" 'consult-focus-lines "e" 'consult-isearch-history) ;; Isearch (:prefix "M-s" :keymaps 'isearch-mode-map "e" 'consult-isearch-history "l" 'consult-line "L" 'consult-line-multi) (:keymaps 'isearch-mode-map "M-e" 'consult-isearch-history "M-r" 'consult-history) ;; Minibuffer history (:keymaps 'minibufer-local-map "M-s" 'consult-history "M-r" 'consult-history) :hook (completion-list-mode . consult-preview-at-point-mode) :init (setq register-preview-delay 0 register-preview-function #'consult-register-format) (advice-add #'register-preview :override #'consult-register-window) :config (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-file-register consult--source-recent-file consult--source-project-recent-file ;; :preview-key "M-." :preview-key '(:debounce 0.4 any)) (setq consult-narrow-key "C-+")) #+end_src ** Embark #+begin_src emacs-lisp (use-package embark :bind (("C-." . embark-act) ;; pick some comfortable binding ("C-;" . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init (setq prefix-help-command #'embark-prefix-help-command) :config (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) (use-package embark-consult :hook (embark-collect-mode . consult-preview-at-point-mode)) #+end_src ** Pairs #+begin_src emacs-lisp (electric-pair-mode t) #+end_src ** Debugging #+begin_src emacs-lisp (use-package dape :commands dape :after jsonrpc) #+end_src ** Truncate lines #+begin_src emacs-lisp (global-visual-line-mode t) #+end_src ** Rainbow delimiters #+begin_src emacs-lisp (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+end_src ** Better *help* #+begin_src emacs-lisp (use-package helpful :bind (("C-h f" . helpful-callable) ("C-h v" . helpful-variable) ("C-h k" . helpful-key) ("C-h x" . helpful-command) ("C-c C-d" . helpful-at-point) ("C-h F" . helpful-function))) #+end_src ** Better other-window #+begin_src emacs-lisp (use-package ace-window :custom (aw-keys '(?h ?j ?k ?l ?g ?a ?s ?d ?f)) (aw-scope 'frame) :bind (("M-o" . ace-window) ("C-c w w" . ace-window))) #+end_src ** Undo Tree #+begin_src emacs-lisp (use-package undo-tree :init (global-undo-tree-mode)) #+end_src ** Dumb jump #+begin_src emacs-lisp (use-package dumb-jump :config (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) #+end_src ** Command sorting and filtering #+begin_src emacs-lisp (use-package prescient :config (prescient-persist-mode) (setq completion-styles '(prescient basic) completion-category-overrides '((file (styles basic partial-completion)))) :custom-face (prescient-primary-highlight ((t (:inherit 'font-lock-keyword-face))))) (use-package corfu-prescient :after corfu :config (corfu-prescient-mode)) (use-package vertico-prescient :after vertico :config (vertico-prescient-mode)) #+end_src ** Anzu Better query replace #+begin_src emacs-lisp (use-package anzu :bind (([remap query-replace] . anzu-query-replace) ([remap query-replace-regexp] . anzu-query-replace-regexp) ([remap isearch-query-replace] . anzu-isearch-query-replace) ([remap isearch-query-replace-regexp] . anzu-isearch-query-replace-regexp))) #+end_src ** Regions #+begin_src emacs-lisp (use-package expand-region :bind ("C-=" . er/expand-region)) (keymap-global-set "C-M-#" 'mark-sexp) (keymap-global-set "C-c d" 'delete-pair) (keymap-global-set "C-z" 'zap-up-to-char) #+end_src * Eshell #+begin_src emacs-lisp (add-hook 'eshell-mode-hook (lambda () (setq-local corfu-auto nil))) (add-hook 'eshell-exec-hook (lambda (p) (buffer-disable-undo))) (add-hook 'eshell-kill-hook (lambda (p s) (buffer-enable-undo))) (setq eshell-history-size 500 eshell-history-append t) #+end_src ** Open eshell #+begin_src emacs-lisp (defun spl3g/eshell-dwim () (interactive) (defvar current-prefix-arg) (let* ((project (project-current)) (func (if project 'project-eshell 'eshell)) (buffer-name (if project (format "*%s-eshell*" (project-name project)) "*eshell*")) (current-prefix-arg t)) (if (not (get-buffer buffer-name)) (let ((buf (funcall func))) (switch-to-buffer (other-buffer buf)) (switch-to-buffer-other-window buf)) (switch-to-buffer-other-window buffer-name)))) #+end_src ** Select eshell #+begin_src emacs-lisp (defun spl3g/select-eshell () (interactive) (let* ((eshell-buffers (seq-filter (lambda (buffer) (eq (with-current-buffer buffer major-mode) 'eshell-mode)) (buffer-list))) (eshell-names (mapcar (lambda (buffer) (buffer-name buffer)) eshell-buffers)) (eshell-windows (remove nil (mapcar (lambda (buffer) (let* ((window (get-buffer-window buffer)) (name (buffer-name buffer))) (when window (cons name window)))) eshell-buffers))) (selected-buffer (if (length> eshell-buffers 1) (completing-read "Select eshell buffer: " eshell-names) (car eshell-buffers))) (selected-window (if (length> eshell-windows 1) (cdr (assoc (completing-read "Select window to place the buffer in: " eshell-windows) eshell-windows)) (cdar eshell-windows)))) (if selected-window (progn (select-window selected-window) (switch-to-buffer selected-buffer)) (switch-to-buffer-other-window selected-buffer)))) #+end_src ** Rename eshell #+begin_src emacs-lisp (defun spl3g/rename-current-eshell () "Add NAME in <> to the current project eshell buffer" (interactive) (let* ((eshell-buffers (seq-filter (lambda (buffer) (eq (with-current-buffer buffer major-mode) 'eshell-mode)) (buffer-list))) (eshell-names (mapcar (lambda (buffer) (buffer-name buffer)) eshell-buffers)) (eshell-windows (remove nil (mapcar (lambda (buffer) (let* ((window (get-buffer-window buffer)) (name (buffer-name buffer))) (when window (cons name window)))) eshell-buffers))) (selected-window (if (and (sequencep eshell-windows) (length> eshell-windows 1)) (assoc (completing-read "Select window to place the buffer in: " eshell-windows) eshell-windows) (car eshell-windows))) (additional-name (when selected-window (read-string "Additional name: "))) (buffer-name (when selected-window (car (split-string (car selected-window) "<")))) (formatted-name (if (length> additional-name 0) (format "%s<%s>" buffer-name additional-name) buffer-name)) ) (if selected-window (with-current-buffer (car selected-window) (rename-buffer formatted-name)) (message "No eshell buffers")))) #+end_src ** Fish completions #+begin_src emacs-lisp (use-package fish-completion :hook (eshell-mode . fish-completion-mode)) #+end_src ** Eat #+begin_src emacs-lisp (use-package eat :hook (eshell-load . eat-eshell-mode) (eshell-load . eat-eshell-visual-command-mode) :custom (eat-enable-auto-line-mode t) :custom-face (ansi-color-bright-blue ((t (:inherit 'ansi-color-blue)))) (ansi-color-bright-red ((t (:inherit 'ansi-color-red)))) (ansi-color-bright-red ((t (:inherit 'ansi-color-red)))) (ansi-color-bright-cyan ((t (:inherit 'ansi-color-cyan)))) (ansi-color-bright-black ((t (:inherit 'ansi-color-black)))) (ansi-color-bright-green ((t (:inherit 'ansi-color-green)))) (ansi-color-bright-white ((t (:inherit 'ansi-color-white)))) (ansi-color-bright-yellow ((t (:inherit 'ansi-color-yellow)))) (ansi-color-bright-magenta ((t (:inherit 'ansi-color-magenta)))) :config (add-to-list 'eat-eshell-semi-char-non-bound-keys [?\e ?o]) (eat-eshell-update-semi-char-mode-map)) #+end_src ** Highlighting #+begin_src emacs-lisp (use-package eshell-syntax-highlighting :hook (eshell-mode . eshell-syntax-highlighting-mode)) #+end_src * LSP #+begin_src emacs-lisp (use-package eglot :ensure nil :bind (:map eglot-mode-map ("C-c s a" . eglot-code-actions) ("C-c s r" . eglot-rename) ("C-c s h" . eldoc) ("C-c s f" . eglot-format) ("C-c s F" . eglot-format-buffer) ("C-c s d" . xref-find-definitions-at-mouse) ("C-c s R" . eglot-reconnect)) :custom (completion-category-overrides '((eglot (styles prescient)) (eglot-capf (styles prescient)))) :config (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)) #+end_src ** Tempel templates #+begin_src emacs-lisp (use-package lsp-snippet-tempel :ensure (:host github :repo "tpeacock19/lsp-snippet") :config (lsp-snippet-tempel-eglot-init)) #+end_src ** Error checking #+begin_src emacs-lisp (use-package flycheck-eglot :after (flycheck eglot) :config (defcustom my/flycheck-eglot-include t "Run eglot-check after selected checker." :type 'boolean :group 'flycheck-eglot) (defun my/flycheck-eglot-advice (setup-func &rest r) "Advice for flycheck-eglot--setup. If `flycheck-checker' is set and `flycheck-eglot-exclusive' is nil, set `flycheck-checker' to the old value after running flycheck-eglot SETUP-FUNC with R arguments." (let ((old-flycheck-checker flycheck-checker)) (apply setup-func r) (when (and (not flycheck-eglot-exclusive) old-flycheck-checker (not (eq old-flycheck-checker 'eglot-check))) (setq flycheck-checker old-flycheck-checker) (when my/flycheck-eglot-include (setf (flycheck-checker-get 'eglot-check 'next-checkers) nil) (flycheck-add-next-checker old-flycheck-checker 'eglot-check))))) (advice-add 'flycheck-eglot--setup :around #'my/flycheck-eglot-advice) :init (global-flycheck-eglot-mode 1)) (use-package flycheck :config (add-to-list 'display-buffer-alist '("\\*Flycheck" (display-buffer-reuse-window display-buffer-at-bottom) (reusable-frames . visible) (window-height . 0.35))) (add-to-list 'flycheck-checkers 'python-ruff) :init (global-flycheck-mode)) #+end_src *** Sideline #+begin_src emacs-lisp (use-package sideline :custom (sideline-truncate t)) (use-package sideline-flycheck :hook (flycheck-mode . sideline-mode) (flycheck-mode . sideline-flycheck-setup) :custom (flycheck-display-errors-function nil) (sideline-flycheck-display-mode 'line) :init (add-to-list 'sideline-backends-right 'sideline-flycheck)) #+end_src ** Corfu #+begin_src emacs-lisp (use-package corfu :hook (emacs-startup . global-corfu-mode) :bind (:map corfu-map ("M-j" . corfu-next) ("M-k" . corfu-previous)) :custom (corfu-preselect 'prompt) (corfu-auto t) (corfu-popupinfo-delay 0.5) (corfu-auto-delay 0) (corfu-auto-prefix 2) (corfu-count 16) (corfu-max-width 120) (corfu-scroll-margin 4) (corfu-on-exact-match nil) (tab-always-indent 'complete) (corfu-cycle t) :config (global-corfu-mode) (corfu-popupinfo-mode)) #+end_src *** Cape #+begin_src emacs-lisp (defun cape--dabbrev-project () (let* ((project (project-current)) (buffers (when project (project-buffers project)))) (if project (butlast buffers (- (length buffers) 4)) (cape--buffers-major-mode)))) (use-package cape :after corfu :custom (dabbrev-ignored-buffer-modes '(archive-mode image-mode eshell-mode)) (cape-dabbrev-check-other-buffers #'cape--dabbrev-project) :config ;; (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible) ;; (advice-add #'lsp-completion-at-point :around #'cape-wrap-nonexclusive) (add-hook 'completion-at-point-functions #'cape-dabbrev) (add-hook 'completion-at-point-functions #'cape-file) (add-hook 'completion-at-point-functions #'cape-elisp-block)) #+end_src *** Icons #+begin_src emacs-lisp (use-package nerd-icons-corfu :after corfu :config (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter)) #+end_src ** Tree-sitter #+begin_src emacs-lisp (use-package treesit-auto :custom (treesit-auto-install 'prompt) :config (treesit-auto-add-to-auto-mode-alist 'all) (delete 'html treesit-auto-langs) (global-treesit-auto-mode)) #+end_src ** Snippets #+begin_src emacs-lisp (use-package tempel :general (:keymaps 'tempel-map "M-TAB" 'tempel-next) :custom (tempel-trigger-prefix "<") :init (add-hook 'completion-at-point-functions #'tempel-complete)) (use-package tempel-collection :ensure t :after tempel) #+end_src ** Formatting #+begin_src emacs-lisp (use-package apheleia :ghook 'prog-mode-hook) #+end_src ** Languages *** Python #+begin_src emacs-lisp (add-hook 'python-ts-mode-hook (lambda () (when (executable-find "ruff") (setq-local flycheck-eglot-exclusive nil flycheck-checker nil apheleia-formatter '(ruff-isort ruff)) (setq flycheck-disabled-checkers '(python-flake8 python-pycompile))))) #+end_src *** Rust #+begin_src emacs-lisp (use-package rust-mode :mode "\\.rs\\'") ;; (use-package cargo-mode ;; :after rust-mode ;; :hook ;; (rust-ts-mode . cargo-minor-mode) ;; :config ;; (setq compilation-scroll-output t)) #+end_src *** Fish #+begin_src emacs-lisp (use-package fish-mode :mode "\\.fish\\'") #+end_src *** Nix #+begin_src emacs-lisp (use-package nix-mode :mode ("\\.nix\\'" "\\.nix.in\\'") :init (with-eval-after-load 'eglot ;; (setq-default eglot-workspace-configuration ;; `(:nixd ( :nixpkgs (:expr "import { }") ;; :formatting (:command ["nixpkgs-fmt"]) ;; :options ( :nixos (:expr "(builtins.getFlake \"/home/jerpo/nixfiles\").nixosConfigurations.ltrr-mini.options") ;; :home-manager (:expr "(builtins.getFlake \"/home/jerpo/nixfiles\").homeConfigurations.\"jerpo@ltrr-mini\".options"))))) )) #+end_src *** Web #+begin_src emacs-lisp (use-package web-mode :mode ("\\.html\\'" "\\.phtml\\'" "\\.tpl\\.php\\'" "\\.[agj]sp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.mustache\\'" "\\.djhtml\\'") :hook (eb-mode . (lambda () (electric-pair-local-mode -1))) :custom (web-mode-markup-indent-offset tab-width) (web-mode-css-indent-offset tab-width) (web-mode-code-indent-offset tab-width) (web-mode-script-padding tab-width) (web-mode-style-padding tab-width) :init ;; Vue mode (define-derived-mode vue-mode web-mode "Vue") (add-to-list 'auto-mode-alist '("\\.vue\\'" . vue-mode)) (add-hook 'vue-mode-hook (lambda () (with-eval-after-load 'eglot (defun vue-eglot-init-options () "Set SDK path and default options." (let ((tsdk-path (expand-file-name "lib/node_modules/typescript/lib/" (shell-command-to-string (string-join '("nix-store --query --references $(which vue-language-server)" "xargs -n1 nix-store -q --referrers" "grep typescript" "grep -v language" "sort -u" "tr -d '\n'") " | "))))) `( :typescript (:tsdk ,tsdk-path) :vue (:hybridMode :json-false)))) (when (not (alist-get 'vue-mode eglot-server-programs)) (add-to-list 'eglot-server-programs `(vue-mode . ("vue-language-server" "--stdio" :initializationOptions ,(vue-eglot-init-options))))))))) ;; (use-package emmet-mode ;; :ghook 'web-mode) #+end_src *** TypeScript #+begin_src emacs-lisp (use-package typescript-ts-mode :defer t :ensure nil :custom (typescript-ts-mode-indent-offset tab-width)) #+end_src *** Odin #+begin_src emacs-lisp (use-package odin-mode :ensure (:repo "https://git.sr.ht/~mgmarlow/odin-mode") :bind (:map odin-mode-map ("C-c C-r" . 'odin-run-project) ("C-c C-c" . 'odin-build-project) ("C-c C-t" . 'odin-test-project)) :mode "\\.odin\\'" :config (with-eval-after-load 'lsp-mode (add-to-list 'lsp-language-id-configuration '(odin-mode . "odin")) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection "ols") :activation-fn (lsp-activate-on "odin") :server-id 'ols)))) #+end_src *** Gleam #+begin_src emacs-lisp (use-package gleam-ts-mode :ensure (:host github :repo "gleam-lang/gleam-mode" :branch "gleam-ts-mode") :mode "\\.gleam\\'" :config (with-eval-after-load 'lsp-mode (add-to-list 'lsp-language-id-configuration '(gleam-ts-mode . "gleam")) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection '("glas" "--stdio")) :activation-fn (lsp-activate-on "gleam") :server-id 'glas)))) #+end_src *** Go #+begin_src emacs-lisp (use-package go-ts-mode :defer t :ensure nil :custom (go-ts-mode-indent-offset tab-width)) #+end_src ** Direnv #+begin_src emacs-lisp (use-package direnv :config (direnv-mode)) #+end_src ** Better scratch #+begin_src emacs-lisp (use-package scratch :commands scratch) #+end_src * Dired ** Bindings #+begin_src emacs-lisp (use-package dired :ensure nil :ensure nil :commands (dired dired-jump) :bind (:map dired-mode-map ("h" . dired-up-directory) ("l" . dired-find-file)) :custom (dired-listing-switches "-hal --group-directories-first") (dired-dwim-target t)) #+end_src * Sql #+begin_src emacs-lisp (use-package sql-indent :hook (sql-mode . sqlind-minor-mode)) (setq sql-connection-alist '(("postgres-sirius" (sql-product 'postgres) (sql-user "postgres") (sql-password "postgres") (sql-server "127.0.0.1") (sql-database "postgres") (sql-port 38746)))) (setq sql-sqlite-program "sqlite3") #+end_src * Additional apps *** Casual #+begin_src emacs-lisp ;; (use-package casual ;; :bind (:map ;; calc-mode-map ;; ("C-o" . casual-calc-tmenu))) #+end_src ** Magit #+begin_src emacs-lisp (use-package magit :after transient :bind (("C-c o g" . magit))) #+end_src