#+TITLE: Emacs Config #+AUTHOR: Joseph Ferano #+PROPERTY: header-args:emacs-lisp :tangle ./init.el #+STARTUP: overview #+TOC: true ** Early Init *** Filename handler alist Skipping a bunch of regular expression searching in the file-name-handler-alist should improve start time. #+begin_src emacs-lisp (defvar default-file-name-handler-alist file-name-handler-alist) (setq file-name-handler-alist nil) #+end_src *** Garbage Collection The default Garbage Collector is triggered at 800 KB, way too conservative, let's bump to 512 MB. Garbage collection is a big contributor to startup times. This fends it off, then is reset later by enabling `gcmh-mode'. Not resetting it will cause stuttering/freezes. #+begin_src emacs-lisp :tangle ./early-init.el ;; -*- lexical-binding: t -*- (setq gc-cons-threshold (expt 2 32)) (add-hook 'emacs-startup-hook (lambda () "Restore defalut values after init." (setq file-name-handler-alist default-file-name-handler-alist) (if (boundp 'after-focus-change-function) (add-function :after after-focus-change-function (lambda () (unless (frame-focus-state) (garbage-collect)))) (add-hook 'focus-out-hook 'garbage-collect)))) (setq native-comp-async-report-warnings-errors nil) #+end_src Disabling these classic visual options during early-init seems to net a 0.04 ms boost in init time #+begin_src emacs-lisp :tangle ./early-init.el (setq max-specpdl-size 1200) (setq max-lisp-eval-depth 800) (scroll-bar-mode -1) (tool-bar-mode -1) (menu-bar-mode -1) (tooltip-mode -1) #+end_src *** Enhancements Disable package.el, since we will be using elpaca.el. According to the elpaca.el documentation; #+BEGIN_SRC emacs-lisp :tangle ./early-init.el (setq package-enable-at-startup nil) #+END_SRC Prioritize old byte-compiled source files over newer sources. It saves us a little IO time to skip all the mtime checks on each lookup. #+begin_src emacs-lisp :tangle ./early-init.el (setq load-prefer-newer nil) (setq safe-local-variable-values '((org-src-preserve-indentation . t) (eval add-hook 'after-save-hook '(lambda nil (org-babel-tangle)) nil t))) ;; (setq server-name (format "Emacs-%d" (emacs-pid))) ;; (add-hook 'after-init-hook #'server-start) #+end_src Prevent instructions on how to close an emacsclient frame. #+begin_src emacs-lisp (setq server-client-instructions nil) #+end_src Implicitly resizing the Emacs frame adds to init time. Fonts larger than the system default can cause frame resizing, which adds to startup time. #+begin_src emacs-lisp (setq frame-inhibit-implied-resize t) #+end_src Ignore X resources. #+begin_src emacs-lisp (advice-add #'x-apply-session-resources :override #'ignore) #+end_src *** UTF-8 Support #+begin_src emacs-lisp :tangle ./early-init.el (setq default-input-method nil) (setq utf-translate-cjk-mode nil) ; disable CJK coding/encoding (Chinese/Japanese/Korean characters) (set-language-environment 'utf-8) (set-keyboard-coding-system 'utf-8-mac) ; For old Carbon emacs on OS X only (setq locale-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-selection-coding-system 'utf-8) (prefer-coding-system 'utf-8) #+end_src Finish up #+begin_src emacs-lisp (provide 'early-init) ;;; early-init.el ends here #+end_src ** COMMENT Elpaca #+BEGIN_SRC emacs-lisp ;; -*- lexical-binding: t -*- (defvar elpaca-installer-version 0.3) (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 :files (:defaults (: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) (condition-case-unless-debug err (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*")) ((zerop (call-process "git" nil buffer t "clone" (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))) (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)) (setq elpaca-queue-limit 30) #+END_SRC ** Package Management #+begin_src emacs-lisp ;; Also read: (setq package-archives '(("elpa" . "https://elpa.gnu.org/packages/") ("elpa-devel" . "https://elpa.gnu.org/devel/") ("nongnu" . "https://elpa.nongnu.org/nongnu/") ("melpa" . "https://melpa.org/packages/"))) ;; Proof-of-concept to install a list of packages (mapc (lambda (package) (unless (package-installed-p package) (package-install package))) '(recentf benchmark-init ;; dashboard ;; ligature ;; hydra ;; multiple-cursors ;; Text Editing evil evil-collection evil-surround evil-snipe evil-commentary evil-goggles avy all-the-icons-ibuffer drag-stuff ;; ace-window ;; Mail smtpmail sendmail age popper centaur-tabs ;; VEMCO vertico vertico-posframe savehist embark embark-consult marginalia orderless consult consult-eglot all-the-icons-completion helpful vterm ;; eat ;; Enhancements olivetti doom-modeline undo-fu undo-fu-session vundo which-key mono-complete beframe harpoon format-all ;; company ;; lsp-mode ;; lsp-ui ;; flycheck ;; Org org-bullets org-kanban org-fancy-priorities org-roam org-download org-transclusion valign ;; Programming Languages tuareg dune merlin merlin-eldoc utop highlight-quoted rustic ob-rust haskell-mode clojure-mode cider fsharp-mode go-mode json-mode markdown-mode typescript-mode elm-mode gdscript-mode ;; Tools mu4e-alert dirvish restclient gptel disaster magit)) (setopt package-vc-selected-packages '((dotenv :url "https://github.com/pkulev/dotenv.el") (indent-bars :url "https://github.com/jdtsmith/indent-bars") (doom-themes :url "https://github.com/JosephFerano/doom-themes") (org-timeblock :url "https://github.com/ichernyshovvv/org-timeblock") (org-roam-ui :url "https://github.com/org-roam/org-roam-ui") (dape :url "https://github.com/svaante/dape") (odin-mode :url "https://github.com/mattt-b/odin-mode") (app-launcher :url "https://github.com/SebastienWae/app-launcher"))) (package-initialize) #+end_src ** COMMENT Benchmarking This is commented out since it adds ever so slightly to init time, but keep it around in case we need to benchmark slow init times later. #+begin_src emacs-lisp (require 'benchmark-init) (add-hook 'after-init-hook 'benchmark-init/deactivate) #+end_src ** Misc Stuff #+begin_SRC emacs-lisp (setq default-directory "/home/joe/") (setq vc-follow-symlinks t) ; Visit real file when editing a symlink without prompting. (global-auto-revert-mode t) ; Revert buffer's file when the file changes on disk (setq confirm-kill-emacs 'y-or-n-p) (require 'recentf) (recentf-mode t) (setq recentf-auto-cleanup 10) (setq recentf-keep '(file-remote-p file-readable-p)) (setq recentf-max-saved-items 1000) (setq package-native-compile t) #+end_src This avoids those annoying *#backup#* files that get added and eventually slow down loading the file again. #+begin_src emacs-lisp (setq auto-save-default nil) (setq create-lockfiles nil) #+end_src #+begin_src emacs-lisp (setq use-dialog-box nil) (fset 'yes-or-no-p 'y-or-n-p) (setq large-file-warning-threshold 100000000) (setq backup-directory-alist `((".*" . ,(expand-file-name "backups" user-emacs-directory)))) (setq backup-by-copying t delete-old-versions t kept-new-versions 6 kept-old-versions 2 version-control t) #+END_SRC I don't even know how you resume from GUI mode, we'll find a use for this keybinding later on #+begin_src emacs-lisp (when (display-graphic-p) (global-unset-key (kbd "C-z"))) #+end_src ** Visuals *** COMMENT Dashboard Use Dashboard.el. First load `all-the-icons` for nicer rendering #+begin_src emacs-lisp ;; (elpaca 'all-the-icons) ;; (package-vc-install (dashboard :ref) (require 'dashboard) (dashboard-setup-startup-hook) (setq dashboard-items '((recents . 6) (projects . 5) (bookmarks . 5))) (setq dashboard-startup-banner 'logo) (setq dashboard-center-content t) (setq dashboard-set-file-icons t) (setq dashboard-set-navigator t) (setq dashboard-set-heading-icons t) (setq dashboard-projects-backend 'project-el) (add-hook 'dashboard-mode-hook (lambda () (setq-local line-spacing 12) ;; TODO: It's not jumping (dashboard-jump-to-recents))) ;; (defun joe/launch-dashboard () ;; "Jump to the dashboard buffer, if doesn't exists create one." ;; (interactive) ;; (switch-to-buffer dashboard-buffer-name) ;; (dashboard-mode) ;; (dashboard-insert-startupify-lists)) #+end_src *** Olivetti #+begin_src emacs-lisp (require 'olivetti) (if (equal "flowjoe-f37" (system-name)) (setq olivetti-minimum-body-width 100) (setq olivetti-minimum-body-width 120)) (global-set-key (kbd "C-x x o") 'olivetti-mode) (add-hook 'prog-mode-hook 'olivetti-mode) #+end_src Remove this hook from Olivetti so that lines can truncate [[https://github.com/rnkn/olivetti/issues/76][Github issue]] #+begin_src emacs-lisp (remove-hook 'olivetti-mode-on-hook 'visual-line-mode) #+end_src *** Themes #+begin_src emacs-lisp ;; Small changes to my favorite themes ;; (load-theme 'doom-nord-light)) (setq custom-safe-themes t) (custom-set-faces '(dashboard-items-face ((t (:inherit widget-button :weight normal))))) #+end_src We want to add whatever custom theme we selected to the custom variables since emacs will just automatically read it on startup but we don't want emacs to add these settings to init.el, which in our case is worse since we have a literate file. Send all custom variables to ~custom.el~ #+begin_src emacs-lisp (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file) #+end_src I copied this from consult so that I could add the customize-save-variable at the end, so if I load another emacs process, it can have the same thing. #+begin_src emacs-lisp (defun joe/consult-theme (theme) "Disable current themes and enable THEME from `consult-themes'. The command supports previewing the currently selected theme." (interactive (list (let* ((regexp (consult--regexp-filter (mapcar (lambda (x) (if (stringp x) x (format "\\`%s\\'" x))) consult-themes))) (avail-themes (seq-filter (lambda (x) (string-match-p regexp (symbol-name x))) (cons 'default (custom-available-themes)))) (saved-theme (car custom-enabled-themes))) (consult--read (mapcar #'symbol-name avail-themes) :prompt "Theme: " :require-match t :category 'theme :history 'consult--theme-history :lookup (lambda (selected &rest _) (setq selected (and selected (intern-soft selected))) (or (and selected (car (memq selected avail-themes))) saved-theme)) :state (lambda (action theme) (pcase action ('return (consult-theme (or theme saved-theme))) ((and 'preview (guard theme)) (consult-theme theme)))) :default (symbol-name (or saved-theme 'default)))))) (when (eq theme 'default) (setq theme nil)) (unless (eq theme (car custom-enabled-themes)) (mapc #'disable-theme custom-enabled-themes) (when theme (if (custom-theme-p theme) (enable-theme theme) (load-theme theme :no-confirm)) )) (customize-save-variable 'custom-enabled-themes custom-enabled-themes)) #+end_src *** Other Setup other stuff #+begin_src emacs-lisp (setq ring-bell-function 'ignore) (when (equal "flowjoe-f37" (system-name)) (add-to-list 'default-frame-alist '(undecorated . t)) (add-to-list 'default-frame-alist '(fullscreen . maximized))) (add-to-list 'default-frame-alist '(vertical-scroll-bars . nil)) ;; (setq default-frame-alist '( ;; (vertical-scroll-bars) ;; (right-divider-width . 5) ;; (tab-bar-lines . 0) ;; (internal-border-width . 12))) (add-hook 'text-mode-hook (lambda () (setq fill-column 80) (turn-on-auto-fill))) (setq-default display-line-numbers 'relative) (make-variable-buffer-local 'global-hl-line-mode) (defun joe/disable-line-numbers () (display-line-numbers-mode 0)) (dolist (mode '( dashboard-mode-hook org-mode-hook term-mode-hook eww-mode-hook eat-mode-hook vterm-mode-hook dirvish-directory-view-mode-hook eshell-mode-hook lsp-ui-doc-frame-mode-hook dired-mode-hook org-agenda-mode-hook shell-mode-hook magit-mode-hook compilation-mode-hook mu4e-headers-mode-hook mu4e-main-mode-hook)) (add-hook mode #'joe/disable-line-numbers)) (set-window-margins nil 0) (setq-default right-fringe-width 10) (setq scroll-margin 0 scroll-conservatively 100000 scroll-preserve-screen-position 1) (global-hl-line-mode +1) (column-number-mode +1) (modify-all-frames-parameters '((right-divider-width . 5) (tab-bar-lines . 0) (internal-border-width . 12))) (when (>= emacs-major-version 29) (pixel-scroll-precision-mode t)) (setq inhibit-startup-screen t) ;; Don’t compact font caches during GC, in case doom modeline gets laggy issue (setq inhibit-compacting-font-caches t) (require 'highlight-quoted) (add-hook 'emacs-lisp-mode-hook 'highlight-quoted-mode) (require 'doom-modeline) (doom-modeline-mode) (doom-modeline-def-modeline 'main '(bar modals bar window-number matches buffer-info remote-host buffer-position word-count selection-info) '(parrot objed-state misc-info battery grip irc mu4e gnus github debug repl lsp bar input-method indent-info buffer-encoding bar major-mode process)) ;; Show the tab names, just put this at the car of the previous list ;; workspace-name ;; Set default mode-line (add-hook 'doom-modeline-mode-hook (lambda () (doom-modeline-set-modeline 'main 'default))) ;; TODO Likely not needed anymore (dolist (mode '(dired-mode-hook lsp-help-mode-hook fundamental-mode-hook)) (add-hook mode (lambda () (setq truncate-lines t)))) #+end_src Ligatures... are they that useful? #+begin_src emacs-lisp :tangle no (require 'ligature) (global-ligature-mode) #+end_src ** Random Functions #+begin_src emacs-lisp (defun joe/print-digit-as-ascii (digit) (string-match-p "^\\(?:0\\|[1-9][0-9]*\\)$" digit)) (defun joe/get-ascii-in-region (beg end) (interactive "r") (if (use-region-p) (message "%s" (concat (mapcar 'string-to-number (split-string (buffer-substring-no-properties beg end) " ")))) (message "No region selected"))) (define-key ctl-x-x-map (kbd "a") #'joe/get-ascii-in-region) #+end_src ** Text #+begin_src emacs-lisp ;; (set-face-attribute 'default nil :family "Fira Code Nerd Font" :height 150) (let ((height (if (equal "flowjoe-f37" (system-name)) 115 105))) (set-face-attribute 'default nil :family "JetBrainsMono Nerd Font Mono" :height height)) ;; (set-face-attribute 'default nil :family "JetBrainsMono Nerd Font Mono" :height 115) ;; (set-face-attribute 'variable-pitch nil :family "Source Code Pro" :height 120) (setq-default c-basic-offset 4) ;; This is annoying (setq-default indent-tabs-mode nil) (setq-default tab-width 4) (setq-default line-spacing 5) (setq indent-line-function #'indent-relative) ;; (add-hook 'before-save-hook 'whitespace-cleanup) #+end_src ** Text Editor Emacs is an great operating system, if only it had a good text editor... *** Text editing #+begin_src emacs-lisp ;; TODO Find out what to do with this (defun joe/bookmark-set-and-save () "Save the current buffer as a bookmark" (interactive) (bookmark-set) (bookmark-save)) (global-set-key (kbd "M-z") #'zap-up-to-char) (global-set-key (kbd "M-Z") #'zap-to-char) (defun joe/duplicate-line-comment () (interactive) (let ((col (current-column))) (duplicate-line) (comment-line 1) (move-to-column col))) (global-set-key (kbd "C-w") #'backward-kill-word) (global-set-key (kbd "C-c d") 'duplicate-line) (global-set-key (kbd "C-c C-;") 'joe/duplicate-line-comment) (global-set-key (kbd "M-n") (kbd "C-u 1 C-v")) (global-set-key (kbd "M-p") (kbd "C-u 1 M-v")) (defun joe/insert-line-below () "Insert an empty line below the current line." (interactive) (save-excursion (end-of-line) (open-line 1))) (defun joe/insert-line-above () "Insert an empty line above the current line." (interactive) (save-excursion (end-of-line 0) (open-line 1))) (global-set-key (kbd "M-o") #'joe/insert-line-below) (global-set-key (kbd "M-O") #'joe/insert-line-above) (setq-default truncate-lines t) #+end_src Stole this from [[https://github.com/purcell/unfill/][Purcell]] but didn't feel like making it a package depencendy #+begin_src emacs-lisp (defun unfill-toggle () "Toggle filling/unfilling of the current region. Operates on the current paragraph if no region is active." (interactive) (let (deactivate-mark (fill-column (if (eq last-command this-command) (progn (setq this-command nil) most-positive-fixnum) fill-column))) (call-interactively 'fill-paragraph))) #+end_src For the longest time I had no idea why the ~(~ and ~)~ vim motions for sentences weren't working, until I randomly saw this in someone's init.el #+begin_src emacs-lisp (setq sentence-end-double-space nil) #+end_src ~drag-stuff~ package to move lines around, here's a snippet that re-indents #+begin_src emacs-lisp (defun indent-region-advice (&rest ignored) (let ((deactivate deactivate-mark)) (if (region-active-p) (indent-region (region-beginning) (region-end)) (indent-region (line-beginning-position) (line-end-position))) (setq deactivate-mark deactivate))) (advice-add 'drag-stuff-up :after 'indent-region-advice) (advice-add 'drag-stuff-down :after 'indent-region-advice) (define-key prog-mode-map (kbd "M-h") #'drag-stuff-left) (define-key prog-mode-map (kbd "M-j") #'drag-stuff-down) (define-key prog-mode-map (kbd "M-k") #'drag-stuff-up) (define-key prog-mode-map (kbd "M-l") #'drag-stuff-right) #+end_src Simple function more quickly align text #+begin_src emacs-lisp (defun joe/align-whitespace (beg end) "Align column text in region by whitespace." (interactive "r") (align-regexp beg end "\\(\\s-*\\)\\s-" 1 0 t) (indent-region beg end)) #+end_src #+begin_src emacs-lisp (add-hook 'csv-mode-hook #'csv-header-line) (add-hook 'csv-mode-hook #'csv-align-mode) (defun csv-align-visible () "Align visible fields." (interactive) (csv-align-fields nil (window-start) (window-end))) (add-hook 'csv-mode-hook (lambda () (define-key csv-mode-map (kbd "C-c C-a") 'csv-align-mode))) #+end_src Fill region is great, except when you don't need it... #+begin_src emacs-lisp (defun unfill-region (beg end) "Unfill the region, joining text paragraphs into a single logical line. This is useful, e.g., for use with `visual-line-mode'." (interactive "*r") (let ((fill-column (point-max))) (fill-region beg end))) ;; Handy key definition (define-key global-map (kbd "C-M-q") #'unfill-region) #+end_src *** COMMENT Hydra #+begin_src emacs-lisp (require 'hydra) (defhydra hydra-navigate (global-map "") "Window Navigation" ("q" nil "quit") ("d" joe/smooth-scroll-half-page-down "half page down") ("u" joe/smooth-scroll-half-page-up "half page up") ("e" joe/smooth-scroll-short-down "line down") ("y" joe/smooth-scroll-short-up "line up") ("n" next-line "line down") ("p" previous-line "line up") ("M-r" move-to-window-line-top-bottom "Reposition cursor")) #+end_src *** COMMENT Multiple Cursors #+begin_src emacs-lisp (require 'multiple-cursors) #+end_src *** COMMENT Meow #+begin_src emacs-lisp (elpaca 'meow) (defun meow-setup () (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty) (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-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) '("d" . meow-delete) '("D" . meow-backward-delete) '("e" . meow-next-word) '("E" . meow-next-symbol) '("f" . 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) '("n" . meow-search) '("o" . meow-block) '("O" . meow-to-block) '("p" . meow-yank) '("q" . meow-quit) '("Q" . meow-goto-line) '("r" . meow-replace) '("R" . meow-swap-grab) '("s" . meow-kill) '("t" . meow-till) '("u" . meow-undo) '("U" . meow-undo-in-selection) '("v" . meow-visit) '("w" . meow-mark-word) '("W" . meow-mark-symbol) '("x" . meow-line) '("X" . meow-goto-line) '("y" . meow-save) '("Y" . meow-sync-grab) '("z" . meow-pop-selection) '("'" . repeat) '("" . ignore))) (require 'meow) (meow-setup) (meow-global-mode t) (setq scroll-preserve-screen-position nil) #+end_src *** Boon #+begin_src emacs-lisp :tangle no (defun joe/psp-scroll-down-half-page () (interactive) (pixel-scroll-precision-scroll-down-page (/ (window-pixel-height) 2))) (defun joe/psp-scroll-up-half-page () (interactive) (pixel-scroll-precision-scroll-up-page (/ (window-pixel-height) 2))) (elpaca 'boon (require 'boon-qwerty) (boon-mode) (define-key boon-moves-map "h" 'backward-char) (define-key boon-moves-map "j" 'next-line) (define-key boon-moves-map "k" 'previous-line) (define-key boon-moves-map "l" 'forward-char) (define-key boon-moves-map "b" 'boon-smarter-backward) (define-key boon-moves-map "w" 'boon-smarter-forward) (define-key boon-moves-map "q" '("hop" . avy-goto-char-2)) (define-key boon-command-map (kbd "C-k") #'joe/scroll-down-line) (define-key boon-command-map (kbd "C-j") #'joe/scroll-up-line) (define-key boon-command-map (kbd "C-d") #'joe/psp-scroll-down-half-page) (define-key boon-command-map (kbd "C-u") #'joe/psp-scroll-up-half-page) (defun joe/scroll-up-line () (interactive) (scroll-up-line 2)) (defun joe/scroll-down-line () (interactive) (scroll-down-line 2)) (define-key boon-moves-map "H" 'backward-paragraph) (define-key boon-moves-map "L" 'forward-paragraph) (define-key boon-moves-map "K" 'boon-smarter-upward) (define-key boon-moves-map "J" 'boon-smarter-downward) (define-key boon-moves-map "o" 'boon-open-next-line-and-insert) (define-key boon-moves-map "O" 'boon-open-line-and-insert) (define-key boon-moves-map "i" 'boon-set-insert-like-state) (define-key boon-moves-map "r" 'boon-replace-by-character) (define-key boon-moves-map "y" 'boon-replace-by-character) (define-key boon-moves-map "p" 'boon-splice) (define-key boon-moves-map "y" 'boon-treasure-region) (define-key ctl-x-map "s" 'save-buffer)) #+end_src *** Evil #+begin_src emacs-lisp (setq evil-want-keybinding nil) (setq evil-undo-system 'undo-fu) (setq evil-want-C-u-scroll t) (setq evil-want-Y-yank-to-eol t) (setq evil-disable-insert-state-bindings t) (setq evil-echo-state nil) (require 'evil) (evil-mode t) ;; (evil-global-set-key 'insert (kbd "C-w") #'evil-delete-backward-word) ;; vv to expand selection to line (evil-global-set-key 'visual (kbd "v") (lambda () (interactive) (evil-first-non-blank) (exchange-point-and-mark) (evil-end-of-line))) (defvar joe/evil-space-mode-map (make-sparse-keymap) "High precedence keymap.") (define-minor-mode joe/evil-space-mode "Global minor mode for higher precedence evil keybindings." :global t) (joe/evil-space-mode) (dolist (state '(normal visual insert)) (evil-make-intercept-map ;; NOTE: This requires an evil version from 2018-03-20 or later (evil-get-auxiliary-keymap joe/evil-space-mode-map state t t) state)) (evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC t") tab-prefix-map (kbd "SPC p") project-prefix-map (kbd "SPC q") 'kill-buffer-and-window (kbd "SPC h") 'help-command (kbd "SPC hf") 'helpful-callable (kbd "SPC hv") 'helpful-variable (kbd "SPC hk") 'helpful-key (kbd "SPC ho") 'helpful-symbol (kbd "SPC hg") 'helpful-at-point (kbd "SPC fb") 'bookmark-jump (kbd "SPC fr") 'consult-recent-file (kbd "SPC ff") 'project-find-file (kbd "SPC fa") '(lambda () (interactive) (project-find-file t)) (kbd "SPC fi") 'joe/edit-init (kbd "SPC bl") 'mode-line-other-buffer (kbd "SPC ba") 'switch-to-buffer (kbd "SPC bb") 'consult-buffer (kbd "SPC bi") 'ibuffer (kbd "SPC bu") 'recentf-open-most-recent-file (kbd "SPC bm") 'joe/toggle-buffer-mode (kbd "SPC br") 'joe/revert-buffer-no-confirm (kbd "SPC gg") 'magit-status (kbd "SPC gc") 'magit-clone ;; (kbd "SPC ss") 'eat ;; (kbd "SPC sv") 'eat-other-window (kbd "SPC ss") 'joe/vterm-here (kbd "SPC sv") 'vterm-other-window (kbd "SPC Ba") 'joe/bookmark-set-and-save (kbd "SPC Bd") 'bookmark-delete (kbd "SPC mr") 'joe/compile-run (kbd "SPC mc") 'joe/compile-comp (kbd "SPC mm") 'mu4e (kbd "SPC ct") 'joe/consult-theme (kbd "SPC cl") 'consult-line (kbd "SPC ci") 'consult-imenu (kbd "SPC cy") 'consult-yank-from-kill-ring (kbd "SPC cg") 'consult-ripgrep (kbd "SPC cF") 'consult-find (kbd "SPC co") 'consult-outline) (define-key evil-window-map "u" #'winner-undo) (define-key evil-window-map "U" #'winner-redo) (defvar joe-mode-map (let ((map (make-sparse-keymap))) ;; (define-key map (kbd "C-'") #'embark-act) map) "my-keys-minor-mode keymap.") (define-key joe/evil-space-mode-map (kbd "M-'") #'embark-dwim) (define-key joe/evil-space-mode-map (kbd "C-'") #'embark-act) (define-key joe/evil-space-mode-map (kbd "C-/") #'comment-line) (defun joe/scroll-up-line () (interactive) (scroll-up-line 2)) (defun joe/scroll-down-line () (interactive) (scroll-down-line 2)) (evil-global-set-key 'normal (kbd "C-e") #'joe/scroll-up-line) (evil-global-set-key 'normal (kbd "C-y") #'joe/scroll-down-line) (require 'evil-collection) (evil-collection-init) (require 'evil-surround) (global-evil-surround-mode t) (require 'evil-snipe) (evil-snipe-override-mode +1) (require 'evil-commentary) (evil-commentary-mode t) (require 'evil-goggles) (evil-goggles-mode t) (setq evil-goggles-duration 0.075) (setq evil-goggles-pulse t) (setq evil-goggles-async-duration 0.55) #+end_src ** Buffers #+begin_src emacs-lisp (defun joe/kill-this-buffer-or-popup () (interactive) "Kill the buffer normally, but if it's a popper popup, call the popper version" (with-current-buffer (current-buffer) (if (or (eq popper-popup-status nil) (eq popper-popup-status 'raised)) (kill-this-buffer) (popper-kill-latest-popup)))) (global-set-key (kbd "C-x k") #'joe/kill-this-buffer-or-popup) (when (boundp 'evil-mode) (evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC k") #'joe/kill-this-buffer-or-popup)) (global-set-key (kbd "C-x M-k") #'kill-buffer) (require 'all-the-icons-ibuffer) (add-hook 'ibuffer-mode-hook #'all-the-icons-ibuffer-mode) (global-set-key [remap list-buffers] 'ibuffer) (global-set-key (kbd "C-x B") 'ibuffer) (global-set-key (kbd "C-x b") 'consult-project-buffer) (global-set-key (kbd "C-x C-b") 'consult-buffer) (defun joe/switch-other-buffer () "Switch to other buffer" (interactive) (switch-to-buffer (other-buffer))) (global-set-key (kbd "C-x l") 'joe/switch-other-buffer) (save-place-mode t) (setq save-place-file (expand-file-name "places" user-emacs-directory)) #+end_src The theme of `C-x 4` bindings is that they operate on other windows, so this function matches that behavior. #+begin_src emacs-lisp (defun joe/kill-other-buffer-and-window () "Kill other buffer and window" (interactive) (other-window 1) (kill-buffer-and-window)) (global-set-key (kbd "C-x 4 0") 'joe/kill-other-buffer-and-window) (global-set-key (kbd "C-x C-0") 'kill-buffer-and-window) #+end_src Harpoon lets you quickly switch between bookmarked buffers #+begin_src emacs-lisp (global-set-key (kbd "C-c h ") 'harpoon-add-file) ;; And the vanilla commands (global-set-key (kbd "C-c h f") 'harpoon-toggle-file) (global-set-key (kbd "C-c h h") 'harpoon-toggle-quick-menu) (global-set-key (kbd "C-c h c") 'harpoon-clear) (global-set-key (kbd "M-1") 'harpoon-go-to-1) (global-set-key (kbd "M-2") 'harpoon-go-to-2) (global-set-key (kbd "M-3") 'harpoon-go-to-3) (global-set-key (kbd "M-4") 'harpoon-go-to-4) (global-set-key (kbd "M-5") 'harpoon-go-to-5) (global-set-key (kbd "C-c h 1") 'harpoon-go-to-1) (global-set-key (kbd "C-c h 2") 'harpoon-go-to-2) (global-set-key (kbd "C-c h 3") 'harpoon-go-to-3) (global-set-key (kbd "C-c h 4") 'harpoon-go-to-4) (global-set-key (kbd "C-c h 5") 'harpoon-go-to-5) (global-set-key (kbd "C-c h 6") 'harpoon-go-to-6) (global-set-key (kbd "C-c h 7") 'harpoon-go-to-7) (global-set-key (kbd "C-c h 8") 'harpoon-go-to-8) (global-set-key (kbd "C-c h 9") 'harpoon-go-to-9) #+end_src ** Windows *** Window Management #+begin_src emacs-lisp (add-hook 'after-init-hook (lambda () (winner-mode t))) (setq joe/popper-side-toggle 'right) (defun joe/window-split-vertical () (interactive) (set 'joe/popper-side-toggle 'right)) (defun joe/window-split-horizontal () (interactive) (set 'joe/popper-side-toggle 'below)) (define-key ctl-x-4-map (kbd "|") #'joe/window-split-vertical) (define-key ctl-x-4-map (kbd "-") #'joe/window-split-horizontal) (define-key ctl-x-4-map (kbd "t") #'rotate-window) (defun joe/qtile-move-dir (dir) (start-process "qtile-dir" nil "qtile" "cmd-obj" "-o" "layout" "-f" dir)) ;; (start-process "qtile-dir" nil "qtile" "cmd-obj" "-o" "layout" "-f" "right")) (defun joe/windmove-left () (interactive) (if (window-in-direction 'left) (windmove-left) (joe/qtile-move-dir "left"))) (defun joe/windmove-right () (interactive) (if (window-in-direction 'right) (windmove-right) (joe/qtile-move-dir "right"))) (defun joe/windmove-up () (interactive) (if (window-in-direction 'up) (windmove-up) (joe/qtile-move-dir "up"))) (defun joe/windmove-down () (interactive) (if (window-in-direction 'down) (windmove-down) (joe/qtile-move-dir "down"))) ;; (global-set-key (kbd "s-h") #'joe/windmove-left) ;; (global-set-key (kbd "s-l") #'joe/windmove-right) ;; (global-set-key (kbd "s-k") #'joe/windmove-up) ;; (global-set-key (kbd "s-j") #'joe/windmove-down) (global-set-key (kbd "s-h") #'windmove-left) (global-set-key (kbd "s-l") #'windmove-right) (global-set-key (kbd "s-k") #'windmove-up) (global-set-key (kbd "s-j") #'windmove-down) ;; There's a bug in Gnome where frames are resized to the wrong dimensions (defun joe/resize-frames () (interactive) (dolist (frame (frame-list)) (toggle-frame-maximized frame) (toggle-frame-maximized frame))) (global-set-key (kbd "s-") #'joe/resize-frames) #+end_src **** COMMENT Unused for now Ace Window will show a hint if there are more than 2 windows, but I don't really use it #+begin_src emacs-lisp (require 'ace-window) (global-set-key (kbd "C-x o") #'ace-window) (global-set-key (kbd "C-x C-o") #'ace-swap-window) #+end_src #+begin_src emacs-lisp ;; TODO Prot help improving workflow (global-set-key (kbd "C-`") #'window-toggle-side-windows) (defvar joe/side-window-buffers '("^\\*Flycheck errors\\*$" "^\\*Completions\\*$" "^\\*Occur\\*$" "^\\*Help\\*$" "^\\*Embark.*" "^\\*helpful .*" "^\\*Compilation\\*$" "^\\*SQL.*" "^\\*HTTP Response\\*$" "^\\*grep\\*$" "^\\*Colors\\*$" "^\\*Async Shell Command\\*$")) (dolist (bufname joe/side-window-buffers) (add-to-list 'display-buffer-alist `(,bufname display-buffer-in-side-window (side . right) (window-width . 0.43) (slot . 0) (window-parameters (no-delete-other-windows . t))))) #+end_src *** Beframe #+begin_src emacs-lisp (defvar consult-buffer-sources) (declare-function consult--buffer-state "consult") (with-eval-after-load 'consult (defface beframe-buffer '((t :inherit font-lock-string-face)) "Face for `consult' framed buffers.") ;; If you want to filter the current buffer you can use this and replace :items in the ;; beframe-consult-source var ;; (defun joe/consult-beframe-names-minus-current () ;; (delete (buffer-name) (beframe-buffer-names))) ;; :items ,#'joe/consult-beframe-names-minus-current (defvar beframe-consult-source `( :name "Frame-specific buffers (current frame)" :narrow ?F :category buffer :face beframe-buffer :history beframe-history :items ,#'beframe-buffer-names :action ,#'switch-to-buffer :state ,#'consult--buffer-state)) (add-to-list 'consult-buffer-sources 'beframe-consult-source)) (setq beframe-create-frame-scratch-buffer nil) (beframe-mode +1) (defun joe/beframe-switch-and-unassume () (interactive) (let ((curr (current-buffer))) (mode-line-other-buffer) (beframe-unassume-current-frame-buffers-selectively '(curr)))) (evil-global-set-key 'normal (kbd "SPC b f") #'joe/beframe-switch-and-unassume) #+end_src *** Popper #+begin_src emacs-lisp (require 'popper) (setq popper-reference-buffers '("\\*compilation\\*" compilation-mode "^\\*vterm\\*" vterm-mode "^\\*Flymake.*" flymake-mode "^\\*Flycheck.*" flycheck-error-list-mode "^\\*Occur\\*$" occur-mode "^\\*lsp-help\\*" lsp-help-mode "^\\*eldoc\\*" special-mode "^\\*godot.*" godot-mode "^\\*ert\\*" ert-results-mode "^\\*xref\\*" xref-mode "^\\*HTTP Response\\*" javascript-mode "^\\*SQL.*" sql-interactive-mode "^\\*cargo-test\\*" cargo-test-mode "^\\*cargo-run\\*" cargo-run-mode "^\\*rustic-compilation\\*" rustic-compilation-mode "^\\*ansi-term\\*$" term-mode ;; "^\\*Async Shell Command\\*$" shell-mode "^\\*Async Shell Command\\*$" ("^\\*Warnings\\*$" . hide) help-mode helpful-mode)) (global-set-key (kbd "C-`") 'popper-toggle-latest) (global-set-key (kbd "C-~") 'popper-cycle) (global-set-key (kbd "C-M-`") 'popper-toggle-type) (require 'popper-echo) (popper-echo-mode t) (defun joe/get-popper-dir () (with-current-buffer (current-buffer) (if (or (> (window-width) 170) (eq olivetti-mode t)) joe/popper-side-toggle 'below))) ;; TODO Prot We need to revisit this function and change the rules; I ;; just saw that Popper uses side-window for it's internal popup at ;; the bottom function. I prefer the way Popper does it for anything ;; that is going to display horizontally at the bottom. However for ;; side splits I much prefer the functionality we have here where if ;; there's a window on the right, then overtake it and then when I ;; close it, keep whatever window was there open. The issue with ;; side-windows is that they keep the two window splits in tact and ;; that's not what I want. Sort of like how Magit does it, that's what ;; I want to copy. But like I have it here, it should respect my ;; "popper side" variable so I can toggle whether it appears at the ;; bottom or above. (defun joe/popper-display-func (buffer &optional _alist) (cond ((eq joe/popper-side-toggle 'below) (popper-select-popup-at-bottom buffer _alist)) ((when-let ((popup-buffer (cl-find-if #'popper-popup-p (mapcar #'window-buffer (window-list))))) (window--display-buffer buffer (get-buffer-window popup-buffer) 'reuse `((body-function . ,#'select-window))))) ((and (eq joe/popper-side-toggle 'right) (window-in-direction 'left)) (window--display-buffer buffer (get-buffer-window (current-buffer)) 'reuse)) ((when-let ((right-window (window-in-direction 'right)) ((eq joe/popper-side-toggle 'right))) (window--display-buffer buffer right-window 'reuse `((body-function . ,#'select-window))))) ((and (not (window-in-direction 'right)) (not (window-in-direction 'left))) (display-buffer-in-direction buffer `((window-height . 0.45) (window-width . 0.45) (direction . right) (body-function . ,#'select-window)))) (t (display-buffer-in-direction buffer `((window-height . 0.45) (window-width . 0.45) (direction . below) (body-function . ,#'select-window)))))) (setq popper-display-function #'joe/popper-display-func) (popper-mode t) #+end_src *** COMMENT Tab-bar & Tab-line #+begin_src emacs-lisp (global-set-key (kbd "s-n") #'tab-line-switch-to-next-tab) (global-set-key (kbd "s-p") #'tab-line-switch-to-prev-tab) #+end_src *** COMMENT Scrolling #+begin_src emacs-lisp (require 'pixel-scroll) (setq pixel-scroll-precision-large-scroll-height 10.0) (setq pixel-scroll-precision-interpolation-factor 30) (defun joe/pixel-scroll-lerp (amount direction) (let ((half-height (* direction (/ (window-height) amount))) (point-min-or-max (if (> direction 0) (point-min) (point-max)))) (when (or (and (pos-visible-in-window-p (point-min)) (< direction 0))) (pixel-scroll-precision-interpolate (* 5 half-height))) (pixel-scroll-precision-interpolate (* 5 half-height)))) (defun joe/smooth-scroll-half-page-down () "Smooth scroll down" (interactive) (joe/pixel-scroll-lerp 1.5 -1)) ;; (pixel-scroll-kbd-up)) (defun joe/smooth-scroll-half-page-up () "Smooth scroll up" (interactive) (joe/pixel-scroll-lerp 1.5 1)) (defun joe/smooth-scroll-short-down () "Smooth scroll down" (interactive) (joe/pixel-scroll-lerp 8 -1)) (defun joe/smooth-scroll-short-up () "Smooth scroll down" (interactive) (joe/pixel-scroll-lerp 8 1)) ;; scroll-up-command (global-set-key (kbd "C-v") #'joe/smooth-scroll-half-page-down) (global-set-key (kbd "M-v") #'joe/smooth-scroll-half-page-up) (global-set-key (kbd "C-S-v") #'joe/smooth-scroll-short-down) (global-set-key (kbd "M-S-v") #'joe/smooth-scroll-short-up) (defun joe/scroll-other-half-down () (interactive) (scroll-other-window 8)) (defun joe/scroll-other-half-up () (interactive) (scroll-other-window -8)) (global-set-key (kbd "C-M-v") #'joe/scroll-other-half-down) (global-set-key (kbd "C-M-S-V") #'joe/scroll-other-half-up) (require 'topspace) #+end_src ** Tabs/Workspaces *** COMMENT Centaur Tabs #+begin_src emacs-lisp ;; (require 'centaur-tabs) (setq centaur-tabs-set-bar 'under) (setq x-underline-at-descent-line t) (setq centaur-tabs-set-close-button nil) (setq centaur-tabs-set-icons t) (setq centaur-tabs-show-navigation-buttons nil) (setq centaur-tabs-set-close-button nil) (setq centaur-tabs-set-modified-marker nil) (setq centaur-tabs-show-new-tab-button nil) (setq centaur-tabs-label-fixed-length 16) (defun joe/forward-tab-or-popup () (interactive) (if (popper-popup-p (current-buffer)) (popper-cycle) (centaur-tabs-forward-tab))) (defun joe/backward-tab-or-popup () (interactive) (if (popper-popup-p (current-buffer)) (popper-cycle) (centaur-tabs-backward-tab))) ;; (global-set-key (kbd "s-n") #'centaur-tabs-forward-tab) ;; (global-set-key (kbd "s-p") #'centaur-tabs-backward-tab) (global-set-key (kbd "s-n") #'joe/forward-tab-or-popup) (global-set-key (kbd "s-p") #'joe/backward-tab-or-popup) (global-set-key (kbd "s-N") #'centaur-tabs-forward-group) (global-set-key (kbd "s-P") #'centaur-tabs-backward-group) (global-set-key (kbd "C-s-p") #'centaur-tabs-move-current-tab-to-left) (global-set-key (kbd "C-s-n") #'centaur-tabs-move-current-tab-to-right) (dolist (mode '(dashboard-mode-hook)) (add-hook mode 'centaur-tabs-local-mode)) (with-eval-after-load 'centaur-tabs (defun joe/fix-centaur-tabs (THEME) (centaur-tabs-mode -1) (centaur-tabs-mode)) (advice-add 'consult-theme :after #'joe/fix-centaur-tabs) (defun centaur-tabs-buffer-groups () "`centaur-tabs-buffer-groups' control buffers' group rules. Group centaur-tabs with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'. All buffer name start with * will group to \"Emacs\". Other buffer group by `centaur-tabs-get-group-name' with project name." (list (cond ((or (derived-mode-p 'comint-mode) (derived-mode-p 'sql-interactive-mode) (string-match "*HTTP" (buffer-name)) (derived-mode-p 'compilation-mode)) "REPLs") ((or (and (string-equal "*" (substring (buffer-name) 0 1)) (not (string-match "*Org Src" (buffer-name)))) (memq major-mode '(magit-process-mode magit-status-mode magit-diff-mode magit-log-mode magit-file-mode magit-blob-mode magit-blame-mode))) "*Buffers*") (t "Emacs"))))) (centaur-tabs-mode +1) #+end_src *** COMMENT iflipb #+begin_src emacs-lisp (global-set-key (kbd "s-n") #'iflipb-next-buffer) (global-set-key (kbd "s-p") #'iflipb-previous-buffer) (setq iflipb-permissive-flip-back t) (setq iflipb-other-buffer-template " %s ") (setq iflipb-current-buffer-template "<[%s]>") (setq iflipb-buffer-list-function #'tabspaces--buffer-list) #+end_src *** COMMENT Tabspaces #+begin_src emacs-lisp (tabspaces-mode +1) ;; Filter Buffers for Consult-Buffer (with-eval-after-load 'consult ;; hide full buffer list (still available with "b" prefix) (consult-customize consult--source-buffer :hidden t :default nil) ;; set consult-workspace buffer list (defvar consult--source-workspace (list :name "Workspace Buffers" :narrow ?w :history 'buffer-name-history :category 'buffer :state #'consult--buffer-state :default t :items (lambda () (consult--buffer-query :predicate #'tabspaces--local-buffer-p :sort 'visibility :as #'buffer-name))) "Set workspace buffer list for consult-buffer.") (add-to-list 'consult-buffer-sources 'consult--source-workspace)) #+end_src *** COMMENT Tabs #+begin_src emacs-lisp ;; (setq tab-bar-mode t) ;; (setq tab-bar-show nil) ;; (global-set-key (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1))) ;; (global-set-key (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2))) ;; (global-set-key (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3))) ;; (global-set-key (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4))) ;; (global-set-key (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5))) ;; (global-set-key (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6))) ;; (global-set-key (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7))) ;; (global-set-key (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8))) ;; (global-set-key (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9))) ;; (evil-global-set-key 'insert (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1))) ;; (evil-global-set-key 'insert (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2))) ;; (evil-global-set-key 'insert (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3))) ;; (evil-global-set-key 'insert (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4))) ;; (evil-global-set-key 'insert (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5))) ;; (evil-global-set-key 'insert (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6))) ;; (evil-global-set-key 'insert (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7))) ;; (evil-global-set-key 'insert (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8))) ;; (evil-global-set-key 'insert (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9))) ;; (evil-global-set-key 'normal (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1))) ;; (evil-global-set-key 'normal (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2))) ;; (evil-global-set-key 'normal (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3))) ;; (evil-global-set-key 'normal (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4))) ;; (evil-global-set-key 'normal (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5))) ;; (evil-global-set-key 'normal (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6))) ;; (evil-global-set-key 'normal (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7))) ;; (evil-global-set-key 'normal (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8))) ;; (evil-global-set-key 'normal (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9))) #+end_src *** Frames #+begin_src emacs-lisp (undelete-frame-mode) (defun joe/select-frame () (interactive) (let* ((frames (mapcar (lambda (f) (cons (substring-no-properties (cdr (assoc 'name (frame-parameters f)))) f)) (frame-list))) (selected-frame-name (completing-read "Select Frame: " (mapcar #'car frames))) (selected-frame (alist-get selected-frame-name frames "" nil 'string-equal))) (select-frame-set-input-focus selected-frame))) (define-key 'ctl-x-5-prefix (kbd "RET") #'joe/select-frame) #+end_src ** Projects Basic enhancements to project.el #+begin_src emacs-lisp (with-eval-after-load 'project (setq project-vc-ignores '("target/" "bin/" "obj/")) (add-to-list 'project-switch-commands '(magit-project-status "Magit" ?m)) ;; TODO: This doesn't start in the correct working directory (add-to-list 'project-switch-commands '(joe/vterm-here "VTerm" ?s)) (add-to-list 'project-vc-extra-root-markers ".dir-locals.el")) #+end_src These are functions to load a project specific file given the conventions I use. #+begin_src emacs-lisp (defun joe/project-open-project-file (FILENAME) (when (project-current) (let* ((proj-dir (project-root (project-current t))) (file-path (expand-file-name FILENAME proj-dir))) (if (file-exists-p file-path) (find-file-other-window (expand-file-name FILENAME proj-dir)) (when (get-buffer FILENAME) (switch-to-buffer (get-buffer FILENAME))))))) (defun joe/project-open-project-todo () (interactive) (joe/project-open-project-file "TODO.org")) (defun joe/project-open-project-hours () (interactive) (joe/project-open-project-file "HOURS.org")) (defun joe/project-open-project-notes () (interactive) (joe/project-open-project-file "NOTES.org")) (defun joe/project-open-project-readme () (interactive) (let* ((project (project-current)) (root (project-root project)) (README (cond ((file-exists-p (expand-file-name "README.org" root)) "README.org") ((file-exists-p (expand-file-name "README.md" root)) "README.md") ((file-exists-p (expand-file-name "README" root)) "README") (t nil)))) (when README (joe/project-open-project-file README)))) (defun joe/project-open-project-license () (interactive) (joe/project-open-project-file "LICENSE")) (defun joe/project-dirvish-dwim () (interactive) (when (project-current) (dirvish-dwim (project-root (project-current))))) (evil-define-key 'normal joe/evil-space-mode-map (kbd "_") #'joe/project-dirvish-dwim) (define-key project-prefix-map "t" #'joe/project-open-project-todo) (define-key project-prefix-map "h" #'joe/project-open-project-hours) (define-key project-prefix-map "n" #'joe/project-open-project-notes) (define-key project-prefix-map "r" #'joe/project-open-project-readme) (define-key project-prefix-map "l" #'joe/project-open-project-license) (define-key project-prefix-map "s" #'eat-project) (define-key project-prefix-map "S" #'eat-project-other-window) ;; Remape this (define-key project-prefix-map "C-r" #'project-query-replace-regexp) #+end_src If you want to try and incorporate something a bit more robust, try this #+begin_src emacs-lisp :tangle no (defun +project-todo () "Edit the to-do file at the root of the current project." (interactive) (let* ((project (project-root (project-current t))) (todo (car (directory-files project t "^TODO\\(\\..*\\)?$")))) (cond ((and todo (file-exists-p todo)) (find-file todo)) (t (message "Project does not contain a TODO file."))))) #+end_src Stuff to immediately switch to Jetbrains for debugging #+begin_src emacs-lisp (global-set-key (kbd "C-M-r") #'joe/open-in-rider) (defun joe/raise-frame-hook () (select-frame-set-input-focus (selected-frame))) (add-hook 'server-switch-hook #'joe/raise-frame-hook) (add-hook 'server-switch-hook #'raise-frame) (defun joe/open-in-rider () (interactive) (shell-command (mapconcat #'shell-quote-argument (list "rider.sh" "--line" (int-to-string (line-number-at-pos)) "--column" (int-to-string (current-column)) buffer-file-name) " "))) #+end_src ** VEMCO *** Vertico #+begin_src emacs-lisp (require 'all-the-icons-completion) ;; (require '(vertico :files (:defaults "extensions/*") ;; :includes (vertico-indexed ;; vertico-repeat ;; vertico-directory)) (require 'vertico) (vertico-mode) ;; (elpaca 'vertico-posframe) ;; (define-key vertico-map (kbd "C-w") #'vertico-directory-delete-word) ;; (define-key vertico-map (kbd "C-r") #'vertico-repeat-) ;; (vertico-posframe-mode t) ;; (vertico-indexed-mode) ;; (setq vertico-posframe-parameters ;; '((left-fringe . 100) ;; (right-fringe . 100))) ;; (setq vertico-posframe-border-width 5) ;; (setq vertico-posframe-min-height 20) (defun posframe-poshandler-slightly-below-top (info) (cons (/ (- (plist-get info :parent-frame-width) (plist-get info :posframe-width)) 2) 180)) ;; (setq vertico-posframe-poshandler #'posframe-poshandler-frame-center) (setq vertico-posframe-poshandler #'posframe-poshandler-slightly-below-top) (defun joe/consult-buffer-vertico-indexed (start-index) (interactive) (let ((vertico-count 12) (vertico-posframe-width 110) (vertico-posframe-height 20) (vertico-group-format nil) (vertico-indexed-start 1) (vertico--index start-index) (consult-buffer-sources '(beframe-consult-source))) (consult-buffer))) (defun joe/consult-buffer-vertico-next () (interactive) (joe/consult-buffer-vertico-indexed 1)) (defun joe/consult-buffer-vertico-last () (interactive) (joe/consult-buffer-vertico-indexed (1- (length (beframe-buffer-names))))) (global-set-key (kbd "s-n") #'joe/consult-buffer-vertico-next) (global-set-key (kbd "s-p") #'joe/consult-buffer-vertico-last) (setq vertico-count 20 vertico-resize nil vertico-cycle t) (setq vertico-posframe-width 130) (setq vertico-posframe-height 30) (setq vertico-posframe-parameters '((child-frame-border-width . 5) (left-fringe . 30) (right-fringe . 30))) (require 'savehist) (savehist-mode) (add-hook 'minibuffer-setup-hook #'vertico-repeat-save) (add-to-list 'savehist-additional-variables 'vertico-repeat-history) (define-key vertico-map (kbd "C-M-n") #'vertico-next-group) (define-key vertico-map (kbd "s-p") #'vertico-previous) (define-key vertico-map (kbd "s-n") #'vertico-next) ;; #' "C-M-p" #'vertico-previous-group) ;; (elpaca 'vertico-directory) ;; :bind (:map vertico-map ;; ("RET" . vertico-directory-enter) ;; ("DEL" . vertico-directory-delete-char) ;; ("M-DEL" . vertico-directory-delete-word)) ;; :hook (rfn-eshadow-update-overlay . vertico-directory-tidy)) #+end_src *** Embark #+begin_src emacs-lisp (require 'embark) #+end_src These two lines allow you to kill buffers without a confirmation and without closing the mini-buffer so you can quickly kill multiple buffers if needed. The odd looking ~'(t .t)~ is for specifying a default for all other actions. #+begin_src emacs-lisp (setq embark-quit-after-action '((t . t) (kill-buffer . nil))) (setf (alist-get 'kill-buffer embark-pre-action-hooks) nil) #+end_src #+begin_src emacs-lisp (defun embark-which-key-indicator () "An embark indicator that displays keymaps using which-key. The which-key help message will show the type and value of the current target followed by an ellipsis if there are further targets." (lambda (&optional keymap targets prefix) (if (null keymap) (which-key--hide-popup-ignore-command) (which-key--show-keymap (if (eq (plist-get (car targets) :type) 'embark-become) "Become" (format "Act on %s '%s'%s" (plist-get (car targets) :type) (embark--truncate-target (plist-get (car targets) :target)) (if (cdr targets) "…" ""))) (if prefix (pcase (lookup-key keymap prefix 'accept-default) ((and (pred keymapp) km) km) (_ (key-binding prefix 'accept-default))) keymap) nil nil t (lambda (binding) (not (string-suffix-p "-argument" (cdr binding)))))))) (setq embark-indicators '(embark-which-key-indicator embark-highlight-indicator embark-isearch-highlight-indicator)) (defun embark-hide-which-key-indicator (fn &rest args) "Hide the which-key indicator immediately when using the completing-read prompter." (which-key--hide-popup-ignore-command) (let ((embark-indicators (remq #'embark-which-key-indicator embark-indicators))) (apply fn args))) (advice-add #'embark-completing-read-prompter :around #'embark-hide-which-key-indicator) (advice-remove #'embark-completing-read-prompter #'embark-hide-which-key-indicator) #+end_src *** Marginalia #+begin_src emacs-lisp (require 'marginalia) (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) (setq marginalia-align 'right) (setq marginalia-max-relative-age most-positive-fixnum) (marginalia-mode) (define-key minibuffer-local-map (kbd "M-A") #'marginalia-cycle) (require 'all-the-icons-completion) (all-the-icons-completion-mode) (all-the-icons-completion-marginalia-setup) (add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup) #+end_src *** Consult #+begin_src emacs-lisp (require 'embark-consult) (require 'consult) ;; (require 'consult-lsp) (global-set-key (kbd "C-. C-l") 'consult-line) (global-set-key (kbd "C-. C-i") 'consult-imenu) (global-set-key (kbd "C-. C-t") 'joe/consult-theme) (global-set-key (kbd "C-. C-r") 'consult-recent-file) (global-set-key (kbd "C-. C-y") 'consult-yank-from-kill-ring) #+end_src *** Orderless #+begin_src emacs-lisp (require 'orderless) (setq completion-styles '(orderless basic) completion-category-overrides '((file (styles basic partial-completion)))) #+end_src ** App Launcher #+begin_src emacs-lisp (defun emacs-run-launcher () (interactive) (unwind-protect (app-launcher-run-app) (delete-frame))) #+end_src ** Dirvish/Dired #+begin_src emacs-lisp (require 'dirvish) (with-eval-after-load 'dirvish (dirvish-override-dired-mode) (setq delete-by-moving-to-trash t) (setq dired-dwim-target t) (setq dirvish-reuse-session nil) ;; (add-hook 'dired-mode-hook 'centaur-tabs-local-mode) (dirvish-define-preview exa (file) "Use `exa' to generate directory preview." :require ("exa") ; tell Dirvish to check if we have the executable (when (file-directory-p file) ; we only interest in directories here `(shell . ("exa" "--icons" "--color=always" "--no-user" "-al" "--group-directories-first" ,file)))) (add-to-list 'dirvish-preview-dispatchers 'exa) (setq dired-listing-switches "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group") (setq dirvish-preview-dispatchers (cl-substitute 'pdf-preface 'pdf dirvish-preview-dispatchers)) (setq dirvish-attributes '(all-the-icons file-size collapse subtree-state)) (defun joe/dirvish-find-directory (dir) (interactive "FDirvish Directory:") (dirvish-dwim dir)) (setq dirvish-quick-access-entries '(("h" "~/" "Home") ("c" "~/.config" "Config") ("d" "~/Downloads/" "Downloads") ("D" "~/Documents/" "Documents") ("b" "~/Documents/Books/" "Books") ("p" "~/Development/" "Dev") ("t" "~/Tutorials/" "Tutorials") ("n" "~/Notes/" "OrgNotes") ("r" "~/Repositories" "Repos") ("B" "~/pCloudDrive/" "pCloud"))) (when (boundp 'evil-mode) (evil-define-key 'normal dirvish-mode-map (kbd "C-c f") #'dirvish-fd (kbd "a") #'dirvish-quick-access (kbd ".") #'dired-create-empty-file (kbd "f") #'dirvish-file-info-menu (kbd "y") #'dirvish-yank-menu (kbd "h") #'dired-up-directory (kbd "l") #'dired-find-file (kbd "s") #'dirvish-quicksort (kbd "v") #'dirvish-vc-menu (kbd "q") #'dirvish-quit (kbd "L") #'dirvish-history-go-forward (kbd "H") #'dirvish-history-go-backward (kbd "o") #'dired-open-file (kbd "TAB") #'dirvish-subtree-toggle (kbd "M-n") #'dirvish-narrow (kbd "M-l") #'dirvish-ls-switches-menu (kbd "M-m") #'dirvish-mark-menu (kbd "M-t") #'dirvish-layout-toggle (kbd "M-s") #'dirvish-setup-menu (kbd "M-e") #'dirvish-emerge-menu (kbd "M-j") #'dirvish-fd-jump))) ;; There's an issue with the keybinding precedence so we need to run this since ;; joe/evil-space-mode-map takes precedence above all else and '-' doesn't do ;; what it should (defun joe/dirvish-up-dwim () (interactive) (if (eq major-mode 'dired-mode) (dired-up-directory) (dirvish-dwim))) (setq dired-omit-files "\\`[.]?#\\|\\`[.][.]?\\|\\.meta$") (add-hook 'dired-mode-hook #'dired-omit-mode) (when (boundp 'evil-mode) (evil-define-key 'normal joe/evil-space-mode-map (kbd "-") #'joe/dirvish-up-dwim)) (global-set-key (kbd "C-x d") #'dirvish-dwim) (global-set-key (kbd "C-x C-d") #'joe/dirvish-find-directory) (defun dired-open-file () "In dired, open the file named on this line." (interactive) (let* ((file (dired-get-filename nil t))) (message "Opening %s..." file) (call-process "xdg-open" nil 0 nil file) (message "Opening %s done" file))) #+end_src ** Email #+begin_src emacs-lisp (when (file-exists-p "/usr/share/emacs/site-lisp/mu4e/") (add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/") (require 'mu4e) ;; Attach files to a message composition buffer by going into `dired' ;; and doing C-c C-m C-a (M-x `gnus-dired-attach'). (require 'gnus-dired) ; does not require `gnus' (add-hook 'dired-mode-hook #'gnus-dired-mode) (add-hook 'mu4e-main-mode-hook 'olivetti-mode) (add-to-list 'auto-mode-alist '("authinfo" . authinfo-mode)) (setq mu4e-get-mail-command "parallel mbsync -V \"-c ~/.config/mbsync/config\" ::: ferano.io.inbox gmail.allmail") ;;; Sending email (SMTP) (require 'smtpmail) (setq smtpmail-default-smtp-server "mail.gandi.net" smtpmail-smtp-server "mail.gandi.net" smtpmail-stream-type 'ssl smtpmail-smtp-service 465 smtpmail-queue-mail nil) (require 'sendmail) (setq send-mail-function 'smtpmail-send-it) (setq mu4e-update-interval 30) (setq mu4e-hide-index-messages t) (setq mu4e-completing-read-function 'completing-read) (setq mu4e-context-policy 'pick-first) (setq mu4e-compose-context-policy 'ask) (setq mu4e-view-auto-mark-as-read nil) ;; (setq mu4e-sent-messages-behavior 'sent) (setq message-kill-buffer-on-exit t) (require 'age) (setq age-default-identity '("~/.local/credentials/personal")) (setq age-default-recipient '("~/.local/credentials/personal.pub")) (setq auth-source-do-cache nil) (defun joe/mu4e-auth-get-field (host prop) "Find PROP in `auth-sources' for HOST entry." (when-let ((source (auth-source-search :host host))) (if (eq prop :secret) (funcall (plist-get (car source) prop)) (plist-get (flatten-list source) prop)))) (setq auth-sources '("~/.local/credentials/authinfo.age")) (setq mu4e-change-filenames-when-moving t) (age-file-enable) (age-encryption-mode +1) (setq mu4e-bookmarks nil) (setq mu4e-contexts `(,(make-mu4e-context :name "Ferano.io" :enter-func (lambda () (mu4e-message "Entering ferano.io")) :leave-func (lambda () (mu4e-message "Leaving ferano.io")) :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to (joe/mu4e-auth-get-field "mail.gandi.net" :user)))) :vars `((user-mail-address . ,(joe/mu4e-auth-get-field "mail.gandi.net" :user)) (user-full-name . "Joseph Ferano") (mu4e-drafts-folder . "/ferano.io/Drafts/") (mu4e-trash-folder . "/ferano.io/Trash/") (mu4e-sent-folder . "/ferano.io/Sent/"))) ,(make-mu4e-context :name "Gmail" :enter-func (lambda () (mu4e-message "Entering gmail")) :leave-func (lambda () (mu4e-message "Leaving gmail")) :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to (joe/mu4e-auth-get-field "mail.gmail.com" :user)))) :vars `((user-mail-address . ,(joe/mu4e-auth-get-field "mail.gmail.com" :user)) (user-full-name . "Joseph Ferano") (mu4e-drafts-folder . "/gmail/[Gmail]/Drafts/") (mu4e-trash-folder . "/gmail/[Gmail]/Trash/") (mu4e-sent-folder . "/gmail/[Gmail]/Sent Mail/"))))) (setq mu4e-maildir-shortcuts '((:maildir "/ferano.io/Inbox" :key ?f) (:maildir "/gmail/[Gmail]/All Mail" :key ?g))) (mu4e 't) (add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)) ;; (:name "Ferano.io Unread" :query "m:/ferano.io/Inbox AND g:unread" :key ?u))) #+end_src Fold threads. This is a gist provided by Rougier [[https://gist.github.com/rougier/98e83fb50e19fb73fe34a7ecc5fc1ccc][here]]. His other package is [[https://github.com/rougier/mu4e-thread-folding][mu4e-thread-folding]] but they work slightly differently. Code for the latter will be kept here commented out in case we want to try it again. #+begin_src emacs-lisp (when (file-exists-p "/usr/share/emacs/site-lisp/mu4e/") (load-file "/home/joe/Dotfiles/.config/emacs/elisp/mu4e-fast-folding.el") (evil-define-key 'normal mu4e-headers-mode-map (kbd "TAB") #'mu4e-fast-folding-thread-toggle) (evil-define-key 'normal mu4e-headers-mode-map (kbd "") #'mu4e-fast-folding-thread-toggle-all)) ;; (load-file "/home/joe/Dotfiles/.config/emacs/elisp/mu4e-thread-folding.el") ;; (require 'mu4e-fast-folding) ;; (add-to-list 'mu4e-header-info-custom ;; '(:empty . (:name "Empty" ;; :shortname "" ;; :function (lambda (msg) " ")))) ;; (setq mu4e-headers-fields '((:empty . 2) ;; (:human-date . 12) ;; (:flags . 6) ;; (:mailing-list . 10) ;; (:from . 22) ;; (:subject . nil))) ;; (evil-define-key 'normal mu4e-headers-mode-map (kbd "TAB") ;; #'mu4e-headers-fold-at-point) ;; (evil-define-key 'normal mu4e-headers-mode-map (kbd "") ;; #'mu4e-headers-unfold-at-point) ;; (mu4e-thread-folding-mode +1) #+end_src ** Avy #+begin_src emacs-lisp (require 'avy) (setq avy-case-fold-search nil) (setq avy-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l ?q ?w ?e ?r ?u ?i ?o ?p ?z ?x ?c ?v ?n ?m)) ;; (define-key global-map (kbd "C-;") 'avy-goto-char) ;; I use this most frequently (define-key global-map (kbd "M-s") 'avy-goto-char) ;; I use this most frequently ;; (define-key global-map (kbd "C-'") 'avy-goto-line) ;; Consistent with ivy-avy (define-key global-map (kbd "M-g c") 'avy-goto-char) (define-key global-map (kbd "M-g e") 'avy-goto-word-0) ;; lots of candidates (define-key global-map (kbd "M-g g") 'avy-goto-line) ;; digits behave like goto-line (define-key global-map (kbd "M-g w") 'avy-goto-word-1) ;; first character of the word (define-key global-map (kbd "M-g P") 'avy-pop-mark) #+end_src ** Helpful #+begin_src emacs-lisp (require 'helpful) (global-set-key (kbd "C-h f") #'helpful-callable) (global-set-key (kbd "C-h v") #'helpful-variable) (global-set-key (kbd "C-h k") #'helpful-key) (global-set-key (kbd "C-h C-d") #'helpful-at-point) (global-set-key (kbd "C-h F") #'helpful-function) (global-set-key (kbd "C-h C") #'helpful-command) #+end_src ** Terminals/Shells *** vterm #+begin_src emacs-lisp (require 'vterm) (setq vterm-shell "/bin/fish") (setq vterm-timer-delay 0.01) (setq vterm-buffer-name-string "VTerm - %s") ;; (setq vterm-buffer-name-string nil) (setq vterm-max-scrollback 100000) (setq vterm-kill-buffer-on-exit t) (defun joe/close-popup-buffer (vterm-buf event-msg) (unless (or (eq popper-popup-status nil) (eq popper-popup-status 'raised)) (popper-close-latest))) (setq vterm-exit-functions '(joe/close-popup-buffer)) (defun joe/vterm-here () (interactive) (let ((vterm-buf (vterm--internal #'switch-to-buffer))) (with-current-buffer vterm-buf (setq popper-popup-status 'raised)))) ;; (global-set-key (kbd "C-c t") #'vterm) ;; (global-set-key (kbd "C-c T") #'joe/vterm-here) ;; (setq explicit-shell-file-name "~/Development/fell/fell") (add-hook 'shell-mode (lambda () (setq-local global-hl-line-mode nil))) (setq shell-kill-buffer-on-exit t) (defun joe/vterm-mode-hook () (define-key vterm-mode-map (kbd "C-c C-x") #'vterm-send-C-x) (when (boundp 'evil-mode) (evil-define-key 'insert vterm-mode-map (kbd "C-f") (lambda () (interactive) (vterm-send-key (kbd "C-f")))) (evil-define-key 'insert vterm-mode-map (kbd "C-w") (lambda () (interactive) (vterm-send-key (kbd "C-w")))) (evil-define-key 'insert vterm-mode-map (kbd "") #'vterm-send-delete)) (setq-local global-hl-line-mode nil) (setq buffer-face-mode-face '(:family "Fira Code Nerd Font")) (buffer-face-mode)) (add-hook 'vterm-mode-hook #'joe/vterm-mode-hook) #+end_src VTerm is loading TRAMP along with it which slows down init time noticeably so call this after startup. Reason we have to call this is so the vterm fucntion can call `vterm--internal`. #+begin_src emacs-lisp :tangle no ;; (add-hook 'emacs-startup-hook (lambda () (require 'vterm))) #+end_src This bit of code is to achieve a vterm scratchpad for my window manager. In order to know I'm closing the right frame, I'm going to use the frame's name to close and remove the hook #+begin_src emacs-lisp (defun vterm--set-title (title) "Use TITLE to set the buffer name according to `vterm-buffer-name-string'." (when (and vterm-buffer-name-string (not (equal (buffer-name) "Scratch VTerm"))) (rename-buffer (format vterm-buffer-name-string title) t))) (defun joe/kill-frame () (when (equal (buffer-name) "Scratch VTerm") (remove-hook 'delete-frame-functions #'joe/kill-vterm-scratch) (remove-hook 'kill-buffer-hook #'joe/kill-frame) (delete-frame))) (defun joe/kill-vterm-scratch (FRAME) (let* ((kill-buffer-query-functions nil) (scratch-framep (cdr (assoc 'scratch-frame (frame-parameters)))) (vterm-buf (get-buffer "Scratch VTerm"))) (when (and scratch-framep vterm-buf) (kill-buffer vterm-buf) (remove-hook 'delete-frame-functions #'joe/kill-vterm-scratch) (remove-hook 'kill-buffer-hook #'joe/kill-frame)))) (defun joe/vterm-scratch () (interactive) (let* ((vterm-buf (vterm--internal #'switch-to-buffer "Scratch VTerm"))) (set-frame-parameter nil 'scratch-frame t) (with-current-buffer vterm-buf (setq mode-line-format nil) (setq popper-popup-status 'raised) (olivetti-mode) (add-hook 'delete-frame-functions #'joe/kill-vterm-scratch) (add-hook 'kill-buffer-hook #'joe/kill-frame)))) #+end_src *** COMMENT eat #+begin_src emacs-lisp (defun joe/eat-mode-hook () (setq-local global-hl-line-mode nil) (setq buffer-face-mode-face '(:family "Fira Code Nerd Font")) (buffer-face-mode)) (add-hook 'eat-mode-hook #'joe/eat-mode-hook) (evil-define-key 'insert eat-semi-char-mode-map (kbd "C-f") #'eat-self-input) (evil-set-initial-state 'eat-mode 'normal) (setq eat-kill-buffer-on-exit 't) (eat-eshell-mode) (eat-eshell-visual-command-mode) #+end_src ** Undo Fu #+begin_src emacs-lisp (require 'undo-fu) (undo-fu-session-global-mode +1) (setq undo-limit 6710886400) ;; 64mb. (setq undo-strong-limit 100663296) ;; 96mb. (setq undo-outer-limit 1006632960) ;; 960mb. (require 'undo-fu-session) (setq undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'")) (require 'vundo) #+end_src ** Which Key #+begin_src emacs-lisp (require 'which-key) (setq which-key-idle-delay 0.3) (which-key-mode) (when (boundp 'evil-mode) (which-key-add-keymap-based-replacements evil-normal-state-map "SPC f" '("Files") "SPC b" '("Buffers") "SPC B" '("Bookmarks") "SPC c" '("Consult") "SPC d" '("Dired") "SPC g" '("Git") "SPC m" '("Make") "SPC t" '("Tabs") "SPC p" '("Packages") "SPC s" '("Shell (vterm)") "SPC h" '("Help"))) #+end_src ** IDE Features *** REPLs #+begin_src emacs-lisp (when (boundp 'evil-mode) (evil-define-key 'insert comint-mode-map (kbd "C-n") 'comint-next-input) (evil-define-key 'insert comint-mode-map (kbd "C-p") 'comint-previous-input)) #+end_src *** Completion #+begin_src emacs-lisp ;; (evil-global-set-key 'insert (kbd "C-f") #'mono-complete-expand) (require 'mono-complete) (evil-global-set-key 'insert (kbd "C-f") #'mono-complete-expand-or-fallback) (setq mono-complete-backends (list 'dabbrev 'filesystem)) (setq mono-complete-backends (list 'capf 'dabbrev 'filesystem)) (setq mono-complete-backend-capf-complete-fn #'eglot-completion-at-point) (setq mono-complete-preview-delay 0.1) ;; (add-to-list 'face-remapping-alist '(mono-complete-preview-face . shadow)) (set-face-attribute 'mono-complete-preview-face nil :foreground 'unspecified :background 'unspecified :inherit 'shadow) (mono-complete-mode +1) (setq completion-in-region-function (lambda (&rest args) (apply (if vertico-mode #'consult-completion-in-region #'completion--in-region) args))) #+end_src *** COMMENT Eglot #+begin_src emacs-lisp (with-eval-after-load 'eglot ;; (flymake-mode -1) ;; Disable it completely until we find out how the hell we can toggle it (setq eglot-stay-out-of '(flymake)) ;; (setq eglot-stay-out-of '()) (setq eldoc-echo-area-use-multiline-p nil) (setq eldoc-idle-delay 0.15) ;; (setq eglot-stay-out-of '()) ;; (add-hook 'eglot-managed-mode-hook (lambda () (flymake-mode -1))) (evil-global-set-key 'normal (kbd "SPC li") 'eglot-inlay-hints-mode) (evil-global-set-key 'normal (kbd "SPC cs") 'consult-eglot-symbols) (evil-global-set-key 'normal (kbd "SPC cr") 'eglot-rename) (evil-global-set-key 'normal (kbd "SPC ca") 'eglot-code-actions)) ;; These don't work ;; (setq flymake-start-on-save-buffer nil) ;; (setq flymake-start-on-flymake-mode nil) #+end_src These help speed eglot up apparently [[https://www.reddit.com/r/emacs/comments/1b25904/is_there_anything_i_can_do_to_make_eglots/][Reddit Link]] #+begin_src emacs-lisp (fset #'jsonrpc--log-event #'ignore) (setq eglot-events-buffer-size 0) #+end_src *** LSP #+begin_src emacs-lisp (require 'lsp-mode) (setq lsp-enable-which-key-integration t) (setq lsp-headerline-breadcrumb-enable nil) (setq lsp-modeline-diagnostics-enable nil) (setq lsp-modeline-code-actions-enable nil) (setq lsp-lens-enable nil) (setq lsp-eldoc-enable-hover nil) (setq eldoc-echo-area-use-multiline-p 'truncate-sym-name-if-fit) (setq eldoc-idle-delay 0) (setq lsp-eldoc-render-all t) ;; (setq lsp-ui-doc-enable t) (defun joe/lsp-mode-hook () (flymake-mode -1)) (add-hook 'lsp-mode-hook #'joe/lsp-mode-hook) (define-key global-map (kbd "C-c l l") #'lsp) (define-key lsp-command-map (kbd "c") #'flymake-mode) (setq lsp-ui-doc-position 'at-point) (setq lsp-ui-doc-show-with-mouse nil) (require 'flycheck) (setq flycheck-check-syntax-automatically '()) (setq flycheck-keymap-prefix (kbd "C-c e")) (add-hook 'after-save-hook #'flycheck-clear) ;; All this changes because we are using eglot now (when (boundp 'evil-mode) (evil-global-set-key 'normal (kbd "M-e") #'flycheck-buffer) (evil-global-set-key 'normal (kbd "M-n") #'flycheck-next-error) (evil-global-set-key 'normal (kbd "M-p") #'flycheck-previous-error) (evil-global-set-key 'normal (kbd "M-d") #'lsp-ui-doc-glance) (evil-global-set-key 'normal (kbd "SPC cs") 'consult-lsp-symbols) (evil-global-set-key 'normal (kbd "SPC cd") 'joe/consult-lsp-diagnostics) (evil-global-set-key 'normal (kbd "SPC cf") 'consult-lsp-file-symbols)) ;; This function allows us to extract Rust's true function signature ;; (cl-defmethod lsp-clients-extract-signature-on-hover (contents (_server-id (eql rust-analyzer))) ;; (-let* (((&hash "value") contents) ;; (groups (--partition-by (s-blank? it) (s-lines (s-trim value)))) ;; (sig_group (if (s-equals? "```rust" (car (-third-item groups))) ;; (-third-item groups) ;; (car groups))) ;; (sig (--> sig_group ;; (--drop-while (s-equals? "```rust" it) it) ;; (--take-while (not (s-equals? "```" it)) it) ;; (--map (s-trim it) it) ;; (s-join " " it)))) ;; (lsp--render-element (concat "```rust\n" sig "\n```"))) ;; (require 'lsp-ui) #+end_src *** COMMENT Flycheck #+begin_src emacs-lisp ;; TODO Add wrapping to these functions (when (boundp 'evil-mode) (evil-global-set-key 'normal (kbd "M-e") #'flycheck-next-error) (evil-global-set-key 'normal (kbd "M-E") #'flycheck-previous-error) (evil-global-set-key 'normal (kbd "ee") 'flycheck-mode) (evil-global-set-key 'normal (kbd "el") #'flycheck-list-errors) (evil-global-set-key 'normal (kbd "ce") #'consult-lsp-diagnostics)) #+end_src *** Compilation #+begin_src emacs-lisp (setq compilation-auto-jump-to-first-error t) (defun joe/save-then-recompile () "Save the buffer before recompiling" (interactive) (when (buffer-file-name) (save-buffer)) (recompile)) (defun joe/save-if-file-ignore-args (&rest _) "Save the buffer before recompiling" (interactive) (joe/save-if-file)) (defun joe/save-if-file () "Save buffer only if it has a backing file" (interactive) (when (buffer-file-name) (save-buffer))) (add-hook 'evil-insert-state-exit-hook #'joe/save-if-file) ;; (add-hook 'after-change-functions #'joe/save-if-file-ignore-args) ;; (define-key global-map (kbd "C-x C-s) #'save-buffer) (define-key global-map (kbd "") #'joe/save-then-recompile) (define-key global-map (kbd "") #'compile) (defun joe/colorize-compilation-buffer () (ansi-color-apply-on-region compilation-filter-start (point))) (add-hook 'compilation-filter-hook 'joe/colorize-compilation-buffer) (defun joe/close-compilation-if-no-warn-err (buffer string) "Bury a compilation buffer if succeeded without warnings " (if (and (string-match "compilation" (buffer-name buffer)) (string-match "finished" string) (not (with-current-buffer (current-buffer) (search-forward "warning" nil t)))) (run-with-timer 1.5 nil (lambda () (and (eq (point) 1) (string-match "compilation" (buffer-name (current-buffer))) (popper-close-latest)))))) (add-hook 'compilation-finish-functions 'joe/close-compilation-if-no-warn-err) #+end_src *** Godot #+begin_src emacs-lisp (require 'gdscript-mode) #+end_src *** Indentation #+begin_src emacs-lisp (setq indent-bars-color '(highlight :face-bg t :blend 0.1)) (setq indent-bars-color-by-depth nil) (global-set-key (kbd "C-x x i") #'indent-bars-mode) #+end_src ** Game Dev #+begin_src emacs-lisp (add-to-list 'auto-mode-alist '("\\.vert\\'" . shader-mode)) (add-to-list 'auto-mode-alist '("\\.frag\\'" . shader-mode)) (add-to-list 'auto-mode-alist '("\\.glsl\\'" . shader-mode)) #+end_src ** AI #+begin_src emacs-lisp (setq gptel-default-mode #'org-mode) (setq gptel-model "gpt-4") (setq gptel-api-key (with-temp-buffer (insert-file-contents (expand-file-name "gptel-key" user-emacs-directory)) (buffer-string))) (add-hook 'gptel-post-response-functions #'font-lock-ensure) #+end_src This function was suggested by Karthink in order to fix an issue where gptel org-mode was jumping back up to the top anytime the buffer was saved. Keeping it around just in case. https://github.com/karthink/gptel/issues/199 #+begin_src emacs-lisp :tangle no (defun gptel--save-state () "Write the gptel state to the buffer. This saves chat metadata when writing the buffer to disk. To restore a chat session, turn on `gptel-mode' after opening the file." (pcase major-mode ('org-mode (org-with-wide-buffer (goto-char (point-min)) (when (org-at-heading-p) (org-open-line 1)) (org-entry-put (point-min) "GPTEL_MODEL" gptel-model) (org-entry-put (point-min) "GPTEL_BACKEND" (gptel-backend-name gptel-backend)) (unless (equal (default-value 'gptel-temperature) gptel-temperature) (org-entry-put (point-min) "GPTEL_TEMPERATURE" (number-to-string gptel-temperature))) (unless (string= (default-value 'gptel--system-message) gptel--system-message) (org-entry-put (point-min) "GPTEL_SYSTEM" gptel--system-message)) (when gptel-max-tokens (org-entry-put (point-min) "GPTEL_MAX_TOKENS" gptel-max-tokens)) ;; Save response boundaries (letrec ((write-bounds (lambda (attempts) (let* ((bounds (gptel--get-bounds)) (offset (caar bounds)) (offset-marker (set-marker (make-marker) offset))) (org-entry-put (point-min) "GPTEL_BOUNDS" (prin1-to-string (gptel--get-bounds))) (when (and (not (= (marker-position offset-marker) offset)) (> attempts 0)) (funcall write-bounds (1- attempts))))))) (funcall write-bounds 6)))) (_ (save-excursion (save-restriction (add-file-local-variable 'gptel-model gptel-model) (add-file-local-variable 'gptel--backend-name (gptel-backend-name gptel-backend)) (unless (equal (default-value 'gptel-temperature) gptel-temperature) (add-file-local-variable 'gptel-temperature gptel-temperature)) (unless (string= (default-value 'gptel--system-message) gptel--system-message) (add-file-local-variable 'gptel--system-message gptel--system-message)) (when gptel-max-tokens (add-file-local-variable 'gptel-max-tokens gptel-max-tokens)) (add-file-local-variable 'gptel--bounds (gptel--get-bounds))))))) #+end_src ** Programming Languages *** CSharp Omnisharp is very slow to start, so I want to start it manually, but not if the buffer already belongs to the current lsp workspace. This function checks if a newly opened buffer belongs to an LSP session and if it does, start lsp #+begin_src emacs-lisp (defun lsp-mode-server-exists-p+ (mode &optional all-workspaces) (when (buffer-file-name) ; Needed for lsp--matching-clients? (let ((major-mode mode)) (seq-intersection (lsp--filter-clients (-andfn #'lsp--supports-buffer? #'lsp--server-binary-present?)) (when-let ((workspaces (if all-workspaces (-flatten (hash-table-values (lsp-session-folder->servers (lsp-session)))) (gethash (lsp-workspace-root) (lsp-session-folder->servers (lsp-session)))))) (mapcar #'lsp--workspace-client workspaces)) #'equal)))) ;; (setq treesit-load-name-override-list '((csharp "libtree-sitter-c-sharp.so"))) #+end_src And we do the rest here, including a macro #+begin_src emacs-lisp (defun joe/csharp-mode-hook () (yas-minor-mode t) (defalias 'joe/serialized-private-public-getter (kmacro "^ W W Y o C-y ^ i p u b l i c SPC l W y i w P a SPC = > SPC B B ~")) (evil-set-register ?g (lambda nil "Unity/C#" (call-interactively 'joe/serialized-private-public-getter))) ;; (schmo/reapply-csharp-ts-mode-font-lock-settings) (electric-pair-local-mode t) (when (lsp-mode-server-exists-p+ 'csharp-mode) (lsp))) (add-hook 'csharp-mode-hook #'joe/csharp-mode-hook) #+end_src *** Odin #+begin_src emacs-lisp (with-eval-after-load 'lsp-mode (setq-default lsp-auto-guess-root t) ;; Helps find the ols.json file with Projectile or project.el (setq lsp-language-id-configuration (cons '(odin-mode . "odin") lsp-language-id-configuration)) (lsp-register-client (make-lsp-client :new-connection (lsp-stdio-connection "~/.local/bin/ols") ;; Adjust the path here :major-modes '(odin-mode) :server-id 'ols :multi-root t))) ;; Ensures lsp-mode sends "workspaceFolders" to the server (add-hook 'odin-mode-hook #'lsp) #+end_src *** C Design some basic functions for compiling. There's also a hook to close the popper window if there are no warnings or errors. It will check if we are still in the compilation buffer as well as whether the point hasn't been moved. This way, if I switched to another popper buffer, like vterm, it doesn't close it. #+begin_src emacs-lisp (require 'disaster) (setq c-default-style "bsd") (defun joe/c-mode-hook () (local-set-key (kbd "C-x c r") (defun joe/make-run () (interactive) (compile "make run"))) (local-set-key (kbd "C-x c c") (defun joe/make () (interactive) (compile "make"))) (electric-pair-local-mode t) (c-toggle-comment-style -1)) (add-hook 'c-mode-hook #'joe/c-mode-hook) #+end_src *** Python #+begin_src emacs-lisp (require 'python) (setq python-shell-interpreter "python") (setq python-shell-interpreter-args "--pylab") ;; What is this for? (setq python-shell-interpreter-args "") ;; What is this for? (define-key inferior-python-mode-map (kbd "C-n") #'comint-next-input) (define-key inferior-python-mode-map (kbd "C-p") #'comint-previous-input) #+end_src *** Rust #+begin_src emacs-lisp (setq rustic-lsp-setup-p nil) (require 'rustic) (require 'ob-rust) ;; Org-Babel ;; Disabling until we figure out how to get it working ;; (elpaca 'parsec) ;; Required by evcxr-mode ;; (elpaca ;; '(evcxr ;; :type git ;; :host github ;; :repo "serialdev/evcxr-mode")) (add-hook 'rust-mode-hook (lambda () ;; (evcxr-minor-mode) (electric-pair-local-mode))) ;; (with-eval-after-load 'rustic ;; ;; Don't autostart ;; ;; (define-key rustic-mode-map (kbd "") #'joe/save-then-recompile) ;; (setq lsp-rust-analyzer-server-display-inlay-hints t) ;; (setq lsp-rust-analyzer-display-lifetime-elision-hints-enable "always") ;; (setq lsp-rust-analyzer-display-chaining-hints t) ;; (setq lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names t) ;; (setq lsp-rust-analyzer-display-closure-return-type-hints t) ;; (setq lsp-rust-analyzer-display-parameter-hints t) ;; (setq lsp-rust-analyzer-display-reborrow-hints t) ;; (setq lsp-rust-analyzer-cargo-watch-command "clippy")) #+end_src *** OCaml #+begin_src emacs-lisp (require 'tuareg) (require 'dune) (require 'utop) ;; (require 'merlin) ;; (require 'merlin-eldoc) ;; Might be worth checking out, depeding on whether we stick with flycheck or not ;; (elpaca 'flycheck-ocaml) ;; Also check this out, see if it adds anything ;; (require 'ocp-indent) (defun opam-env () "Load the opam env to get the PATH variables so everything works" (interactive nil) (dolist (var (car (read-from-string (shell-command-to-string "opam config env --sexp")))) (setenv (car var) (cadr var)))) (setq opam-share (substring (shell-command-to-string "opam config var share 2> /dev/null") 0 -1)) (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share)) (add-to-list 'exec-path "/home/joe/.opam/default/bin/") #+end_src We won't use the LSP server but rather directly talk to Merlin, since I guess LSP just wraps Merlin and there's no need for a middle-man when it's already been implemented. #+begin_src emacs-lisp ;; (require 'utop) ;; Use the opam installed utop (setq utop-command "opam exec -- utop -emacs") (let ((opam-share (ignore-errors (car (process-lines "opam" "var" "share"))))) (when (and opam-share (file-directory-p opam-share)) ;; Register Merlin (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share)) (autoload 'merlin-mode "merlin" nil t nil) ;; Automatically start it in OCaml buffers (add-hook 'tuareg-mode-hook 'merlin-mode t) (add-hook 'caml-mode-hook 'merlin-mode t) ;; Use opam switch to lookup ocamlmerlin binary (setq merlin-command 'opam))) #+end_src *** Elisp #+begin_src emacs-lisp (global-set-key (kbd "C-x C-r") 'eval-region) (evil-define-key 'insert emacs-lisp-mode-map (kbd "C-j") 'eval-print-last-sexp) #+end_src *** Web #+begin_src emacs-lisp (require 'typescript-mode) (setq typescript-indent-level 2) #+end_src *** SQL #+begin_src emacs-lisp (defun joe/mark-sql-defun () "Mark the current SQL function definition." (interactive) (let ((beg (save-excursion (re-search-backward "^create or replace function [^(]+" nil t) (match-end 0))) (end (save-excursion (re-search-forward "\\(language\\)" nil t) (line-end-position)))) (when (and beg end) (goto-char beg) (set-mark end))) (evil-visual-line) (evil-visual-line)) (defun sql-send-pg-function () "Send the current PGSQL function to sql interactive." (interactive) (let ((beg (save-excursion (re-search-backward "^create or replace function [^(]+" nil t) (line-beginning-position))) (end (save-excursion (re-search-forward "\\(language\\)" nil t) (line-end-position)))) (save-excursion (when (and beg end) (goto-char beg) (set-mark end)) (sql-send-region beg end) (deactivate-mark)))) ;; This is required so that .dir-locals that read env files for credentials works (require 'dotenv) (defun joe/sql-mode-hook () (define-key sql-mode-map (kbd "C-M-h") #'joe/mark-sql-defun) (define-key sql-mode-map (kbd "C-M-x") #'sql-send-pg-function) (global-set-key (kbd "") #'sql-connect)) (add-hook 'sql-mode-hook #'joe/sql-mode-hook) (defun joe/sql-save-history-hook () (let ((lval 'sql-input-ring-file-name) (rval 'sql-product)) (if (symbol-value rval) (let ((filename (concat user-emacs-directory "sql/" (symbol-name (symbol-value rval)) "-history.sql"))) (set (make-local-variable lval) filename)) (error (format "SQL history will not be saved because %s is nil" (symbol-name rval)))))) (add-hook 'sql-interactive-mode-hook 'joe/sql-save-history-hook) (defun joe/sql-login-hook () "Custom SQL log-in behaviours. See `sql-login-hook'." ;; n.b. If you are looking for a response and need to parse the ;; response, use `sql-redirect-value' instead of `comint-send-string'. (when (eq sql-product 'postgres) (let ((proc (get-buffer-process (current-buffer)))) ;; Output each query before executing it. (n.b. this also avoids ;; the psql prompt breaking the alignment of query results.) (comint-send-string proc "\\set ECHO queries\n")))) (add-hook 'sql-login-hook 'joe/sql-login-hook) #+end_src *** COMMENT Haskell #+begin_src emacs-lisp (require 'haskell-mode) (setq haskell-interactive-popup-errors nil) (evil-define-key 'insert haskell-interactive-mode-map (kbd "C-n") #'haskell-interactive-mode-history-next) (evil-define-key 'insert haskell-interactive-mode-map (kbd "C-p") #'haskell-interactive-mode-history-previous) #+end_src *** COMMENT Clojure #+begin_src emacs-lisp (require 'clojure-mode) (require 'cider) (setq cider-show-error-buffer 'only-in-repl) #+end_src *** COMMENT FSharp #+begin_src emacs-lisp (require 'fsharp-mode) ;; (elpaca 'eglot-fsharp) #+end_src *** COMMENT Go #+begin_src emacs-lisp (require 'go-mode) ;; (elpaca 'go-imports) #+end_src *** Other #+begin_src emacs-lisp (require 'json-mode) (require 'markdown-mode) #+end_src ** Debugging *** COMMENT DAP #+begin_src emacs-lisp (require 'dap-mode) ;; (setq dap-auto-configure-features '(locals breakpoints expressions tooltip)) (require 'dap-cpptools) (dap-cpptools-setup) (add-hook 'dap-stopped-hook (lambda (arg) (call-interactively #'dap-hydra))) (setq dap-cpptools-extension-version "1.12.1") (setq dap-default-terminal-kind "integrated") (dap-auto-configure-mode +1) (dap-register-debug-template "Rust::CppTools Run Configuration" (list :type "cppdbg" :request "launch" :name "Rust::Run" :MIMode "gdb" :miDebuggerPath "rust-gdb" :environment [] :program "${workspaceFolder}/target/debug/kanban-tui" :cwd "${workspaceFolder}" :console "external" :dap-compilation "cargo build" :dap-compilation-dir "${workspaceFolder}")) #+end_src *** COMMENT GDB/GUD #+begin_src emacs-lisp (setq gdb-many-windows t) (setq gud-tooltip-dereference t) (defun joe/gdb () (interactive) (gdb (format "%s -i=mi" (file-name-sans-extension buffer-file-name)))) (defun hook-gud-mode () (define-key gud-global-map (kbd "C-c") #'gud-cont) (define-key gud-global-map (kbd "C-r") #'gud-run)) (add-hook 'gud-mode-hook #'hook-gud-mode) #+end_src *** dape #+begin_src emacs-lisp (setq dape-buffer-window-arrangement 'right) (defun joe/dape-toggle-breakpoint-and-rerun () (interactive) (unless (dape--breakpoints-at-point) (dape-breakpoint-toggle)) (dape-restart)) (define-key global-map (kbd "") #'joe/dape-toggle-breakpoint-and-rerun) #+end_src Apparently this helps with the performance of dape #+begin_src emacs-lisp (setq read-process-output-max (* 1024 1024)) ;; 1mb #+end_src ** org-mode *** General #+begin_src emacs-lisp (setq org-todo-keywords '((sequence "TODO" "IN-PROGRESS" "|" "DONE" "BACKLOG"))) (setq org-directory "~/Notes/") (evil-define-key 'normal calendar-mode-map (kbd "RET") #'org-calendar-select) ;; This is for org-clock-report (setq org-duration-format 'h:mm) #+end_src There's an issue when I invoke ~org-clock-in~ where it seems to search all my ~org-agenda-files~ for any open clocks. This is a bit annoying as it opens all these files whenever I clock in, making clocking in slow. This fixes it but then existing clocks don't get resolved. Here's a github [[https://github.com/doomemacs/doomemacs/issues/5317][issue]] describing a little bit about the problem; #+begin_src emacs-lisp (with-eval-after-load 'org-clock (defun joe/org-files-list () "A version of `org-files-list' that doesn't open all `org-agenda-files'" (let ((files '())) (dolist (buf (buffer-list)) (with-current-buffer buf (when (and (derived-mode-p 'org-mode) (buffer-file-name)) (cl-pushnew (expand-file-name (buffer-file-name)) files :test #'equal)))) files)) (defun org-resolve-clocks (&optional only-dangling-p prompt-fn last-valid) "Resolve all currently open Org clocks. If `only-dangling-p' is non-nil, only ask to resolve dangling \(i.e., not currently open and valid) clocks." (interactive "P") (unless org-clock-resolving-clocks (let ((org-clock-resolving-clocks t)) (dolist (file (joe/org-files-list)) (let ((clocks (org-find-open-clocks file))) (dolist (clock clocks) (let ((dangling (or (not (org-clock-is-active)) (/= (car clock) org-clock-marker)))) (if (or (not only-dangling-p) dangling) (org-clock-resolve clock (or prompt-fn (lambda (clock) (format "Dangling clock started %d mins ago" (floor (org-time-convert-to-integer (time-since (cdr clock))) 60)))) (or last-valid (cdr clock)))))))))))) #+end_src In this modified function, =dolist= processes the list returned by =buffer-list= instead of =org-files-list=. For each buffer in the list, we switch to it with =with-current-buffer= and then test if its major mode is =org-mode=. If it is, we process the clocks in that buffer as before. We find open clocks in the current buffer's associated file using =(buffer-file-name buffer)=. It's assumed that all Org mode buffers have associated files. #+end_src *** Visuals #+begin_src emacs-lisp (setq org-blank-before-new-entry '((heading . nil) (plain-list-item . nil))) (setq org-cycle-separator-lines 1) (setq org-hide-emphasis-markers t) (require 'org-bullets) (require 'org-fancy-priorities) ;; (setq org-fancy-priorities-list '("πŸ…°" "πŸ…±" "πŸ…²" "πŸ…³" "πŸ…΄")) ;; (setq org-fancy-priorities-list '("⚑" "⬆" "⬇")) ;; (setq org-fancy-priorities-list '("⚑" "⬆" "⬇")) (setq org-fancy-priorities-list '((?D . "πŸŒƒ") (?C . "πŸŒ‡") (?B . "β˜€οΈ") (?A . "πŸŒ„"))) (defun joe/org-hook () (org-fancy-priorities-mode) (visual-line-mode) (org-bullets-mode) (org-indent-mode) (olivetti-mode)) (add-hook 'org-mode-hook 'joe/org-hook) #+end_src *** org-agenda #+begin_src emacs-lisp (setq org-agenda-files '("~/Notes/Schedule.org")) (setq org-agenda-time-grid '((daily today require-timed) (700 900 1200 1700 1900 2030 2300) " ═════ " " ════════════════════ ")) (define-key global-map (kbd "C-c a") #'org-agenda) (setq org-agenda-custom-commands '(("n" "Agenda and all TODOs" ((agenda "") (alltodo ""))) ("d" "Daily view" ((agenda "" ((org-agenda-overriding-header "Daily Agenda") (org-agenda-span 1) (org-agenda-time-grid nil) ;; (org-agenda-overriding-columns-format "%20ITEM %DEADLINE") (org-agenda-view-columns-initially nil) (org-agenda-block-separator ?═))) (tags-todo "work" ((org-agenda-overriding-header "πŸ–₯οΈπŸ’Έ Work\n") (org-agenda-block-separator ?═))) (tags-todo "gamingpads" ((org-agenda-overriding-header "πŸ–₯οΈπŸ“šοΈπŸ•ΉοΈ Gaming Pads\n") (org-agenda-block-separator ?⎯))) (tags-todo "admin" ((org-agenda-overriding-header "✍🏻 Admin\n") (org-agenda-block-separator ?⎯))) (tags-todo "sideprojects" ((org-agenda-overriding-header "πŸ–₯οΈπŸ§”πŸ» Side Projects\n") (org-agenda-block-separator ?⎯))) )))) #+end_src *** org-capture #+begin_src emacs-lisp (defun joe/capture-leetcode-newfile () (let* ((title (read-string "Title: ")) (title (string-replace " " "-" title)) (title (string-replace "." "" title))) (expand-file-name (format "%s.org" title) "~/Development/coding-challenges/leetcode/"))) (defun joe/capture-leetcode-template () (mapconcat 'identity '(":PROPERTIES:" ":DIFFICULTY: medium" ":END:" "" "#+TITLE: Leetcode %?" "#+AUTHOR: Joseph Ferano" "#+STARTUP: show2levels" "#+TAGS: " "" "* Python Solution" ":PROPERTIES:" ":CompletionTime: " ":TimeComplexity: O()" ":MemoryComplexity: O()" ":END:" ":LOGBOOK:" ":END:" "" "#+begin_src python" "#+end_src" "** Review" "** Techniques & Patterns") "\n")) (defun my-org-capture-today () (concat "<" (format-time-string "%Y-%m-%d %a") ">")) (defun joe/capture-daily () (concat "** %^t\n" "*** Schedule\n" "*** Resources\n" "*** Notes")) (setq org-capture-templates `( ("e" "Emacs Improvement" entry (file+headline "Emacs.org" "Quick Wins") "** TODO ") ("l" "Leetcode Solution" plain (function ,(lambda () (find-file (joe/capture-leetcode-newfile)))) #'joe/capture-leetcode-template) ("d" "Daily Entry" entry (file "Daily.org") #'joe/capture-daily) ("b" "New Book To Read" entry (file+headline "Books.org" "To Read") "** %^{Book Title}%^{AUTHOR}p" :prepend t))) (define-key global-map (kbd "C-c c") #'org-capture) #+end_src *** org-babel #+begin_src emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (makefile . t) (ocaml . t) (python . t) (C . t) (haskell . t) ;; (rust . t) (shell . t))) (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")) (add-to-list 'org-structure-template-alist '("ml" . "src ocaml")) (add-to-list 'org-structure-template-alist '("rs" . "src rust")) (add-to-list 'org-structure-template-alist '("py" . "src python")) (add-to-list 'org-structure-template-alist '("hs" . "src haskell")) (add-to-list 'org-structure-template-alist '("sh" . "src shell")) (add-to-list 'org-structure-template-alist '("cc" . "src C :includes stdio.h stdlib.h")) (setq org-edit-src-content-indentation 0) (setq org-src-window-setup 'current-window) #+end_src org-tempo provides the ability to insert block snippets in the form of =