#+PROPERTY: header-args :results silent #+PROPERTY: header-args:emacs-lisp :lexical t :tangle ~/.emacs.d/config/base.el This is a literate init file holding my Emacs configuration. It is initially loaded by a [[file:init.el][bootstrap file]] that lives at ~/.emacs.d/init.el. * Prelude Enables lexical binding for everything in init.el: #+BEGIN_SRC emacs-lisp ;;; -*- lexical-binding: t; -*- #+END_SRC ** Garbage collection Some GC tweaks [[https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly]["borrowed" from Doom emacs]]. Turn off GC during init and restore it afterwards: #+BEGIN_SRC emacs-lisp (setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.6) (add-hook 'emacs-startup-hook (lambda () (setq gc-cons-threshold 100000000 gc-cons-percentage 0.1))) #+END_SRC Also suppress GC for 1 second after the minibuffer is active to avoid stuttering autocompletion and other GC hangups: #+BEGIN_SRC emacs-lisp (defun defer-garbage-collection () (setq gc-cons-threshold most-positive-fixnum)) (defun restore-garbage-collection () (run-at-time 1 nil (lambda () (setq gc-cons-threshold 100000000)))) (add-hook 'minibuffer-setup-hook #'defer-garbage-collection) (add-hook 'minibuffer-exit-hook #'restore-garbage-collection) #+END_SRC ** Unset file-handler-alist during initialization Another optimization from [[https://github.com/hlissner/doom-emacs/blob/develop/docs/faq.org#how-does-doom-start-up-so-quickly][Doom Emacs]]. #+BEGIN_SRC emacs-lisp (defvar file-name-handler-alist-backup file-name-handler-alist) (setq file-name-handler-alist nil) (add-hook 'emacs-startup-hook (lambda () (setq file-name-handler-alist file-name-handler-alist-backup))) #+END_SRC ** Variables #+BEGIN_SRC emacs-lisp (setq vc-follow-symlinks t frame-resize-pixelwise t tab-always-indent 'complete enable-recursive-minibuffers t read-process-output-max (* 1024 1024) bookmark-save-flag 1) (setq-default indent-tabs-mode nil) #+END_SRC * Default directory #+BEGIN_SRC emacs-lisp (cd "~") #+END_SRC * Packages Load [[https://github.com/raxod502/straight.el][straight.el]] to manage package installation: #+BEGIN_SRC emacs-lisp (defvar bootstrap-version) (unless (boundp 'bootstrapping-init) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))) #+END_SRC `use-package` is a macro that simplifies installing and loading packages. #+BEGIN_SRC emacs-lisp (straight-use-package 'use-package) (setq straight-use-package-by-default t) #+END_SRC ** Utility functions #+BEGIN_SRC emacs-lisp (defun find-library-readme (library) (interactive (list (read-library-name))) (let* ((dir (file-name-directory (file-truename (find-library-name library)))) (doc (car (directory-files dir t "\\(readme\\|README\\)\\..*")))) (if (not doc) (error "No README found") (find-file doc) (when (eq major-mode 'markdown-mode) (markdown-view-mode))))) #+END_SRC * Benchmarking `benchmark-init` does what it says on the box. This sets it up to benchmark my init time and then disable benchmarking after init completes. #+BEGIN_SRC emacs-lisp (use-package benchmark-init :config (add-hook 'after-init-hook 'benchmark-init/deactivate)) #+END_SRC * General Better keybinding. #+BEGIN_SRC emacs-lisp (use-package general :init (setq general-override-states '(insert emacs hybrid normal visual motion operator replace))) #+END_SRC * Which-key `which-key` makes keybindings discoverable. #+BEGIN_SRC emacs-lisp (use-package which-key :config (which-key-mode)) #+END_SRC This function defines a prefix group for `which-key` so that it doesn't display `prefix`. #+BEGIN_SRC emacs-lisp (defun jdormit/define-prefix (binding name) (which-key-add-key-based-replacements (concat leader " " binding) name) (which-key-add-key-based-replacements (concat "," " " binding) name)) #+END_SRC * Evil Mode Because I like modal editing and dislike RSI. #+BEGIN_SRC emacs-lisp (use-package evil :init (setq evil-want-keybinding nil) :config (evil-mode 1)) #+END_SRC Make undo not undo paragraphs at a time: #+BEGIN_SRC emacs-lisp (setq evil-want-fine-undo t) #+END_SRC Add a convenience function for making buffer-local ex-commands: #+BEGIN_SRC emacs-lisp (defun evil-ex-define-local-cmd (cmd function) (set (make-local-variable 'evil-ex-commands) (copy-tree evil-ex-commands)) (evil-ex-define-cmd cmd function)) #+END_SRC ** evil-collection A collection of evil bindings for various modes #+BEGIN_SRC emacs-lisp (use-package evil-collection :after (evil) :hook ((after-init . evil-collection-init)) :config (setq evil-collection-company-use-tng nil)) #+END_SRC ** leader key Use the spacebar as a leader key in evil-mode's normal state and in various other modes: #+BEGIN_SRC emacs-lisp (defconst leader "SPC") (general-define-key :keymaps 'override :states '(normal visual motion) "SPC" nil) (general-create-definer leader-def-key :keymaps 'override :states '(normal visual motion) :prefix leader :prefix-map 'leader-map) #+END_SRC #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "?" "help") (leader-def-key "?" help-map) #+END_SRC ** evil-snipe #+BEGIN_SRC emacs-lisp (use-package evil-snipe :after (evil) :config (evil-snipe-mode 1) (evil-snipe-override-mode 1) (add-to-list 'evil-snipe-disabled-modes 'dired-mode) (add-to-list 'evil-snipe-disabled-modes 'structlog-mode) (with-eval-after-load 'magit (add-hook 'magit-mode-hook 'turn-off-evil-snipe-override-mode)) (with-eval-after-load 'prodigy (add-hook 'prodigy-mode-hook 'turn-off-evil-snipe-mode)) (with-eval-after-load 'pass (add-hook 'pass-mode-hook 'turn-off-evil-snipe-mode))) #+END_SRC ** evil-commentary Adds Evil commands to comment out lines of code: #+BEGIN_SRC emacs-lisp (use-package evil-commentary :after (evil) :hook ((prog-mode . evil-commentary-mode))) #+END_SRC ** Additional keybindings #+BEGIN_SRC emacs-lisp (general-def 'normal "zM" #'hs-hide-level :keymaps 'override) (general-def 'normal "z=" #'text-scale-increase :keymaps 'override) (general-def 'normal "z-" #'text-scale-decrease :keymaps 'override) (general-def 'normal "z0" #'text-scale-adjust :keymaps 'override) (general-def 'normal view-mode-map "0" nil :keymaps 'override) (general-def 'normal prodigy-view-mode-map "0" nil :keymaps 'override) (general-def 'normal messages-buffer-mode-map "SPC" leader-map :keymaps 'override) #+END_SRC * Hydra [[https://github.com/abo-abo/hydra][Hydra]]s are convenient keybinding menus. #+BEGIN_SRC emacs-lisp (use-package hydra :defer t) #+END_SRC * Syncthing I put lots of stuff in Syncthing, but the actual folder location differs on my different computers. This function resolves to the Syncthing directory: #+BEGIN_SRC emacs-lisp (defcustom syncthing-path (expand-file-name "~/Sync") "The absolute path to the Syncthing directory" :type 'directory) (defun syncthing-directory (&optional path) (f-join syncthing-path (s-chop-prefix (f-path-separator) (or path "")))) #+END_SRC * Emacs Lisp ** Packages Some helpful ELisp packages: - [[https://github.com/kiwanami/emacs-deferred][deferred]] is an async API library - [[https://github.com/magnars/s.el][s.el]] is a string manipulation library - [[https://github.com/magnars/dash.el][dash.el]] is a list manipulation library - [[https://github.com/rejeep/f.el][f.el]] is a file manipulation library - [[https://github.com/tkf/emacs-request][request]] is an HTTP library #+BEGIN_SRC emacs-lisp (use-package deferred :commands (deferred:next deferred:nextc deferred:error deferred:cancel deferred:watch deferred:wait deferred:$ deferred:loop deferred:parallel deferred:earlier deferred:call deferred:apply deferred:process deferred:process-buffer deferred:wait-idle deferred:url-retrieve deferred:url-get deferred:url-post deferred:new deferred:succeed deferred:fail deferred:callback deferred:callback-post deferred:errorback deferred:errorback-post deferred:try deferred:timeout deferred:process)) (use-package s :defer t :init (add-hook 'emacs-startup-hook (lambda () (require 's))) :config (defun camel-case (&optional arg) (interactive "P") (let* ((bounds (bounds-of-thing-at-point 'symbol)) (word (buffer-substring (car bounds) (cdr bounds))) (camel (if arg (s-upper-camel-case word) (s-lower-camel-case word)))) (delete-region (car bounds) (cdr bounds)) (insert camel)))) (use-package dash :defer t :init (add-hook 'emacs-startup-hook (lambda () (require 'dash)))) (use-package dash-functional :defer t :init (add-hook 'emacs-startup-hook (lambda () (require 'dash-functional)))) (use-package f :defer t :init (add-hook 'emacs-startup-hook (lambda () (require 'f))) (autoload 'f-join "f")) (use-package request :commands (request request-deferred)) (use-package ht :defer t) #+END_SRC ** Editing Elisp #+BEGIN_SRC emacs-lisp (general-def '(normal motion) emacs-lisp-mode-map "C-c C-c" #'eval-defun :keymaps 'override) (general-def '(normal motion insert) lisp-interaction-mode-map "C-c C-c" #'eval-print-last-sexp :keymaps 'override) (add-hook 'ielm-mode-hook 'smartparens-strict-mode) #+END_SRC ** Load path For machine or user specific libraries: #+BEGIN_SRC emacs-lisp (add-to-list 'load-path (expand-file-name "~/site-lisp")) #+END_SRC And for global ones: #+BEGIN_SRC emacs-lisp (add-to-list 'load-path "/usr/local/share/emacs/site-lisp") #+END_SRC ** Utilities Reading a file as a string: #+BEGIN_SRC emacs-lisp (defun read-file (path) "Returns the contents of the file as a string" (with-temp-buffer (insert-file-contents path) (buffer-string))) #+END_SRC Opening a file as sudo: #+BEGIN_SRC emacs-lisp (defun sudo-find-file (file-name) "Like find file, but opens the file as root." (interactive "F") (let ((tramp-file-name (concat "/sudo::" (expand-file-name file-name)))) (find-file tramp-file-name))) #+END_SRC Recursive =assoc= for nested alists: #+BEGIN_SRC emacs-lisp (defun assoc-recursive (alist &rest keys) "Recursively find KEYs in ALIST." (while keys (setq alist (cdr (assoc (pop keys) alist)))) alist) #+END_SRC Format a millis timestamp into human-readable form: #+BEGIN_SRC emacs-lisp (defun format-epoch-millis (millis) (interactive "nTimestamp: ") (message (format-time-string "%F %r" (/ millis 1000)))) #+END_SRC The same but for seconds: #+BEGIN_SRC emacs-lisp (defun format-epoch-seconds (seconds) (interactive "nTimestamp: ") (message (format-time-string "%F %r" seconds))) #+END_SRC Checking if a buffer contains a string: #+BEGIN_SRC emacs-lisp (defun buffer-contains-substring (string) (save-excursion (save-match-data (goto-char (point-min)) (search-forward string nil t)))) #+END_SRC Pretty-print JSON: #+BEGIN_SRC emacs-lisp (defun pprint-json (raw-json) (with-temp-buffer (insert raw-json) (json-pretty-print (point-min) (point-max)) (buffer-substring (point-min) (point-max)))) #+END_SRC Load environment variables into Emacs from a shell script: #+BEGIN_SRC emacs-lisp (cl-defun extract-vars-from-env-file (file &key dir) "Extracts an alist of variable name to value from a bash script that exports environment variables." (let ((file (expand-file-name file)) (var-re "\\(.+?\\)=\\(.+\\)$") (env '())) (with-temp-buffer (cd (expand-file-name (or dir (file-name-directory file)))) (insert (shell-command-to-string (concat "source " (shell-quote-argument file) " > /dev/null && env"))) (goto-char (point-min)) (save-match-data (while (re-search-forward var-re nil t) (push (cons (match-string 1) (match-string 2)) env)))) env)) (defun source-env-file (file) (interactive "fFile: \n") (let ((env (extract-vars-from-env-file file))) (dolist (binding env) (setenv (car binding) (cdr binding))))) (cl-defmacro with-env-from-file (file &rest body) (declare (indent 1)) (let ((env-var (make-symbol "the-env")) (path-var (make-symbol "the-path"))) `(let* ((,env-var (extract-vars-from-env-file ,file)) (,path-var (assoc "PATH" ,env-var)) (exec-path (if ,path-var (append (split-string (cdr ,path-var) ":") exec-path) exec-path)) (process-environment (append (mapcar (lambda (elt) (format "%s=%s" (car elt) (cdr elt))) ,env-var) process-environment))) ,@body))) (cl-defun call-with-env-from-file (file callback &key dir) (let* ((env (extract-vars-from-env-file file :dir dir)) (path (assoc "PATH" env)) (exec-path (if path (append (split-string (cdr path) ":") exec-path) exec-path)) (process-environment (append (mapcar (lambda (elt) (format "%s=%s" (car elt) (cdr elt))) env) process-environment))) (funcall callback))) (defmacro with-env (env &rest body) (declare (indent 1)) `(let* ((process-environment (append (mapcar (lambda (elt) (format "%s=%s" (car elt) (cdr elt))) ,env) process-environment))) ,@body)) #+END_SRC Convenience macro to run some code in a particular default-directory: #+BEGIN_SRC emacs-lisp (defmacro with-default-directory (dir &rest body) (declare (indent 1)) `(let ((default-directory ,dir)) ,@body)) #+END_SRC #+BEGIN_SRC emacs-lisp (defun random-alnum (&optional n) (let* ((n-chars (or n 1)) (alnum "abcdefghijklmnopqrstuvwxyz0123456789") (result "")) (dotimes (_ n-chars result) (let ((i (% (abs (random)) (length alnum)))) (setq result (concat result (substring alnum i (1+ i)))))))) #+END_SRC A handy function to colorize a buffer with ANSI escape characters in it: #+BEGIN_SRC emacs-lisp (defun ansi-color (&optional begin end) (interactive) (let ((begin (or begin (point-min))) (end (or end (point-max))) (inhibit-read-only t)) (ansi-color-apply-on-region begin end))) #+END_SRC ** Persisting variables between session The idea behind this is pretty simple - variables get persisted in ~/.emacs.d/ as a plist of (variable-name variable-value). #+BEGIN_SRC emacs-lisp (defvar persisted-vars-file "~/.emacs.d/persisted-vars") #+END_SRC This function retrieves the plist of persisted variables or nil if it doesn't exist: #+BEGIN_SRC emacs-lisp (defun get-persisted-plist () (let ((file (expand-file-name persisted-vars-file))) (when (file-exists-p file) (let ((vars-plist-str (read-file file))) (unless (string= vars-plist-str "") (car (read-from-string vars-plist-str))))))) #+END_SRC This function retrieves a persisted variable: #+BEGIN_SRC emacs-lisp (defun get-persisted-var (var-name) "Retrieves the value of persisted variable `var-name`, or nil if not found" (let ((vars-plist (get-persisted-plist))) (when vars-plist (plist-get vars-plist var-name)))) #+END_SRC And this function persists a variable: #+BEGIN_SRC emacs-lisp (defun persist-variable (var-name value) (let ((file (expand-file-name persisted-vars-file)) (vars-plist (get-persisted-plist))) (if vars-plist (progn (plist-put vars-plist var-name value) (write-region (prin1-to-string vars-plist) nil file)) (let ((vars-plist `(,var-name ,value))) (write-region (prin1-to-string vars-plist) nil file))))) #+END_SRC ** Process handling Some utilities for calling out to other processes. #+BEGIN_SRC emacs-lisp (defun make-process-sentinel (success err) "Makes a process sentinel that calls `success` on success and `err` on error" (lambda (proc event) (cond ((string-match-p "finished" event) (funcall success)) (t (funcall err))))) (defun make-success-err-msg-sentinel (buf success-msg err-msg &optional kill-on-err) (make-process-sentinel (lambda () (message success-msg) (kill-buffer buf)) (lambda () (message err-msg) (when kill-on-err (kill-buffer buf))))) #+END_SRC A function to call a process passing some string as stdin and returning the process output: #+BEGIN_SRC emacs-lisp (defun make-process-fn (program &rest args) "Returns a function that, when called, call `program` with arguments `args`, passing the function argument as stdin" (lambda (&optional input) (with-temp-buffer (if input (progn (insert input) (apply #'call-process-region (point-min) (point-max) program t t nil args)) (apply #'call-process program nil t nil args)) (buffer-substring-no-properties (point-min) (point-max))))) #+END_SRC The same function but for commands that need to run in a shell: #+BEGIN_SRC emacs-lisp (defun make-shell-fn (program &rest args) "Returns a function that, when called, calls `program` in a shell with arguments `args`, passing the function argument as stdin" (lambda (&optional input) (let ((cmd (combine-and-quote-strings `(,program ,@args)))) (with-temp-buffer (if input (progn (insert input) (call-shell-region (point-min) (point-max) cmd t t)) (call-process-shell-command cmd nil t)) (buffer-substring-no-properties (point-min) (point-max)))))) #+END_SRC Running a shell command as sudo: #+BEGIN_SRC emacs-lisp (defun sudo-shell-command (command) (with-temp-buffer (cd "/sudo::/") (shell-command command))) #+END_SRC ** Buffer switch hooks I want to be able to run code whenever I switch to a buffer running certain modes. The code to run is stored in a alist mapping mode names to lists of code to run (stored as a raw data structure to be eval'ed): #+BEGIN_SRC emacs-lisp (defvar buffer-mode-hooks '()) #+END_SRC To add a new hook, push the code to run onto the correct list: #+BEGIN_SRC emacs-lisp (defun add-buffer-mode-hook (mode fn) (if-let ((existing-entry (assoc mode buffer-mode-hooks))) (push fn (cdr existing-entry)) (let ((new-entry `(,mode . (,fn)))) (push new-entry buffer-mode-hooks)))) #+END_SRC Whenever the buffer changes, look up the major-mode to see if there is any code to run: #+BEGIN_SRC emacs-lisp (defun run-buffer-mode-hooks () (when-let ((entry (assoc major-mode buffer-mode-hooks))) (dolist (fn (cdr entry)) (funcall fn)))) (add-hook 'buffer-list-update-hook #'run-buffer-mode-hooks) #+END_SRC ** Aliases #+BEGIN_SRC emacs-lisp (defalias 'doc 'describe-symbol) #+END_SRC ** Miscellaneous #+BEGIN_SRC emacs-lisp (setq warning-suppress-types '((undo discard-info))) #+END_SRC * Org Mode Notes, agenda, calendar, blogging, journaling, etc. Loaded early to [[https://github.com/raxod502/straight.el#the-wrong-version-of-my-package-was-loaded][avoid a version clash]]. First, a function to get my notes directory: #+BEGIN_SRC emacs-lisp (defun org-directory (&optional path) "Returns the directory for my org notes, appends PATH if given" (f-join (expand-file-name "~/org") (s-chop-prefix (f-path-separator) (or path "")))) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package org :straight org-plus-contrib :commands (org-element-map org-agenda org-capture) :mode (("\\.org\\'" . org-mode)) :init (jdormit/define-prefix "o" "org") (leader-def-key "oa" 'org-agenda) (leader-def-key "oc" 'org-capture) :config ;; Disable expensive hooks when building agenda buffer (advice-add 'org-get-agenda-file-buffer :around (lambda (oldfn &rest args) (let ((find-file-hook '()) (org-mode-hook '()) (after-change-major-mode-hook '())) (apply oldfn args)))) (defun agenda-files (&optional file) (let ((agenda-dir (org-directory))) (if file (concat (file-name-as-directory agenda-dir) file) agenda-dir))) (setq org-src-fontify-natively t org-ellipsis " ▼" org-directory (org-directory) org-link-elisp-confirm-function 'y-or-n-p org-startup-with-inline-images t org-return-follows-link t org-log-done 'time org-file-apps '(("log" . emacs) (auto-mode . emacs) (directory . emacs) ("\\.pdf\\'" . emacs) ("\\.mm\\'" . default) ("\\.x?html?\\'" . default)) org-agenda-files (list (agenda-files)) org-capture-templates `(("L" "Lola task" entry (file+headline ,(agenda-files "todo.org") "Lola") "* TODO %i%?") ("p" "Personal task" entry (file+headline ,(agenda-files "todo.org") "Personal") "* TODO %i%?") ("n" "Note" entry (file ,(agenda-files "notes.org")) "* %^{Description}\n%i%?") ("l" "Log" entry (file ,(agenda-files "log.org")) "* %<%Y-%m-%d %H:%M:%S>%?")) org-refile-use-outline-path 'file org-refile-targets `((org-agenda-files :level . 0) (,(agenda-files "notes.org") :level . 1) (,(agenda-files "todo.org") :level . 1)) org-todo-keywords '((sequence "TODO(t)" "IN PROGRESS(i)" "BLOCKED(b)" "|" "DONE(d)" "CANCELLED(c)")) org-agenda-todo-ignore-scheduled 'future org-agenda-tags-todo-honor-ignore-options t org-agenda-span 'day org-agenda-custom-commands '(("L" "Lola" ((tags-todo "@lola"))) ("t" "TODOs" ((agenda) (alltodo))))) (defun setup-org-mode () (require 'org-attach) (org-display-inline-images nil t) (org-redisplay-inline-images) (auto-fill-mode)) (add-hook 'org-mode-hook #'setup-org-mode) :general (normal org-mode-map "T" #'org-insert-todo-heading "K" #'org-move-subtree-up "J" #'org-move-subtree-down "" #'org-return "TAB" #'org-cycle "SPC" leader-map "gn" #'org-next-link "gp" #'org-previous-link) (org-mode-map "C-c e" #'org-preview-latex-fragment) (org-mode-map "C-c C-l" #'org-insert-link) ("C-c l" #'org-store-link)) #+END_SRC ** Evil-Org Set up evil keybindings for Org mode: #+BEGIN_SRC emacs-lisp (use-package evil-org :after (evil org) :hook ((org-mode . evil-org-mode) (org-agenda-mode . evil-org-mode)) :config (add-hook 'evil-org-mode-hook (lambda () (evil-org-set-key-theme '(textobjects insert navigation additional shift todo)) (general-def 'insert org-mode-map [backspace] 'org-delete-backward-char))) (require 'evil-org-agenda) (evil-org-agenda-set-keys) (general-def 'motion org-agenda-mode-map "SPC" leader-map) (general-def 'motion org-agenda-mode-map "gs" 'org-save-all-org-buffers) (general-def '(normal motion) evil-org-mode-map "C-S-j" nil "C-S-k" nil "C-S-h" nil "C-S-l" nil)) #+END_SRC ** Org-mode hydra A helpful agenda-mode hydra: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'org-agenda (defhydra hydra-org-agenda (:pre (setq which-key-inhibit t) :post (setq which-key-inhibit nil) :hint none) " Org agenda (_q_uit) ^Clock^ ^Visit entry^ ^Date^ ^Other^ ^-----^---- ^-----------^------------ ^----^----------- ^-----^--------- _ci_ in _SPC_ in other window _ds_ schedule _gr_ reload _co_ out _TAB_ & go to location _dd_ set deadline _._ go to today _cq_ cancel _RET_ & del other windows _dt_ timestamp _gd_ go to date _cj_ jump _o_ link _+_ do later ^^ ^^ ^^ _-_ do earlier ^^ ^^ ^^ ^^ ^^ ^View^ ^Filter^ ^Headline^ ^Toggle mode^ ^----^-------- ^------^--------------- ^--------^------- ^-----------^---- _vd_ day _ft_ by tag _ht_ set status _tf_ follow _vw_ week _fr_ refine by tag _hk_ kill _tl_ log _vt_ fortnight _fc_ by category _hr_ refile _ta_ archive trees _vm_ month _fh_ by top headline _hA_ archive _tA_ archive files _vy_ year _fx_ by regexp _h:_ set tags _tr_ clock report _vn_ next span _fd_ delete all filters _hp_ set priority _td_ diaries _vp_ prev span ^^ ^^ ^^ _vr_ reset ^^ ^^ ^^ ^^ ^^ ^^ ^^ " ;; Entry ("hA" org-agenda-archive-default) ("hk" org-agenda-kill) ("hp" org-agenda-priority) ("hr" org-agenda-refile) ("h:" org-agenda-set-tags) ("ht" org-agenda-todo) ;; Visit entry ("o" link-hint-open-link :exit t) ("" org-agenda-goto :exit t) ("TAB" org-agenda-goto :exit t) ("SPC" org-agenda-show-and-scroll-up) ("RET" org-agenda-switch-to :exit t) ;; Date ("dt" org-agenda-date-prompt) ("dd" org-agenda-deadline) ("+" org-agenda-do-date-later) ("-" org-agenda-do-date-earlier) ("ds" org-agenda-schedule) ;; View ("vd" org-agenda-day-view) ("vw" org-agenda-week-view) ("vt" org-agenda-fortnight-view) ("vm" org-agenda-month-view) ("vy" org-agenda-year-view) ("vn" org-agenda-later) ("vp" org-agenda-earlier) ("vr" org-agenda-reset-view) ;; Toggle mode ("ta" org-agenda-archives-mode) ("tA" (org-agenda-archives-mode 'files)) ("tr" org-agenda-clockreport-mode) ("tf" org-agenda-follow-mode) ("tl" org-agenda-log-mode) ("td" org-agenda-toggle-diary) ;; Filter ("fc" org-agenda-filter-by-category) ("fx" org-agenda-filter-by-regexp) ("ft" org-agenda-filter-by-tag) ("fr" org-agenda-filter-by-tag-refine) ("fh" org-agenda-filter-by-top-headline) ("fd" org-agenda-filter-remove-all) ;; Clock ("cq" org-agenda-clock-cancel) ("cj" org-agenda-clock-goto :exit t) ("ci" org-agenda-clock-in :exit t) ("co" org-agenda-clock-out) ;; Other ("q" nil :exit t) ("gd" org-agenda-goto-date) ("." org-agenda-goto-today) ("gr" org-agenda-redo)) (general-def '(normal visual motion insert emacs) org-agenda-mode-map "." 'hydra-org-agenda/body)) #+END_SRC ** Exporting *** HTML Export to HTML: #+BEGIN_SRC emacs-lisp (use-package htmlize :commands htmlize-buffer) #+END_SRC Don't put section numbers in front of headers: #+BEGIN_SRC emacs-lisp (setq org-export-with-section-numbers nil) #+END_SRC Disable the preamble and postamble: #+BEGIN_SRC emacs-lisp (setq org-html-preamble nil org-html-postamble nil) #+END_SRC Redefine org-html-src-block to wrap code blocks in
 and language class for use by highlight.js:
#+BEGIN_SRC emacs-lisp
  (defun org-html-src-block (src-block _contents info)
    "Transcode a SRC-BLOCK element from Org to HTML.
  CONTENTS holds the contents of the item.  INFO is a plist holding
  contextual information."
    (if (org-export-read-attribute :attr_html src-block :textarea)
	(org-html--textarea-block src-block)
      (let* ((lang (org-element-property :language src-block))
	     (code (org-html-format-code src-block info))
	     (label (let ((lbl (and (org-element-property :name src-block)
				    (org-export-get-reference src-block info))))
		      (if lbl (format " id=\"%s\"" lbl) "")))
	     (klipsify  (and  (plist-get info :html-klipsify-src)
			      (member lang '("javascript" "js"
					     "ruby" "scheme" "clojure" "php" "html")))))
	(if (not lang) (format "
\n%s
" label code) (format "
\n%s%s\n
" ;; Build caption. (let ((caption (org-export-get-caption src-block))) (if (not caption) "" (let ((listing-number (format "%s " (format (org-html--translate "Listing %d:" info) (org-export-get-ordinal src-block info nil #'org-html--has-caption-p))))) (format "" listing-number (org-trim (org-export-data caption info)))))) ;; Contents. (if klipsify (format "
%s
" lang label (if (string= lang "html") " data-editor-type=\"html\"" "") code) (format "
%s
" lang lang label code))))))) #+END_SRC *** Github-flavored markdown #+BEGIN_SRC emacs-lisp (use-package ox-gfm :after (org) :hook (org-mode . (lambda () (require 'ox-gfm)))) #+END_SRC *** Jira #+BEGIN_SRC emacs-lisp (use-package ox-jira :after (org) :straight (:host github :repo "stig/ox-jira.el" :branch "trunk") :hook (org-mode . (lambda () (require 'ox-jira)))) #+END_SRC ** org-babel Literate programming! #+BEGIN_SRC emacs-lisp (add-hook 'org-mode-hook (lambda () (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t) (shell . t) (clojure . t) (lisp . t) (scheme . t) (java . t) (js . t) (dot . t) (ditaa . t) (ledger . t) (sql . t) (jq . t) (restclient . t) (plantuml . t))))) #+END_SRC Get rid of the confirmation prompt: #+BEGIN_SRC emacs-lisp (setq org-confirm-babel-evaluate nil) #+END_SRC Display inline images after executing a source block: #+BEGIN_SRC emacs-lisp (add-hook 'org-babel-after-execute-hook (lambda () (org-display-inline-images nil t) (org-redisplay-inline-images))) #+END_SRC Enable async source block execution. Note: this execute the contents of the source block in a separate Emacs process, so blocks that rely on anything defined in init.org or the current Emacs process won't work as expected. #+BEGIN_SRC emacs-lisp (use-package ob-async :straight (ob-async :host github :repo "astahlman/ob-async") :defer t :hook (org-mode . (lambda () (require 'ob-async)))) #+END_SRC Filter out the "u" from unicode results in org tabels returned from Python source blocks: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'ob-python (defun org-babel-python-table-or-string (results) "Convert RESULTS into an appropriate elisp value. If the results look like a list or tuple, then convert them into an Emacs-lisp table, otherwise return the results as a string." (let ((res (org-babel-script-escape results))) (if (listp res) (mapcar (lambda (el) (cond ((eq el 'None) org-babel-python-None-to) ((listp el) (-filter (lambda (m) (not (eq m 'u))) el)) (t el))) res) res)))) #+END_SRC ** Images #+BEGIN_SRC emacs-lisp (setq org-image-actual-width nil) #+END_SRC ** org-scratch It's very useful to open a new org buffer for a quick org-babel exploration. #+BEGIN_SRC emacs-lisp (defun org-scratch (&optional make-new) "Switches to an org-mode buffer not associated with any file. If called with a prefix argument, always creates a new buffer; otherwise, switches to the existing *org-scratch* buffer if it exists." (interactive "P") (let ((org-scratch-buffer-name (generate-new-buffer-name "*org-scratch*" (unless make-new "*org-scratch*")))) (switch-to-buffer org-scratch-buffer-name) (org-mode))) (leader-def-key "os" #'org-scratch) #+END_SRC ** org-gcal Integrate Google calendar with org-mode: #+BEGIN_SRC emacs-lisp (defun get-calendar-file (name) (org-directory name)) (defun org-gcal-sync-advice (oldfn &rest args) (deferred:nextc (apply oldfn args) (lambda (_) (dolist (entry org-gcal-fetch-file-alist) (let ((file (cdr entry))) (with-current-buffer (find-file-noselect file) (save-buffer))))))) (use-package org-gcal :commands (org-gcal-sync org-gcal-fetch org-gcal-post-at-point org-gcal-delete-at-point org-gcal-request-token) :config (advice-add 'org-gcal-sync :around #'org-gcal-sync-advice) (setq org-gcal-client-id (password-store-get "lola-org-gcal-client-id") org-gcal-client-secret (password-store-get "lola-org-gcal-client-secret") org-gcal-fetch-file-alist `(("jeremydormitzer@lola.com" . ,(get-calendar-file "lola-gcal.org")) ("jeremy.dormitzer@gmail.com" . ,(get-calendar-file "personal-gcal.org")) ("lut2o2moohg6qkdsto1qfq7th4@group.calendar.google.com" . ,(get-calendar-file "j-n-gcal.org"))) org-gcal-notify-p nil)) (defun org-agenda-redo-and-fetch-gcal (&optional all) (interactive "P") (let ((cb (if all #'org-agenda-redo-all #'org-agenda-redo))) (deferred:nextc (org-gcal-fetch) cb))) (with-eval-after-load 'org-agenda (general-def '(normal motion) org-agenda-mode-map "gR" #'org-agenda-redo-and-fetch-gcal)) #+END_SRC ** Utilities A function to get the TITLE property of the current org buffer: #+BEGIN_SRC emacs-lisp (defun org-get-title (&optional contents) (let ((raw (or contents (buffer-substring (point-min) (point-max))))) (with-temp-buffer (insert raw) (car (org-element-map (org-element-parse-buffer) 'keyword (lambda (el) (when (string-match-p "TITLE" (org-element-property :key el)) (org-element-property :value el)))))))) #+END_SRC ** org-present An ultra-minimalist presentation mode for Org: #+BEGIN_SRC emacs-lisp (use-package org-present :commands (org-present) :config (defun org-present-on () (org-present-big) (org-display-inline-images) (org-present-hide-cursor) (display-line-numbers-mode -1) (org-present-read-only)) (defun org-present-off () (org-present-small) (org-present-show-cursor) (unless org-startup-with-inline-images (org-remove-inline-images)) (display-line-numbers-mode) (org-present-read-write)) (add-hook 'org-present-mode-hook #'org-present-on) (add-hook 'org-present-mode-quit-hook #'org-present-off) ;; Redefine org-present to call org-present-narrow after running hooks (defun org-present () "init." (interactive) (setq org-present-mode t) (org-present-add-overlays) (run-hooks 'org-present-mode-hook) (org-present-narrow) (org-present-run-after-navigate-functions)) :init (general-def '(normal visual motion emacs) org-present-mode-keymap "" #'org-present-prev "" #'org-present-next "C-k" #'org-present-prev "C-j" #'org-present-next "q" #'org-present-quit)) #+END_SRC ** org-cliplink Intelligently inserts an org-mode link from the clipboard. #+BEGIN_SRC emacs-lisp (use-package org-cliplink :after (org) :commands (org-cliplink org-cliplink-clipboard-content) :general (org-mode-map "C-c C-S-L" #'org-cliplink)) #+END_SRC ** org-board [[https://github.com/scallywag/org-board][Org-board]] is a bookmarking/archiving system built on Org mode: #+BEGIN_SRC emacs-lisp (use-package org-board :after (org) :config ;; Org-capture setup (defvar org-capture-bookmark-last-url nil) (defvar org-capture-bookmark-last-title nil) (defun org-capture-bookmark-get-url () (let* ((clip (org-cliplink-clipboard-content)) (parsed (url-generic-parse-url clip))) (if (url-type parsed) clip (read-string "Bookmark URL: ")))) (defun org-capture-bookmark-get-title (url) (or (org-cliplink-retrieve-title-synchronously url) (read-string "Bookmark title: "))) (defun bookmark-file (title) (org-directory (format "%s.org" (org-roam--get-new-id title)))) (defun org-capture-bookmark-file () (let* ((url (org-capture-bookmark-get-url)) (title (org-capture-bookmark-get-title url))) (setq org-capture-bookmark-last-url url) (setq org-capture-bookmark-last-title title) (bookmark-file title))) (defun org-capture-bookmark-link () (format "[[%s][%s]]" org-capture-bookmark-last-url org-capture-bookmark-last-title)) (defun org-capture-bookmark-title () org-capture-bookmark-last-title) (defun save-bookmark (url) (save-excursion (goto-char (point-min)) (when (search-forward "* Bookmark" nil t) (org-board-new url) (wallabag-add-entry url (cl-function (lambda (&key data &allow-other-keys) (let ((entry-url (format "%s/view/%s" wallabag-base-url (alist-get 'id data)))) (message "Added bookmark to Wallabag: %s" entry-url)))))))) (defun org-capture-bookmark-after-finalize () "Runs `org-board-new' on the captured entry. Also saves to Wallabag." (let ((success (not org-note-abort)) (key (plist-get org-capture-plist :key)) (desc (plist-get org-capture-plist :description))) (when (and success (equal key "b") (equal desc "Bookmark") org-capture-bookmark-last-url) (save-bookmark org-capture-bookmark-last-url) (setq org-capture-bookmark-last-url nil) (setq org-capture-bookmark-last-title nil)))) (add-hook 'org-capture-prepare-finalize-hook #'org-capture-bookmark-after-finalize) (add-to-list 'org-capture-templates '("b" "Bookmark" plain (file org-capture-bookmark-file) "#+TITLE: %(org-capture-bookmark-title)\n\n- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n- source :: %(org-capture-bookmark-link)\n%?\n* Bookmark")) ;; Org-protocol setup (defun make-org-protocol-bookmark (url title) (with-temp-buffer (let ((filename (bookmark-file title))) (save-excursion (insert (concat (format "#+TITLE: %s\n\n" title) "- tags :: [[file:deft/bookmarks.org][Bookmarks]]\n" (format "- source :: [[%s][%s]]\n\n" url title) "* Bookmark")) (write-file filename) (save-bookmark url) (save-buffer))))) (defun bookmark-via-org-protocol (url) (org-cliplink-retrieve-title (url-unhex-string url) #'make-org-protocol-bookmark)) (with-eval-after-load 'org-protocol (add-to-list 'org-protocol-protocol-alist '("Bookmark" :protocol "bookmark" :function bookmark-via-org-protocol :kill-client t))) (add-to-list 'org-board-wget-switches "--recursive") (add-to-list 'org-board-wget-switches "--level=1") (add-to-list 'org-board-wget-switches "--span-hosts") ;; Use w3m instead of eww to open org-board archived links (advice-add 'org-board-open-with :around (lambda (oldfn filename-string arg &rest args) (cond ((not (file-exists-p filename-string)) 1) ((and filename-string (or (and arg (eq org-board-default-browser 'system)) (and (not arg) (eq org-board-default-browser 'eww)))) (let ((filename (concat "file://" (s-chop-prefix "file://" filename-string)))) (w3m filename t) 0)) (:else (apply oldfn filename-string arg args))))) :general (org-mode-map "C-c b" org-board-keymap)) #+END_SRC ** org-rifle Quickly find stuff in Org files: #+BEGIN_SRC emacs-lisp (use-package helm-org-rifle :commands (helm-org-rifle helm-org-rifle-agenda-files helm-org-rifle-current-buffer helm-org-rifle-directories helm-org-rifle-files helm-org-rifle-org-directory helm-org-rifle-occur helm-org-rifle-occur-agenda-files helm-org-rifle-occur-current-buffer helm-org-rifle-occur-directories helm-org-rifle-occur-files helm-org-rifle-occur-org-directory) :init (defvar helm-org-rifle-commands-map (make-sparse-keymap)) (general-def helm-org-rifle-commands-map "r" #'helm-org-rifle "a" #'helm-org-rifle-agenda-files "b" #'helm-org-rifle-current-buffer "d" #'helm-org-rifle-directories "f" #'helm-org-rifle-files "o" #'helm-org-rifle-org-directory "R" #'helm-org-rifle-occur "A" #'helm-org-rifle-occur-agenda-files "B" #'helm-org-rifle-occur-current-buffer "D" #'helm-org-rifle-occur-directories "F" #'helm-org-rifle-occur-files "O" #'helm-org-rifle-occur-org-directory) (leader-def-key "or" helm-org-rifle-commands-map) (jdormit/define-prefix "or" "org-rifle") :config (defun around-helm-org-rifle (oldfn &rest args) (if (and (boundp 'centaur-tabs-mode) centaur-tabs-mode) (progn (centaur-tabs-mode -1) (apply oldfn args) (centaur-tabs-mode 1)) (apply oldfn args))) (advice-add 'helm-org-rifle :around #'around-helm-org-rifle)) #+END_SRC ** Org Noter [[https://github.com/weirdNox/org-noter][Org Noter]] lets me take org-mode notes on PDFs, epubs, and DocView files: #+BEGIN_SRC emacs-lisp (use-package org-noter :commands org-noter :general (normal org-noter-doc-mode-map "i" #'org-noter-insert-note "q" #'org-noter-kill-session "C-M-n" #'org-noter-sync-next-note "C-M-p" #'org-noter-sync-prev-note "M-." #'org-noter-sync-current-page-or-chapter "M-i" #'org-noter-insert-precise-note "M-n" #'org-noter-sync-next-page-or-chapter "M-p" #'org-noter-sync-prev-page-or-chapter "C-M-." #'org-noter-sync-current-note)) #+END_SRC ** Org Roam [[https://org-roam.readthedocs.io/en/develop/][Org-roam]] is another backlink package for org-mode: #+BEGIN_SRC emacs-lisp (use-package org-roam :straight (:host github :repo "jethrokuan/org-roam") :commands (org-roam org-roam-today org-roam-find-file org-roam-insert org-roam-show-graph org-roam--get-new-id) :custom (org-roam-directory (org-directory)) :init (defvar org-roam-map (make-sparse-keymap)) (jdormit/define-prefix "on" "org-roam") (which-key-add-key-based-replacements "C-c n" "org-roam") (which-key-add-major-mode-key-based-replacements 'org-mode "gn" "org-roam") (with-eval-after-load 'org (org-roam-mode)) :config (add-hook 'org-roam-backlinks-mode-hook #'olivetti-mode) :general (leader-map "fo" #'org-roam-find-file "of" #'org-roam-find-file "on" org-roam-map) (org-roam-map "l" #'org-roam "t" #'org-roam-today "f" #'org-roam-find-file "i" #'org-roam-insert "g" #'org-roam-show-graph) (normal org-mode-map "gr" org-roam-map) (normal org-roam-backlinks-mode-map "" #'org-open-at-point "q" #'quit-window) ("C-c n" org-roam-map)) #+END_SRC ** org-journal [[https://github.com/bastibe/org-journal][org-journal]] is a package that provides some convenience functions around keeping a daily Org-mode journal. I also set it up so it plays nice with Org-roam: #+BEGIN_SRC emacs-lisp (use-package org-journal :commands (org-journal-today org-journal-new-entry) :init (defun org-journal-file-header-func (time) (format "#+TITLE: %s" (format-time-string "%Y-%m-%d" time))) (defun org-journal-today () (interactive) (org-journal-new-entry t)) (defun org-journal-capture-func () (org-journal-new-entry t) (goto-char (point-min)) ;; Account for the #+TITLE (forward-line)) (jdormit/define-prefix "oj" "org-journal") (leader-def-key "ojn" #'org-journal-new-entry) (leader-def-key "ojt" #'org-journal-today) :config (add-to-list 'org-capture-templates '("j" "Journal entry" entry (function org-journal-capture-func) "* %(format-time-string org-journal-time-format)\n%?")) :custom (org-journal-file-type 'daily) (org-journal-dir (org-directory)) (org-journal-file-format "%Y-%m-%d.org") (org-journal-file-header 'org-journal-file-header-func) (org-journal-carryover-items "") :general (org-roam-map "t" 'org-journal-today)) #+END_SRC ** org-super-agenda #+BEGIN_SRC emacs-lisp (use-package org-super-agenda :after (org) :init (setq org-super-agenda-groups '((:name "In progress" :todo "IN PROGRESS") (:name "Lola" :tag "@lola") (:name "unifyDB" :tag "@unifydb") (:name "Personal" :tag "@personal"))) (org-super-agenda-mode) :config (setq org-super-agenda-header-map (make-sparse-keymap))) #+END_SRC ** Org-Jira Jira in Emacs: #+BEGIN_SRC emacs-lisp (use-package org-jira :after (org) :init (setq jiralib-url "https://lola.atlassian.net" org-jira-working-dir (org-directory "jira") org-jira-jira-status-to-org-keyword-alist '(("To Do" . "TODO") ("Blocked" . "BLOCKED") ("In Progress" . "IN PROGRESS") ("Code Review" . "IN PROGRESS") ("Awaiting Release" . "IN PROGRESS") ("Done" . "DONE") ("Won't Fix" . "CANCELLED"))) (add-to-list 'org-agenda-files org-jira-working-dir) (add-to-list 'org-super-agenda-groups `(:name "Jira" :file-path ,org-jira-working-dir) t)) #+END_SRC * Doom themes #+BEGIN_SRC emacs-lisp (use-package doom-themes) (use-package doom-modeline) (doom-modeline-mode 1) #+END_SRC * Ewal theme #+BEGIN_SRC emacs-lisp (use-package doom-ewal-themes :straight (doom-ewal-themes :host github :repo "jdormit/doom-ewal-themes" :files ("themes" :defaults))) #+END_SRC * Customization File I don't want anything to write to my init.el, so save customizations in a separate file: #+BEGIN_SRC emacs-lisp (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file t) #+END_SRC * Path `exec-path-from-shell` uses Bash to set MANPATH, PATH, and exec-path from those defined in the user's shell config. This won't work on Windows. #+BEGIN_SRC emacs-lisp (use-package exec-path-from-shell :if (memq window-system '(mac ns x)) :config (setq exec-path-from-shell-variables '("PATH" "MANPATH" "LEDGER_FILE" "LOLA_HOME" "MODELS_HOME" "LOLA_TRAVEL_SERVICE_HOME" "WORKON_HOME" "PIPENV_VERBOSITY" "PIPENV_DONT_LOAD_ENV" "PIPENV_MAX_DEPTH" "PYENV_ROOT" "KOPS_STATE_STORE" "PLAID_CLIENT_ID" "PLAID_SECRET" "PLAID_ENVIRONMENT") exec-path-from-shell-check-startup-files nil) (exec-path-from-shell-initialize)) #+END_SRC * Autocompletion There seems to be [[https://github.com/company-mode/company-mode/issues/68][some contention]] about whether autocomplete or company are better autocomplete packages. I'm going with company for now because the maintainer seems nicer... #+BEGIN_SRC emacs-lisp (use-package company :commands (company-complete company-mode) :hook ((prog-mode . company-mode)) :config (setq company-idle-delay 0.3)) (general-def "C-M-i" #'company-complete) (general-def "M-" #'company-complete) #+END_SRC * Multiple cursors #+BEGIN_SRC emacs-lisp (use-package evil-mc :defer 0 :config (global-evil-mc-mode) (general-def '(normal visual) "gs" evil-mc-cursors-map "M-n" #'evil-mc-make-and-goto-next-cursor "M-p" #'evil-mc-make-and-goto-prev-cursor "C-n" #'evil-mc-make-and-goto-next-match "C-t" #'evil-mc-skip-and-goto-next-match "C-p" #'evil-mc-make-and-goto-prev-match)) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package evil-multiedit :defer 0 :config (evil-multiedit-default-keybinds)) #+END_SRC * Transient A framework for creating Magit-style popups: #+BEGIN_SRC emacs-lisp (use-package transient :commands (define-transient-command)) #+END_SRC * Magit Magit is objectively the best Git interface. #+BEGIN_SRC emacs-lisp (use-package magit :commands (magit-status magit-blame magit-find-file magit-name-local-branch)) #+END_SRC #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "g" "git") (leader-def-key "gs" #'magit-status "gg" #'magit-file-dispatch "gd" #'magit-dispatch "gf" #'magit-find-file) #+END_SRC ** Forge [[https://github.com/magit/forge][Forge]] is an extension for Magit that lets it interact with code forges (e.g. GitHub). #+BEGIN_SRC emacs-lisp (use-package forge :after (magit) :config (add-to-list 'forge-alist '("git.jeremydormitzer.com" "git.jeremydormitzer.com/api/v1" "git.jeremydormitzer.com" forge-gitea-repository)) (with-eval-after-load 'evil-magit (general-def '(normal motion) magit-mode-map "yu" #'forge-copy-url-at-point-as-kill)) :general ((normal motion visual) forge-topic-list-mode-map "y" #'forge-copy-url-at-point-as-kill "q" #'quit-window) :custom (forge-owned-accounts '((jdormit . (remote-name "jdormit"))))) #+END_SRC ** evil-magit Evil keybindings for magit! #+BEGIN_SRC emacs-lisp (use-package evil-magit :after (evil magit) :config (with-eval-after-load 'magit (require 'evil-magit)) :general ('normal magit-mode-map "SPC" leader-map)) #+END_SRC ** Transient #+BEGIN_SRC emacs-lisp (setq transient-default-level 7) #+END_SRC * git-link Open files in Git forges (GitHub, GitLab, etc.): #+BEGIN_SRC emacs-lisp (use-package git-link :commands (git-link) :init (defun git-link-copy () (interactive) (let ((git-link-open-in-browser nil)) (call-interactively 'git-link))) (leader-def-key "gl" #'git-link) (leader-def-key "gy" #'git-link-copy) :custom (git-link-open-in-browser t) (git-link-remote-alist '(("git.sr.ht" git-link-sourcehut) ("github" git-link-github) ("bitbucket" git-link-bitbucket) ("gitorious" git-link-gitorious) ("gitlab" git-link-gitlab) ("visualstudio\\|azure" git-link-azure) ("git.jeremydormitzer.com" git-link-bitbucket)))) #+END_SRC * with-editor A utility from the author of Magit to run shell commands using the current Emacs instance as $EDITOR. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'with-editor (shell-command-with-editor-mode) (add-hook 'shell-mode-hook #'with-editor-export-editor) (add-hook 'term-exec-hook #'with-editor-export-editor) (add-hook 'eshell-mode-hook #'with-editor-export-editor)) #+END_SRC * Password Store Interfacing with Pass, the "standard Unix password manager". This should also be loaded before `exec-path-from-shell`. #+BEGIN_SRC emacs-lisp (defun password-store-synchronize () (interactive) (with-editor-async-shell-command "pass git pull && pass git push")) (use-package password-store :defer t :if (executable-find "pass") :config (setq password-store-password-length 20) :init (leader-def-key "P" 'password-store-copy) (epa-file-enable)) (use-package pass :if (executable-find "pass") :commands pass :general (normal pass-mode-map "S" #'password-store-synchronize)) (leader-def-key "ap" #'pass) (setq auth-sources '("~/.authinfo" password-store)) #+END_SRC * Init File A function to reload my init file. It reloads the major mode after the init file is loaded to rebind keymappings. #+BEGIN_SRC emacs-lisp (defun reload-init-file () (interactive) (load-file "~/.emacs.d/init.el") (funcall major-mode)) #+END_SRC And another one to edit it: #+BEGIN_SRC emacs-lisp (defun find-init-file () (interactive) (find-file "~/.emacs.d/init.org")) #+END_SRC * Keybindings These are general keybindings; those specific to certain packages are in the sections for those packages. In some modes I want vanilla Emacs bindings: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil (add-to-list 'evil-emacs-state-modes 'benchmark-init/tabulated-mode) (add-to-list 'evil-emacs-state-modes 'benchmark-init/tree-mode) (add-to-list 'evil-emacs-state-modes 'cider-stacktrace-mode) (add-to-list 'evil-emacs-state-modes 'geiser-debug-mode) (add-to-list 'evil-emacs-state-modes 'undo-tree-visualizer-mode) (add-to-list 'evil-emacs-state-modes 'makey-key-mode) (add-to-list 'evil-emacs-state-modes 'term-mode)) #+END_SRC ** Visual line navigation #+BEGIN_SRC emacs-lisp (general-def 'motion "j" #'evil-next-visual-line) (general-def 'motion "k" #'evil-previous-visual-line) (general-def 'motion "j" #'evil-next-visual-line) (general-def 'motion "k" #'evil-previous-visual-line) #+END_SRC ** M-x #+BEGIN_SRC emacs-lisp (leader-def-key "SPC" 'execute-extended-command) #+END_SRC ** Eval-ing #+BEGIN_SRC emacs-lisp (leader-def-key ":" #'eval-expression) #+END_SRC ** Init file commands #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "." "dotfile") (leader-def-key ".r" 'reload-init-file) (leader-def-key ".f" 'find-init-file) #+END_SRC ** Commands about files #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "f" "files") (leader-def-key "ff" 'find-file) (leader-def-key "fd" 'dired) (leader-def-key "fs" 'sudo-find-file) (leader-def-key "ft" 'auto-revert-tail-mode) (leader-def-key "fp" 'find-file-at-point) (general-def '(normal motion visual) "gf" 'find-file-at-point) #+END_SRC ** Window commands #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "w" "window") (leader-def-key "w/" 'split-window-right) (leader-def-key "w-" 'split-window-below) (leader-def-key "wm" 'delete-other-windows) (leader-def-key "wd" 'delete-window) (leader-def-key "ws" 'window-swap-states) #+END_SRC ** Buffer commands A function to switch to previous buffer from [[http://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/][this blog post]]: #+BEGIN_SRC emacs-lisp (defun switch-to-previous-buffer () "Switch to previously open buffer. Repeated invocations toggle between the two most recently open buffers." (interactive) (switch-to-buffer (other-buffer (current-buffer) 1))) (leader-def-key "TAB" 'switch-to-previous-buffer) #+END_SRC A function to kill all buffers except the current one from [[https://www.emacswiki.org/emacs/KillingBuffers#toc2][EmacsWiki]]: #+BEGIN_SRC emacs-lisp (defun kill-other-buffers () "Kill all other buffers." (interactive) (mapc 'kill-buffer (delq (current-buffer) (buffer-list)))) #+END_SRC #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "b" "buffer") (leader-def-key "bb" #'switch-to-buffer) (leader-def-key "bn" #'next-buffer) (leader-def-key "bp" #'previous-buffer) (leader-def-key "bd" #'kill-buffer) (leader-def-key "bm" #'kill-other-buffers) (leader-def-key "br" #'rename-buffer) #+END_SRC ** Frame commands #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "F" "frame") (leader-def-key "Fn" #'make-frame-command) (leader-def-key "Fo" #'other-frame) (leader-def-key "Fm" #'delete-other-frames) (leader-def-key "Fd" #'delete-frame) #+END_SRC ** Running shell commands #+BEGIN_SRC emacs-lisp (leader-def-key "!" 'shell-command) (leader-def-key "|" 'shell-command-on-region) (general-def 'normal shell-mode-map "q" #'bury-buffer) #+END_SRC ** Toggles Like in Spacemacs, put all toggle commands behind a prefix: #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "t" "toggle") #+END_SRC Toggles about line truncation: #+BEGIN_SRC emacs-lisp (leader-def-key "tt" 'toggle-truncate-lines) (leader-def-key "tT" 'visual-line-mode) (leader-def-key "tf" 'auto-fill-mode) #+END_SRC Toggle lisp debugging: #+BEGIN_SRC emacs-lisp (leader-def-key "td" 'toggle-debug-on-error) #+END_SRC ** Shells/REPLs Emacs has a shell for every mood! #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "s" "shells/REPLs") (leader-def-key "ss" 'shell) (leader-def-key "si" 'ielm) (leader-def-key "se" 'eshell) (leader-def-key "sa" 'ansi-term) #+END_SRC ** Applications #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "a" "applications") #+END_SRC ** Help Buffers #+BEGIN_SRC emacs-lisp (general-def 'motion help-mode-map "TAB" #'forward-button) #+END_SRC ** Code commands #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "c" "code") (leader-def-key "cd" #'xref-find-definitions) (leader-def-key "cr" #'xref-find-references) (leader-def-key "ca" #'xref-find-apropos) (general-def 'normal "M-." #'xref-find-definitions) #+END_SRC ** Process list #+BEGIN_SRC emacs-lisp (general-def '(normal motion visual) process-menu-mode-map "d" #'process-menu-delete-process) #+END_SRC ** Registers #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "r" "registers") (leader-def-key "r" ctl-x-r-map) #+END_SRC * Evil-numbers Handy functions to increment/decrement numbers at point: #+BEGIN_SRC emacs-lisp (use-package evil-numbers :straight (:host github :repo "janpath/evil-numbers") :general ((normal visual motion) "g+" 'evil-numbers/inc-at-pt) ((normal visual motion) "g-" 'evil-numbers/dec-at-pt) ((normal visual motion) "g C-+" 'evil-numbers/inc-at-pt-incremental) ((normal visual motion) "g C--" 'evil-numbers/dec-at-pt-incremental)) #+END_SRC * Winner Winner is a minor mode that keeps an undo/redo history of the window configuration: #+BEGIN_SRC emacs-lisp (winner-mode 1) (leader-def-key "wn" #'winner-redo) (leader-def-key "wp" #'winner-undo) #+END_SRC * Buffer move A handy package to shift buffers between open windows: #+BEGIN_SRC emacs-lisp (use-package buffer-move :general ("C-S-j" #'buf-move-down) ("C-S-k" #'buf-move-up) ("C-S-h" #'buf-move-left) ("C-S-l" #'buf-move-right)) #+END_SRC * Info #+BEGIN_SRC emacs-lisp (use-package info :init (defhydra hydra-info (:color blue :hint nil) " Info-mode: ^^_]_ forward (next logical node) ^^_l_ast (←) _u_p (↑) _f_ollow reference _T_OC ^^_[_ backward (prev logical node) ^^_r_eturn (→) _m_enu (↓) (C-u for new window) _i_ndex _d_irectory ^^_n_ext (same level only) ^^_H_istory _g_oto (C-u for new window) _,_ next index item _c_opy node name ^^_p_rev (same level only) _<_/_t_op _b_eginning of buffer virtual _I_ndex _C_lone buffer regex _s_earch (_S_ case sensitive) ^^_>_ final _e_nd of buffer ^^ _a_propos _1_ .. _9_ Pick first .. ninth item in the node's menu. " ("]" Info-forward-node) ("[" Info-backward-node) ("n" Info-next) ("p" Info-prev) ("s" Info-search) ("S" Info-search-case-sensitively) ("l" Info-history-back) ("r" Info-history-forward) ("H" Info-history) ("t" Info-top-node) ("<" Info-top-node) (">" Info-final-node) ("u" Info-up) ("^" Info-up) ("m" Info-menu) ("g" Info-goto-node) ("b" beginning-of-buffer) ("e" end-of-buffer) ("f" Info-follow-reference) ("i" Info-index) ("," Info-index-next) ("I" Info-virtual-index) ("T" Info-toc) ("d" Info-directory) ("c" Info-copy-current-node-name) ("C" clone-buffer) ("a" info-apropos) ("1" Info-nth-menu-item) ("2" Info-nth-menu-item) ("3" Info-nth-menu-item) ("4" Info-nth-menu-item) ("5" Info-nth-menu-item) ("6" Info-nth-menu-item) ("7" Info-nth-menu-item) ("8" Info-nth-menu-item) ("9" Info-nth-menu-item) ("?" Info-summary "Info summary") ("h" Info-help "Info help") ("q" Info-exit "Info exit") ("C-g" nil "cancel" :color blue)) :general (Info-mode-map "C-/" 'hydra-info/body)) #+END_SRC * xref After I select an xref reference, I want the xref buffer closed: #+BEGIN_SRC emacs-lisp (defun xref-goto-xref-and-quit () (interactive) (xref-goto-xref t)) (general-def 'normal xref--xref-buffer-mode-map "RET" #'xref-goto-xref-and-quit :keymaps 'override) #+END_SRC Don't prompt for an identifier for these xref commands: #+BEGIN_SRC emacs-lisp (setq xref-prompt-for-identifier '(not xref-find-definitions xref-find-definitions-other-window xref-find-definitions-other-frame xref-find-references)) #+END_SRC Some keybindings: #+BEGIN_SRC emacs-lisp (general-def "M-r" #'xref-find-references) #+END_SRC * IBuffer #+BEGIN_SRC emacs-lisp (use-package ibuffer :straight (:type built-in) :init (defhydra hydra-ibuffer-main (:color pink :hint nil) " ^Navigation^ | ^Mark^ | ^Actions^ | ^View^ -^----------^-+-^----^--------+-^-------^--------+-^----^------- _k_: ʌ | _m_: mark | _D_: delete | _g_: refresh _RET_: visit | _u_: unmark | _S_: save | _s_: sort _j_: v | _*_: specific | _a_: all actions | _/_: filter -^----------^-+-^----^--------+-^-------^--------+-^----^------- " ("j" ibuffer-forward-line) ("RET" ibuffer-visit-buffer :color blue) ("k" ibuffer-backward-line) ("m" ibuffer-mark-forward) ("u" ibuffer-unmark-forward) ("*" hydra-ibuffer-mark/body :color blue) ("D" ibuffer-do-delete) ("S" ibuffer-do-save) ("a" hydra-ibuffer-action/body :color blue) ("g" ibuffer-update) ("s" hydra-ibuffer-sort/body :color blue) ("/" hydra-ibuffer-filter/body :color blue) ("o" ibuffer-visit-buffer-other-window "other window" :color blue) ("q" quit-window "quit ibuffer" :color blue) ("." nil "toggle hydra" :color blue)) (defhydra hydra-ibuffer-mark (:color teal :columns 5 :after-exit (hydra-ibuffer-main/body)) "Mark" ("*" ibuffer-unmark-all "unmark all") ("M" ibuffer-mark-by-mode "mode") ("m" ibuffer-mark-modified-buffers "modified") ("u" ibuffer-mark-unsaved-buffers "unsaved") ("s" ibuffer-mark-special-buffers "special") ("r" ibuffer-mark-read-only-buffers "read-only") ("/" ibuffer-mark-dired-buffers "dired") ("e" ibuffer-mark-dissociated-buffers "dissociated") ("h" ibuffer-mark-help-buffers "help") ("z" ibuffer-mark-compressed-file-buffers "compressed") ("b" hydra-ibuffer-main/body "back" :color blue)) (defhydra hydra-ibuffer-action (:color teal :columns 4 :after-exit (if (eq major-mode 'ibuffer-mode) (hydra-ibuffer-main/body))) "Action" ("A" ibuffer-do-view "view") ("E" ibuffer-do-eval "eval") ("F" ibuffer-do-shell-command-file "shell-command-file") ("I" ibuffer-do-query-replace-regexp "query-replace-regexp") ("H" ibuffer-do-view-other-frame "view-other-frame") ("N" ibuffer-do-shell-command-pipe-replace "shell-cmd-pipe-replace") ("M" ibuffer-do-toggle-modified "toggle-modified") ("O" ibuffer-do-occur "occur") ("P" ibuffer-do-print "print") ("Q" ibuffer-do-query-replace "query-replace") ("R" ibuffer-do-rename-uniquely "rename-uniquely") ("T" ibuffer-do-toggle-read-only "toggle-read-only") ("U" ibuffer-do-replace-regexp "replace-regexp") ("V" ibuffer-do-revert "revert") ("W" ibuffer-do-view-and-eval "view-and-eval") ("X" ibuffer-do-shell-command-pipe "shell-command-pipe") ("b" nil "back")) (defhydra hydra-ibuffer-sort (:color amaranth :columns 3) "Sort" ("i" ibuffer-invert-sorting "invert") ("a" ibuffer-do-sort-by-alphabetic "alphabetic") ("v" ibuffer-do-sort-by-recency "recently used") ("s" ibuffer-do-sort-by-size "size") ("f" ibuffer-do-sort-by-filename/process "filename") ("m" ibuffer-do-sort-by-major-mode "mode") ("b" hydra-ibuffer-main/body "back" :color blue)) (defhydra hydra-ibuffer-filter (:color amaranth :columns 4) "Filter" ("m" ibuffer-filter-by-used-mode "mode") ("M" ibuffer-filter-by-derived-mode "derived mode") ("n" ibuffer-filter-by-name "name") ("c" ibuffer-filter-by-content "content") ("e" ibuffer-filter-by-predicate "predicate") ("f" ibuffer-filter-by-filename "filename") (">" ibuffer-filter-by-size-gt "size") ("<" ibuffer-filter-by-size-lt "size") ("/" ibuffer-filter-disable "disable") ("b" hydra-ibuffer-main/body "back" :color blue)) :general ((normal motion visual insert emacs) ibuffer-mode-map "." 'hydra-ibuffer-main/body)) #+END_SRC * Speedbar Speedbar is cool but having it open in a separate frame is annoying. This makes it open in a side window in the same frame: #+BEGIN_SRC emacs-lisp (use-package sr-speedbar :commands (sr-speedbar-toggle sr-speedbar-open sr-speedbar-select-window sr-speedbar-exist-p) :general (speedbar-mode-map "q" #'sr-speedbar-close)) (defun switch-to-speedbar () (interactive) (unless (sr-speedbar-exist-p) (sr-speedbar-open)) (sr-speedbar-select-window)) (leader-def-key "S" #'switch-to-speedbar) #+END_SRC * Trailing whitespace [[https://github.com/lewang/ws-butler][ws-butler]] deletes trailing whitespace on lines that you touch, but leaves other lines alone: #+BEGIN_SRC emacs-lisp (use-package ws-butler :straight (ws-butler :host github :repo "lewang/ws-butler") :hook ((prog-mode . ws-butler-mode))) #+END_SRC * Whitespace Visualation #+BEGIN_SRC emacs-lisp (setq whitespace-line-column 80 whitespace-style '(face lines-tail)) (leader-def-key "tw" #'whitespace-mode) #+END_SRC * Line Numbers Toggle line numbers: #+BEGIN_SRC emacs-lisp (setq display-line-numbers-type 'visual) (leader-def-key "tn" 'display-line-numbers-mode) #+END_SRC Toggle line numbering mode (normal or relative): #+BEGIN_SRC emacs-lisp (defun toggle-line-number-mode () (interactive) (when display-line-numbers (if (eq display-line-numbers 'visual) (progn (setq display-line-numbers t) (setq display-line-numbers-type t)) (progn (setq display-line-numbers 'visual) (setq display-line-numbers-type 'visual))))) (leader-def-key "tr" #'toggle-line-number-mode) #+END_SRC Display line numbers by default in code and org-mode buffers: #+BEGIN_SRC emacs-lisp (add-hook 'prog-mode-hook #'display-line-numbers-mode) (add-hook 'org-mode-hook #'display-line-numbers-mode) #+END_SRC * Amx A better M-x. #+BEGIN_SRC emacs-lisp (use-package amx :config (amx-mode)) #+END_SRC * Olivetti Mode Olivetti is a minor mode for a nice writing environment. #+BEGIN_SRC emacs-lisp (use-package olivetti :config (setq-default olivetti-body-width 100) (setq olivetti-body-width 100) :commands olivetti-mode) (leader-def-key "to" 'olivetti-mode) #+END_SRC * Winum This package includes functions to switch windows by number. #+BEGIN_SRC emacs-lisp (defun winum-assign-0-to-dired-sidebar () (when (equal major-mode 'dired-sidebar-mode) 10)) (use-package winum :config (winum-mode) (add-to-list 'winum-assign-functions #'winum-assign-0-to-dired-sidebar) (leader-def-key "0" 'winum-select-window-0-or-10) (leader-def-key "1" 'winum-select-window-1) (leader-def-key "2" 'winum-select-window-2) (leader-def-key "3" 'winum-select-window-3) (leader-def-key "4" 'winum-select-window-4) (leader-def-key "5" 'winum-select-window-5) (leader-def-key "6" 'winum-select-window-6) (leader-def-key "7" 'winum-select-window-7) (leader-def-key "8" 'winum-select-window-8) (leader-def-key "9" 'winum-select-window-9) :custom (winum-scope 'frame-local)) #+END_SRC I don't want which-key display "lambda" for the descriptions of these, so set a custom display function. This is [[https://github.com/syl20bnr/spacemacs/blob/master/layers/+distributions/spacemacs-bootstrap/packages.el#L312][stolen from Spacemacs]]. #+BEGIN_SRC emacs-lisp (push '(("\\(.*\\) 0" . "select-window-0") . ("\\1 0..9" . "window 0..9")) which-key-replacement-alist) (push '((nil . "select-window-[1-9]") . t) which-key-replacement-alist) #+END_SRC * Backups and Autosaves Store backups and autosaves in a centralized place. This should really be the default... #+BEGIN_SRC emacs-lisp (make-directory (expand-file-name "~/.emacs.d/autosaves") t) (setq auto-save-file-name-transforms '((".*" "~/.emacs.d/autosaves" t))) (setq backup-directory-alist '(("." . "~/.emacs.d/backups"))) #+END_SRC * Smartparens/Parinfer Smartparens enables structured editing of s-expressions and other pairs: #+BEGIN_SRC emacs-lisp (use-package smartparens :hook ((prog-mode . smartparens-strict-mode) (eshell-mode . smartparens-strict-mode) (vterm-mode . smartparens-mode) (geiser-repl-mode . smartparens-strict-mode) (inferior-python-mode . smartparens-strict-mode)) :init (defhydra hydra-smartparens (:hint nil) " Moving^^^^ Slurp & Barf^^ Wrapping^^ Sexp juggling^^^^ Destructive ------------------------------------------------------------------------------------------------------------------------ [_a_] beginning [_n_] down [_h_] bw slurp [_R_] rewrap [_S_] split [_t_] transpose [_c_] change inner [_w_] copy [_e_] end [_N_] bw down [_H_] bw barf [_u_] unwrap [_s_] splice [_A_] absorb [_C_] change outer [_f_] forward [_p_] up [_l_] slurp [_U_] bw unwrap [_r_] raise [_E_] emit [_k_] kill [_g_] quit [_b_] backward [_P_] bw up [_L_] barf [_(__{__[_] wrap (){}[] [_j_] join [_o_] convolute [_K_] bw kill [_q_] quit" ;; Moving ("a" sp-beginning-of-sexp) ("e" sp-end-of-sexp) ("f" sp-forward-sexp) ("b" sp-backward-sexp) ("n" sp-down-sexp) ("N" sp-backward-down-sexp) ("p" sp-up-sexp) ("P" sp-backward-up-sexp) ;; Slurping & barfing ("h" sp-backward-slurp-sexp) ("H" sp-backward-barf-sexp) ("l" sp-forward-slurp-sexp) ("L" sp-forward-barf-sexp) ;; Wrapping ("R" sp-rewrap-sexp) ("u" sp-unwrap-sexp) ("U" sp-backward-unwrap-sexp) ("(" sp-wrap-round) ("{" sp-wrap-curly) ("[" sp-wrap-square) ;; Sexp juggling ("S" sp-split-sexp) ("s" sp-splice-sexp) ("r" sp-raise-sexp) ("j" sp-join-sexp) ("t" sp-transpose-sexp) ("A" sp-absorb-sexp) ("E" sp-emit-sexp) ("o" sp-convolute-sexp) ;; Destructive editing ("c" sp-change-inner :exit t) ("C" sp-change-enclosing :exit t) ("k" sp-kill-sexp) ("K" sp-backward-kill-sexp) ("w" sp-copy-sexp) ("q" nil) ("g" nil)) :config (require 'smartparens-config) (show-smartparens-global-mode t) (defun sp-wrap-double-quotes (&optional arg) (interactive "P") (sp-wrap-with-pair "\"")) (defun sp-wrap-single-quotes (&optional arg) (interactive "P") (sp-wrap-with-pair "\'")) (setq sp-ignore-modes-list (delete 'minibuffer-inactive-mode sp-ignore-modes-list)) (sp-local-pair 'minibuffer-inactive-mode "\'" nil :actions nil) :general (prog-mode-map "C-c p" 'hydra-smartparens/body) (normal prog-mode-map "g p" 'hydra-smartparens/body) (normal "g[" 'sp-wrap-square) (normal "g(" 'sp-wrap-round) (normal "g{" 'sp-wrap-curly) (normal "g\"" 'sp-wrap-double-quotes) (normal "g\'" 'sp-wrap-single-quotes)) (use-package evil-smartparens :after (evil smartparens) :hook ((smartparens-enabled . evil-smartparens-mode))) (jdormit/define-prefix "l" "lisp") (jdormit/define-prefix "lw" "wrap") (leader-def-key "lwr" 'sp-wrap-round) (leader-def-key "lws" 'sp-wrap-square) (leader-def-key "lwc" 'sp-wrap-curly) (leader-def-key "ls" 'sp-forward-slurp-sexp) (leader-def-key "lb" 'sp-forward-barf-sexp) #+END_SRC Enable ES6 arrow functions in web-mode ("borrowed" from [[https://github.com/Fuco1/smartparens/issues/823#issuecomment-403019519][this GitHub comment]]): #+BEGIN_SRC emacs-lisp (with-eval-after-load 'smartparens (defun sp-after-equals-p (_id action _context) (when (memq action '(insert navigate)) (sp--looking-back-p "=>" 2))) (defun sp-after-equals-skip-p (ms mb _me) (when (eq ms ">") (save-excursion (goto-char mb) (sp--looking-back-p "=" 1)))) (sp-local-pair '(web-mode) "<" nil :unless '(:add sp-after-equals-p) :skip-match 'sp-after-equals-skip-p)) #+END_SRC Parinfer infers parens from indentation and vice-versa. Currently disabled since it turned out to be more annoying than good... #+BEGIN_SRC emacs-lisp (use-package parinfer :disabled :init (leader-def-key "lt" 'parinfer-toggle-mode) (setq parinfer-extensions '(defaults pretty-parens evil smart-tab smart-yank)) :hook ((clojure-mode . parinfer-mode) (emacs-lisp-mode . parinfer-mode) (common-lisp-mode . parinfer-mode) (scheme-mode . parinfer-mode) (lisp-mode . parinfer-mode))) #+END_SRC * jq The JSON multitool. #+BEGIN_SRC emacs-lisp (use-package jq-mode :commands (jq-mode jq-interactively)) #+END_SRC * link-hint A very helpful package that provides jump-to-link functionality: #+BEGIN_SRC emacs-lisp (use-package link-hint :commands (link-hint-open-link link-hint-copy-link) :init (jdormit/define-prefix "ol" "link-hint") (leader-def-key "oll" #'link-hint-open-link) (leader-def-key "olc" #'link-hint-copy-link)) #+END_SRC * Projectile #+BEGIN_SRC emacs-lisp (use-package projectile :hook ((after-init . projectile-mode)) :commands (projectile-mode projectile-find-file projectile-grep projectile-switch-project projectile-project-root) :config (jdormit/define-prefix "p" "projectile") (leader-def-key "p" projectile-command-map)) (defmacro with-projectile-root (&rest body) `(with-temp-buffer (when (projectile-project-root) (cd (projectile-project-root))) ,@body)) #+END_SRC * Perspective A package that groups buffers/windows into workspaces per project: #+BEGIN_SRC emacs-lisp (use-package perspective :commands (persp-mode) :hook ((after-init . persp-mode)) :init (jdormit/define-prefix "v" "perspective") :config (leader-def-key "v" perspective-map) (defun switch-to-previous-buffer () "Switch to previously open buffer. Repeated invocations toggle between the two most recently open buffers." (interactive) (persp-switch-to-buffer (other-buffer (current-buffer) 1))) :custom (persp-sort 'created) :general ([remap kill-buffer] #'persp-kill-buffer*) ([remap switch-to-buffer] #'persp-switch-to-buffer*) ([remap counsel-switch-buffer] #'persp-counsel-switch-buffer) ([remap ivy-switch-buffer] #'persp-ivy-switch-buffer)) (use-package persp-projectile :after (perspective projectile) :demand t :config (projectile-persp-bridge counsel-projectile) (projectile-persp-bridge counsel-projectile-switch-project) :general ([remap projectile-switch-project] #'projectile-persp-switch-project)) #+END_SRC * Mode line * UI Get rid of the janky buttons: #+BEGIN_SRC emacs-lisp (tool-bar-mode -1) #+END_SRC And the menu bar: #+BEGIN_SRC emacs-lisp (menu-bar-mode -1) #+END_SRC And the ugly scroll bars: #+BEGIN_SRC emacs-lisp (set-scroll-bar-mode nil) #+END_SRC Use =variable-pitch-mode= in text modes: #+BEGIN_SRC emacs-lisp (add-hook 'text-mode-hook (lambda () (variable-pitch-mode))) (add-hook 'w3m-mode-hook (lambda () (variable-pitch-mode))) #+END_SRC Always use =buffer-face-mode= in code and text buffers: #+BEGIN_SRC emacs-lisp (add-hook 'prog-mode-hook #'buffer-face-mode) (add-hook 'text-mode-hook #'buffer-face-mode) #+END_SRC Display the column number in programming modes: #+BEGIN_SRC emacs-lisp (add-hook 'prog-mode-hook #'column-number-mode) #+END_SRC Disable the bell: #+BEGIN_SRC emacs-lisp (setq ring-bell-function 'ignore) #+END_SRC Render stuff differently based on whether or not we are graphical: #+BEGIN_SRC emacs-lisp (defun graphical-setup () (when (display-graphic-p (selected-frame)) (message "Running graphically"))) (defun non-graphical-setup () (when (not (display-graphic-p (selected-frame))) (message "Running in terminal") (menu-bar-mode -1))) (defun do-graphical-non-graphical-setup () (graphical-setup) (non-graphical-setup)) (add-hook 'window-setup-hook #'do-graphical-non-graphical-setup) #+END_SRC Try to make the background normal colored in the terminal: #+BEGIN_SRC emacs-lisp (defvar no-background-in-tty-faces '(default line-number magit-section-highlight)) (defun on-frame-open (frame) (unless (display-graphic-p frame) (menu-bar-mode -1) (dolist (face no-background-in-tty-faces) (set-face-background face "unspecified" frame)))) (mapc #'on-frame-open (frame-list)) (add-hook 'after-make-frame-functions #'on-frame-open) (defun on-after-init () (unless (display-graphic-p (selected-frame)) (dolist (face no-background-in-tty-faces) (set-face-background face "unspecified" (selected-frame))))) (add-hook 'window-setup-hook #'on-after-init) #+END_SRC UI-related keybindings: #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "u" "UI") (leader-def-key "ut" #'customize-themes) (leader-def-key "uf" #'customize-face) (leader-def-key "uc" #'display-time-mode) (leader-def-key "ub" #'display-battery-mode) #+END_SRC * Window handling Following [[https://github.com/nex3/perspective-el#some-musings-on-emacs-window-layouts][some excellent advice]] from the author of perspective.el about making Emacs' window handling saner: #+BEGIN_SRC emacs-lisp (setq display-buffer-alist '((".*" (display-buffer-reuse-window display-buffer-same-window)))) (setq display-buffer-reuse-frames t) ; reuse windows in other frames (setq even-window-sizes nil) ; display-buffer: avoid resizing #+END_SRC * Centaur tabs [[https://github.com/ema2159/centaur-tabs][Centaur tabs]] is a package that gives Emacs buffer tabs similar to those in Atom or VS Code: #+BEGIN_SRC emacs-lisp (use-package centaur-tabs :commands (centaur-tabs-mode centaur-tabs-local-mode centaur-tabs-mode-on-p) :init (setq centaur-tabs-set-icons t centaur-tabs-gray-out-icons 'buffer centaur-tabs-height 30 centaur-tabs-set-bar 'under x-underline-at-descent-line t centaur-tabs-set-modified-marker t centaur-tabs-show-navigation-buttons t centaur-tabs-down-tab-text " ☰ " centaur-tabs-backward-tab-text " ◀ " centaur-tabs-forward-tab-text " ▶ " centaur-tabs-close-button "✕" centaur-tabs-modified-marker "⬤" centaur-tabs-cycle-scope 'tabs centaur-tabs-label-fixed-length 20) (leader-def-key "uT" #'centaur-tabs-mode) (centaur-tabs-mode) :config (centaur-tabs-group-by-projectile-project) ;; Custom buffer groups (defun centaur-tabs-projectile-buffer-groups () "Return the list of group names BUFFER belongs to." (if centaur-tabs-projectile-buffer-group-calc (symbol-value 'centaur-tabs-projectile-buffer-group-calc) (set (make-local-variable 'centaur-tabs-projectile-buffer-group-calc) (cond ((or (get-buffer-process (current-buffer)) (memq major-mode '(comint-mode compilation-mode))) '("Term")) ((string-equal "*" (substring (buffer-name) 0 1)) '("Misc")) ((condition-case _err (projectile-project-root) (error nil)) (list (projectile-project-name))) ((memq major-mode '(emacs-lisp-mode python-mode emacs-lisp-mode c-mode c++-mode javascript-mode js-mode js2-mode makefile-mode lua-mode vala-mode)) '("Coding")) ((memq major-mode '(nxhtml-mode html-mode mhtml-mode css-mode)) '("HTML")) ((memq major-mode '(org-journal-mode)) '("Journal")) ((memq major-mode '(org-mode calendar-mode diary-mode)) '("Org")) ((memq major-mode '(dired-mode)) '("Dir")) (t '("Other")))) (symbol-value 'centaur-tabs-projectile-buffer-group-calc))) ;; Don't show tabs for certain types of buffers (advice-add 'centaur-tabs-hide-tab :around (lambda (oldfn buf &rest args) (if (with-current-buffer buf (or (eq major-mode 'vuiet-mode) (eq major-mode 'dired-sidebar-mode))) t (apply oldfn buf args)))) ;; Only show tabs in buffers visiting files (advice-add 'centaur-tabs-line :around (lambda (oldfn &rest args) (if (buffer-file-name) (apply oldfn args) (setq header-line-format nil)))) ;; Enable prefix argument for tab switching keybindings (advice-add 'centaur-tabs-forward :around (lambda (oldfn &rest args) (if (numberp current-prefix-arg) (dotimes (_ current-prefix-arg) (apply oldfn args)) (apply oldfn args)))) (advice-add 'centaur-tabs-backward :around (lambda (oldfn &rest args) (if (numberp current-prefix-arg) (dotimes (_ current-prefix-arg) (apply oldfn args)) (apply oldfn args)))) ;; Use Org-mode titles for tab names when possible (advice-add 'centaur-tabs-buffer-tab-label :around (lambda (oldfn tab &rest args) (if-let ((title (or (and (fboundp 'org-roam-db--get-titles) (car (org-roam-db--get-titles (buffer-file-name (car tab))))) (org-get-title (with-current-buffer (car tab) (buffer-substring (point-min) (min (point-max) 200))))))) (if (> centaur-tabs-label-fixed-length 0) (centaur-tabs-truncate-string centaur-tabs-label-fixed-length (format " %s" title)) (format " %s" title)) (apply oldfn tab args)))) ;; Add a cache to speed up icon rendering for huge groups (defvar centaur-tabs-icon-cache (make-hash-table :test 'equal) "A cache holding icons generated for centaur-tabs mode tabs.") (advice-add 'centaur-tabs-icon :around (lambda (oldfn tab face selected &rest args) (let ((key (list tab face selected))) (or (gethash key centaur-tabs-icon-cache) (puthash key (apply oldfn tab face selected args) centaur-tabs-icon-cache))))) :general ((normal motion visual) "g t" #'centaur-tabs-forward) ((normal motion visual) "g T" #'centaur-tabs-backward) (leader-map "pt" #'centaur-tabs-counsel-switch-group) :hook (git-commit-mode . (lambda () (when (centaur-tabs-mode-on-p) (centaur-tabs-local-mode))))) #+END_SRC * Frame parameters Functions to change the frame size: #+BEGIN_SRC emacs-lisp (defun jdormit/set-frame-size (width height) (interactive "nWidth: \nnHeight: ") (if (display-graphic-p) (set-frame-size (selected-frame) width height) (message "Not running graphically"))) (defun jdormit/set-frame-width (width) (interactive "nWidth: ") (jdormit/set-frame-size width (frame-height))) (defun jdormit/set-frame-height (height) (interactive "nHeight: ") (jdormit/set-frame-size (frame-width) height)) #+END_SRC Keybindings: #+BEGIN_SRC emacs-lisp (leader-def-key "Fw" 'jdormit/set-frame-width) (leader-def-key "Fh" 'jdormit/set-frame-height) (leader-def-key "Fs" 'jdormit/set-frame-size) #+END_SRC * Transpose Frame A handy utility that reverse the current frame window split (vertical to horizontal or vice-versa): #+BEGIN_SRC emacs-lisp (use-package transpose-frame :defer t :init (leader-def-key "wt" 'transpose-frame)) #+END_SRC * EShell Easy keybinding to open EShell: #+BEGIN_SRC emacs-lisp (defun open-eshell (&optional arg) (interactive "P") (if (and (fboundp 'projectile-project-root) (projectile-project-root)) (projectile-run-eshell arg) (eshell arg))) (leader-def-key "'" 'open-eshell) #+END_SRC Make EShell's tab completion work like Bash's: #+BEGIN_SRC emacs-lisp (setq eshell-cmpl-cycle-completions nil) #+END_SRC Add additional useful modules: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'esh-module (add-to-list 'eshell-modules-list 'eshell-tramp)) (with-eval-after-load 'eshell (require 'esh-module)) #+END_SRC Prefer Lisp commands to external programs: #+BEGIN_SRC emacs-lisp (setq eshell-prefer-lisp-functions t eshell-prefer-lisp-variables t) #+END_SRC Enable password caching: #+BEGIN_SRC emacs-lisp (setq password-cache t password-cache-expiry 120) #+END_SRC Destroy shell buffers created by eshell when the process dies:: #+BEGIN_SRC emacs-lisp (setq eshell-destroy-buffer-when-process-dies t) #+END_SRC Visual programs: #+BEGIN_SRC emacs-lisp (defun eshell-setup () (add-to-list 'eshell-visual-commands "crawl") (add-to-list 'eshell-visual-commands "ssh") (add-to-list 'eshell-visual-commands "watch") (add-to-list 'eshell-visual-subcommands '("kubectl" "exec")) (add-to-list 'eshell-visual-subcommands '("k" "exec")) (add-to-list 'eshell-visual-subcommands '("docker" "build")) (add-to-list 'eshell-visual-subcommands '("docker" "push"))) (add-hook 'eshell-mode-hook #'eshell-setup) #+END_SRC And a function to run any program visually: #+BEGIN_SRC emacs-lisp (defun eshell/v (&rest args) (apply #'eshell-exec-visual args)) #+END_SRC Load .dir-locals.el when switching directories: #+BEGIN_SRC emacs-lisp (add-hook 'eshell-mode-hook #'hack-dir-local-variables-non-file-buffer) (add-hook 'eshell-after-prompt-hook #'hack-dir-local-variables-non-file-buffer) #+END_SRC A function to properly clear the eshell: #+BEGIN_SRC emacs-lisp (defun clear-eshell (&optional prefix) (interactive) (let ((input (eshell-get-old-input))) (eshell/clear-scrollback) (eshell-emit-prompt) (insert input))) (add-hook 'eshell-mode-hook (lambda () (general-def eshell-mode-map "C-c C-o" #'clear-eshell))) #+END_SRC Compilation-shell-minor-mode: #+BEGIN_SRC emacs-lisp (add-hook 'eshell-mode-hook #'compilation-shell-minor-mode) #+END_SRC ** Prompt #+BEGIN_SRC emacs-lisp (defun jdormit-eshell-prompt () (let ((branch (magit-name-local-branch "HEAD"))) (format "%s%s" (if branch (format "(%s) " branch) "") (concat (abbreviate-file-name (eshell/pwd)) " " (propertize (if (= (user-uid) 0) "#" "λ") 'face `(:foreground "#859900")) " ")))) (setq jdormit-eshell-prompt-regex "^[^#λ\n]* [#λ] ") (setq eshell-prompt-function 'jdormit-eshell-prompt) (setq eshell-prompt-regexp jdormit-eshell-prompt-regex) #+END_SRC * Flycheck Syntax checking etc.: #+BEGIN_SRC emacs-lisp (use-package flycheck :init (defhydra hydra-flycheck (:pre (flycheck-list-errors) :post (quit-windows-on "*Flycheck errors*") :hint nil) "Errors" ("f" flycheck-error-list-set-filter "Filter") ("j" flycheck-next-error "Next") ("k" flycheck-previous-error "Previous") ("gg" flycheck-first-error "First") ("G" (progn (goto-char (point-max)) (flycheck-previous-error)) "Last") ("q" nil)) :config (setq-default flycheck-disabled-checkers '(emacs-lisp emacs-lisp-checkdoc)) (global-flycheck-mode) :general ((normal motion visual) flycheck-mode-map "ze" 'hydra-flycheck/body)) #+END_SRC * Tabs #+BEGIN_SRC emacs-lisp (defun disable-tab-insert () (interactive) (setq indent-tabs-mode nil)) #+END_SRC * aggressive-indent-mode Like [[help:electric-indent-mode][electric-indent-mode]] but reindents after every change: #+BEGIN_SRC emacs-lisp (use-package aggressive-indent :hook ((clojure-mode . aggressive-indent-mode) (emacs-lisp-mode . aggressive-indent-mode) (lisp-mode . aggressive-indent-mode) (scheme-mode . aggressive-indent-mode))) #+END_SRC * Indentation guides #+BEGIN_SRC emacs-lisp (use-package highlight-indent-guides :commands highlight-indent-guides-mode :hook ((python-mode . highlight-indent-guides-mode) (yaml-mode . highlight-indent-guides-mode)) :init (leader-def-key "th" #'highlight-indent-guides-mode) :custom (highlight-indent-guides-method 'character) (highlight-indent-guides-auto-character-face-perc 7) (highlight-indent-guides-responsive 'stack) (highlight-indent-guides-auto-stack-character-face-perc 10)) #+END_SRC * JSON #+BEGIN_SRC emacs-lisp (use-package json-mode :mode (("\\.json\\'" . json-mode)) :config (setq js-indent-level 2)) (use-package json-navigator :commands (json-navigator-navigator json-navigator-navigate-after-point json-navigator-navigate-region)) (use-package tree-mode :general (normal tree-mode-map "D" #'tree-mode-delete-tree "k" #'tree-mode-previous-node "j" #'tree-mode-next-node "l" #'tree-mode-next-sib "h" #'tree-mode-previous-sib "u" #'tree-mode-goto-parent "r" #'tree-mode-goto-root "gr" #'tree-mode-reflesh "E" #'tree-mode-expand-level "e" #'tree-mode-toggle-expand "s" #'tree-mode-sort-by-tag "/" #'tree-mode-keep-match "!" #'tree-mode-collapse-other-except) :hook ((json-navigator-mode . tree-minor-mode))) (defun json-pprint () (interactive) (let ((begin (if (region-active-p) (region-beginning) (point-min))) (end (if (region-active-p) (region-end) (point-max)))) (if (executable-find "jq") (shell-command-on-region begin end "jq ." nil t) (json-pretty-print begin end)))) (general-def json-mode-map "C-M-\\" 'json-pprint) #+END_SRC * JavaScript Some formatting stuff: #+BEGIN_SRC emacs-lisp (setq js-indent-level 4) #+END_SRC #+BEGIN_SRC emacs-lisp (use-package web-mode :mode (("\\.html\\'" . web-mode) ("\\.js\\'" . web-mode) ("\\.jsx\\'" . web-mode) ("\\.mako\\'" . web-mode) ("\\.jinja2\\'" . web-mode) ("\\.hbs\\'" . web-mode)) :config (setq web-mode-engines-alist '(("django" . "\\.jinja2\\'"))) (add-hook 'web-mode-hook (lambda () (when (equal web-mode-content-type "javascript") (web-mode-set-content-type "jsx")) (when (or (equal web-mode-content-type "javascript") (equal web-mode-content-type "jsx")) (lsp-deferred)))) (add-hook 'web-mode-hook #'disable-tab-insert) :custom (web-mode-enable-auto-pairing nil)) #+END_SRC Use nvm to manage node versions: #+BEGIN_SRC emacs-lisp (use-package nvm :straight (nvm :host github :repo "rejeep/nvm.el") :commands (nvm-use nvm-use-for nvm-use-for-buffer)) #+END_SRC A command to format JS via prettier: #+BEGIN_SRC emacs-lisp (defun prettier () (interactive) (let ((start (if (use-region-p) (region-beginning) (point-min))) (end (if (use-region-p) (region-end) (point-max))) (parser (cond ((eq major-mode 'graphql-mode) "graphql") (t "babel")))) (shell-command-on-region start end (concat "prettier --parser " parser) nil t))) #+END_SRC ** NVM Manage node version via NVM within Emacs: #+BEGIN_SRC emacs-lisp (use-package nvm :commands (nvm-use nvm-use-for nvm-use-for-buffer nvm--installed-versions) :init (defun nvm (version) (interactive (list (completing-read "Node version: " (mapcar #'car (nvm--installed-versions))))) (nvm-use version))) #+END_SRC * Java #+BEGIN_SRC emacs-lisp (use-package lsp-java :hook ((java-mode . lsp-deferred))) #+END_SRC * Kotlin #+BEGIN_SRC emacs-lisp (use-package kotlin-mode :mode (("\\.kt\\'" . kotlin-mode)) :config (with-eval-after-load 'lsp (when (executable-find "kotlin-language-server") (add-hook 'kotlin-mode-hook #'lsp-deferred)))) #+END_SRC * Groovy Used for Jenkins configuration scripts and probably other things. #+BEGIN_SRC emacs-lisp (use-package groovy-mode :commands (groovy-mode) :mode (("\\.groovy\\'" . groovy-mode)) :init (add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode)) :custom (groovy-indent-offset 2)) #+END_SRC * Typescript #+BEGIN_SRC emacs-lisp (use-package typescript-mode :mode ("\\.ts\\'") :config (with-eval-after-load 'lsp (add-hook 'typescript-mode-hook 'lsp-deferred))) #+END_SRC * LSP Mode Emacs support for the Language Server Protocol #+BEGIN_SRC emacs-lisp (use-package lsp-mode :defer t :init (defhydra hydra-lsp (:exit t :hint nil) " Buffer^^ Server^^ Symbol ------------------------------------------------------------------------------------- [_f_] format [_M-r_] restart [_d_] declaration [_i_] implementation [_o_] documentation [_m_] imenu [_S_] shutdown [_D_] definition [_t_] type [_r_] rename [_x_] execute action [_M-s_] describe session [_R_] references [_s_] signature" ("d" lsp-find-declaration) ("D" lsp-ui-peek-find-definitions) ("R" lsp-ui-peek-find-references) ("i" lsp-ui-peek-find-implementation) ("t" lsp-find-type-definition) ("s" lsp-signature-help) ("o" lsp-describe-thing-at-point) ("r" lsp-rename) ("f" lsp-format-buffer) ("m" lsp-ui-imenu) ("x" lsp-execute-code-action) ("M-s" lsp-describe-session) ("M-r" lsp-restart-workspace) ("S" lsp-shutdown-workspace)) :general (lsp-mode-map "C-c h" 'hydra-lsp/body) ((normal visual motion) lsp-mode-map "K" #'lsp-describe-thing-at-point) :hook ((lsp-mode . (lambda () (let ((lsp-keymap-prefix "gl")) (lsp-enable-which-key-integration))))) :config (setq lsp-prefer-flymake nil) (general-def '(normal visual motion) "gl" lsp-command-map) :commands lsp-mode lsp lsp-deferred :custom (lsp-enable-snippet nil) (lsp-eldoc-render-all nil) (lsp-headerline-breadcrumb-enable nil) (lsp-file-watch-threshold 100000)) (use-package lsp-ui :after (lsp-mode) :custom (lsp-ui-sideline-enable t) (lsp-ui-sideline-show-symbol t) (lsp-ui-sideline-show-hover t) (lsp-ui-sideline-show-code-actions t) (lsp-ui-sideline-update-mode 'point) (lsp-ui-doc-alignment 'window) (lsp-ui-doc-header t) (lsp-ui-doc-position 'top) (lsp-ui-doc-background '((t (:inherit region)))) (lsp-ui-doc-header '((t (:inherit lsp-face-highlight-write)))) (lsp-ui-sideline-current-symbol '((t (:inherit font-lock-constant-face :weight ultra-bold))))) (with-eval-after-load 'lsp-clients (defun lsp-typescript-javascript-tsx-jsx-activate-p (filename major-mode) "Checks if the javascript-typescript language server should be enabled based on FILE-NAME and MAJOR-MODE" (or (member major-mode '(typescript-mode typescript-tsx-mode js-mode js2-mode rjsx-mode)) (and (eq major-mode 'web-mode) (or (string-suffix-p ".tsx" filename t) (string-suffix-p ".jsx" filename t) (string-suffix-p ".js" filename t)))))) #+END_SRC * Python ** General #+BEGIN_SRC emacs-lisp (leader-def-key "sp" #'run-python) (add-hook 'python-mode-hook #'disable-tab-insert) #+END_SRC ISort is a Python utility to sort imports: #+BEGIN_SRC emacs-lisp (use-package py-isort :commands (py-isort-buffer py-isort-region) :config (setq py-isort-options '("-m=3")) :general (python-mode-map "C-c C-i" #'py-isort-buffer)) #+END_SRC Run black on the current buffer: #+BEGIN_SRC emacs-lisp (general-def 'normal python-mode-map "C-M-\\" #'format-all-buffer) #+END_SRC [[https://github.com/naiquevin/sphinx-doc.el][sphinx-doc.el]] automatically generates doc strings for Python functions (and updates existing ones!): #+BEGIN_SRC emacs-lisp (use-package sphinx-doc :hook ((python-mode . sphinx-doc-mode))) #+END_SRC ** Dev environment/IDE stuff Support pyvenv within Emacs: #+BEGIN_SRC emacs-lisp (use-package pyvenv :defer 0 :commands (pyvenv-mode pyvenv-tracking-mode pyvenv-workon pyvenv-activate pyvenv-track-virtualenv) :config (pyvenv-mode) (pyvenv-tracking-mode)) (defun eshell/workon (name) (pyvenv-workon name)) (defun eshell/activate (dir) (pyvenv-activate dir)) (defun eshell/deactivate () (pyvenv-deactivate)) #+END_SRC And support pyenv (NOT pyvenv) to change Python versions: #+BEGIN_SRC emacs-lisp (use-package pyenv-mode :defer t) #+END_SRC Use the LSP python client: #+BEGIN_SRC emacs-lisp (use-package lsp-pyright :hook (python-mode . (lambda () (require 'lsp-pyright) (lsp-deferred))) :custom (lsp-pyright-use-library-code-for-types t) :general (python-mode-map "C-c C-d" #'lsp-describe-thing-at-point) ('normal python-mode-map "K" #'lsp-describe-thing-at-point)) #+END_SRC Override the flycheck python-mypy checker to run in the right directory: #+BEGIN_SRC emacs-lisp (flycheck-define-checker python-mypy "Mypy syntax and type checker. Requires mypy>=0.580. See URL `http://mypy-lang.org/'." :command ("mypy" "--show-column-numbers" (config-file "--config-file" flycheck-python-mypy-config) (option "--cache-dir" flycheck-python-mypy-cache-dir) source-original) :error-patterns ((error line-start (file-name) ":" line (optional ":" column) ": error:" (message) line-end) (warning line-start (file-name) ":" line (optional ":" column) ": warning:" (message) line-end) (info line-start (file-name) ":" line (optional ":" column) ": note:" (message) line-end)) :modes python-mode ;; Ensure the file is saved, to work around ;; https://github.com/python/mypy/issues/4746. :predicate flycheck-buffer-saved-p :working-directory (lambda (checker) (projectile-compilation-dir))) #+END_SRC ** Autoflake [[https://pypi.org/project/autoflake/][Autoflake]] is a tool that removes unused imports and variables from Python code: #+BEGIN_SRC emacs-lisp (defvar autoflake-args '() "Arguments to pass to the autoflake command. See URL `https://pypi.org/project/autoflake' for options.") (defun autoflake (arg) (interactive "P") (let ((autoflake-cmd (or (executable-find "autoflake") (error "Autoflake executable not found"))) (file (cond ((or arg (not buffer-file-name)) (read-file-name "Run autoflake on file: ")) (buffer-file-name buffer-file-name) (:else (error "Invalid file for autoflake"))))) (apply #'call-process autoflake-cmd nil nil nil (append autoflake-args (list "--in-place" file))))) #+END_SRC ** Testing [[https://github.com/wbolster/emacs-python-pytest][python-pytest.el]] integrates Pytest with Emacs: #+BEGIN_SRC emacs-lisp (use-package python-pytest :commands (python-pytest-popup python-pytest--current-defun) :general (python-mode-map "C-c t p" #'python-pytest-popup) ((normal motion visual) python-pytest-mode-map "g r" #'python-pytest-repeat) ((normal motion visual) python-pytest-mode-map "q" #'quit-window)) #+END_SRC And borrowing some functions from python-pytest, we can get some nosetests support as well: #+BEGIN_SRC emacs-lisp (defvar nosetests-args "" "Additional args to pass to nosetests") (defun run-nose (args &optional debug) "Runs nosetests with `args' If `debug' is non-nil, run in a GUD PDB session." (let* ((nosetests-cmd (executable-find "nosetests")) (cmdline (format "%s %s" nosetests-cmd args))) (when (not nosetests-cmd) (user-error "Nosetests command not found")) (if debug (realgud:pdb cmdline) (compile cmdline)))) (defun run-nose-reading-args (arg nose-args &optional debug) "Runs nosetests with default args or prompts for args with prefix If `debug' is non-nil, run in a GUD PDB session." (let ((args (if arg (read-string "Nosetests arguments: " nil nil nosetests-args) nosetests-args))) (run-nose (format "%s %s" args nose-args) debug))) (defun run-nose-in-project (arg nose-args &optional debug) "Runs nosetests from the project root If `debug' is non-nil, run in a GUD PDB session." (let ((dir (or (projectile-project-root) default-directory))) (with-temp-buffer (cd dir) (run-nose-reading-args arg nose-args debug)))) (defun nosetests-all (arg) "Runs nosetests on all project tests, prompting for the tests directory" (interactive "P") (let ((test-dir (read-file-name "Test directory: " (or (projectile-project-root) default-directory) nil t "tests" #'directory-name-p))) (run-nose-in-project arg (directory-file-name test-dir)))) (defun nosetests-debug-all (arg) "Runs nosetests in a GUD session on all project tests" (interactive "P") (let ((test-dir (read-file-name "Test directory: " (or (projectile-project-root) default-directory) nil t "tests" #'directory-name-p))) (run-nose-in-project arg (directory-file-name test-dir) t))) (defun nosetests-module (arg module) "Runs nosetests in the module of the current file" (interactive (list current-prefix-arg (read-file-name "Run nosetests on module: " nil nil t (file-name-directory (buffer-file-name)) #'directory-name-p))) (run-nose-in-project arg (directory-file-name module))) (defun nosetests-debug-module (arg module) "Runs nosetests in a GUD session in the module of the current file" (interactive (list current-prefix-arg (read-file-name "Run nosetests on module: " nil nil t (file-name-directory (buffer-file-name)) #'directory-name-p))) (run-nose-in-project arg (directory-file-name module) t)) (defun nosetests-file (arg file) "Runs nosetests on the current file" (interactive (list current-prefix-arg (read-file-name "Run nosetests on file: " nil nil t (buffer-file-name)))) (run-nose-in-project arg file)) (defun nosetests-debug-file (arg file) "Runs nosetests in a GUD session on the current file" (interactive (list current-prefix-arg (read-file-name "Run nosetests on file: " nil nil t (buffer-file-name)))) (run-nose-in-project arg file t)) (defun nosetests-def (arg def) "Runs nosetests on the enclosing function at point" (interactive (list current-prefix-arg (python-pytest--current-defun))) (run-nose-in-project arg (format "%s:%s" (buffer-file-name) def))) (defun nosetests-debug-def (arg def) "Runs nosetests in a GUD session on the enclosing function at point" (interactive (list current-prefix-arg (python-pytest--current-defun))) (run-nose-in-project arg (format "%s:%s" (buffer-file-name) def) t)) (defvar nosetests-map (make-sparse-keymap) "Keymap for nosetests") (defvar nosetests-debug-map (make-sparse-keymap) "Keymap for debugging nosetests") (general-def python-mode-map "C-c t n" nosetests-map) (general-def nosetests-map "a" #'nosetests-all) (general-def nosetests-map "m" #'nosetests-module) (general-def nosetests-map "f" #'nosetests-file) (general-def nosetests-map "d" #'nosetests-def) (general-def nosetests-map "b" nosetests-debug-map) (general-def nosetests-debug-map "a" #'nosetests-debug-all) (general-def nosetests-debug-map "m" #'nosetests-debug-module) (general-def nosetests-debug-map "f" #'nosetests-debug-file) (general-def nosetests-debug-map "d" #'nosetests-debug-def) (which-key-add-major-mode-key-based-replacements 'python-mode "C-c t n" "nosetests") (which-key-add-major-mode-key-based-replacements 'python-mode "C-c t n b" "debug") #+END_SRC * Hy Python but Lispy! #+BEGIN_SRC emacs-lisp (defun run-hy () (interactive) (run-lisp (expand-file-name "~/.virtualenvs/hy/bin/hy"))) #+END_SRC * Go Basic support: #+BEGIN_SRC emacs-lisp (use-package go-mode :mode (("\\.go\\'" . go-mode))) #+END_SRC LSP support - requires [[https://github.com/sourcegraph/go-langserver][go-langserver]]. #+BEGIN_SRC emacs-lisp (add-hook 'go-mode-hook #'lsp-deferred) #+END_SRC * Clojure Start with clojure-mode: #+BEGIN_SRC emacs-lisp (use-package clojure-mode :mode (("\\.clj\\'" . clojure-mode) ("\\.cljs\\'" . clojurescript-mode) ("\\.cljc\\'" . clojurec-mode) ("\\.edn\\'" . clojure-mode)) :config (define-clojure-indent (defroutes 'defun) (GET 2) (POST 2) (PUT 2) (DELETE 2) (HEAD 2) (ANY 2) (OPTIONS 2) (PATCH 2) (rfn 2) (let-routes 1) (context 2) (:= 3) (:+ 3) (match 1) (for-all 2) (checking 2) (let-flow 1))) #+END_SRC Add flycheck support: #+BEGIN_SRC emacs-lisp (use-package flycheck-clj-kondo :after (clojure-mode) :if (executable-find "clj-kondo") :ensure t) #+END_SRC Sprinkle in some CIDER: #+BEGIN_SRC emacs-lisp (use-package cider :commands (cider-mode cider-jack-in cider-jack-in-clojurescript) :config (setq cider-known-endpoints '(("local" "localhost" "4005")) cider-prompt-for-symbol nil) (general-def cider-mode-map "C-c t" cider-test-commands-map) (add-hook 'cider-repl-mode-hook 'smartparens-strict-mode) :hook ((clojure-mode . cider-mode) (clojurescript-mode . cider-mode) (clojurec-mode . cider-mode)) :general (cider-stacktrace-mode-map "SPC" leader-map) ('normal cider-mode-map "M-." #'cider-find-var) (cider-repl-mode-map "C-c C-l" #'cider-repl-clear-buffer)) (defun jdormit/cider-setup () (local-set-key (kbd "C-c M-b") 'cider-debug-defun-at-point)) (add-hook 'cider-mode-hook 'jdormit/cider-setup) #+END_SRC Add some handy hydras: #+BEGIN_SRC emacs-lisp (use-package cider-hydra :after (cider) :hook ((cider-mode . cider-hydra-mode))) #+END_SRC Clj-refactor adds magical refactoring abilities: #+BEGIN_SRC emacs-lisp (use-package clj-refactor :init (defun clj-refactor-setup () (interactive) (clj-refactor-mode 1) (cljr-add-keybindings-with-prefix "C-c r")) :config (setq cljr-auto-sort-ns t) :hook ((clojure-mode . clj-refactor-setup))) #+END_SRC Add support for running Org-mode Clojure source blocks with [[https://github.com/borkdude/babashka][Babashka]]: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'ob-clojure (defcustom org-babel-clojure-backend nil "Backend used to evaluate Clojure code blocks." :group 'org-babel :type '(choice (const :tag "inf-clojure" inf-clojure) (const :tag "cider" cider) (const :tag "slime" slime) (const :tag "bb" bb) (const :tag "Not configured yet" nil))) (defun elisp->clj (in) (cond ((listp in) (concat "[" (s-join " " (mapcar #'elisp->clj in)) "]")) (t (format "%s" in)))) (defun ob-clojure-eval-with-bb (expanded params) "Evaluate EXPANDED code block with PARAMS using babashka." (unless (executable-find "bb") (user-error "Babashka not installed")) (let* ((stdin (let ((stdin (cdr (assq :stdin params)))) (when stdin (elisp->clj (org-babel-ref-resolve stdin))))) (input (cdr (assq :input params))) (file (make-temp-file "ob-clojure-bb" nil nil expanded)) (command (concat (when stdin (format "echo %s | " (shell-quote-argument stdin))) (format "bb %s -f %s" (cond ((equal input "edn") "") ((equal input "text") "-i") (t "")) (shell-quote-argument file)))) (result (shell-command-to-string command))) (s-trim result))) (defun org-babel-execute:clojure (body params) "Execute a block of Clojure code with Babel." (let* ((org-babel-clojure-backend (or (cdr (assq :backend params)) org-babel-clojure-backend)) (org-babel-clojure-backend (when org-babel-clojure-backend (intern org-babel-clojure-backend)))) (unless org-babel-clojure-backend (user-error "You need to customize org-babel-clojure-backend")) (let* ((expanded (org-babel-expand-body:clojure body params)) (result-params (cdr (assq :result-params params))) result) (setq result (cond ((eq org-babel-clojure-backend 'inf-clojure) (ob-clojure-eval-with-inf-clojure expanded params)) ((eq org-babel-clojure-backend 'cider) (ob-clojure-eval-with-cider expanded params)) ((eq org-babel-clojure-backend 'slime) (ob-clojure-eval-with-slime expanded params)) ((eq org-babel-clojure-backend 'bb) (ob-clojure-eval-with-bb expanded params)))) (org-babel-result-cond result-params result (condition-case nil (org-babel-script-escape result) (error result)))))) (customize-set-variable 'org-babel-clojure-backend 'bb)) (add-hook 'org-mode-hook (lambda () (require 'ob-clojure))) #+END_SRC * Scheme Tell emacs about file extensions which should activate scheme-mode: #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.guile\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.rkt\\'" . scheme-mode)) #+END_SRC [[http://www.nongnu.org/geiser/geiser_1.html][Geiser]] is a Scheme IDE for Emacs that supports a bunch of common Scheme implementations. #+BEGIN_SRC emacs-lisp (use-package geiser :commands (run-geiser) :config (setq geiser-active-implementations (cl-reduce (lambda (acc val) (if (executable-find (symbol-name val)) (cons val acc) acc)) '(guile racket chicken chez mit chibi gambit) :initial-value nil)) :general (geiser-mode-map 'normal "M-." 'geiser-edit-symbol-at-point "M-," 'geiser-pop-symbol-stack) (geiser-debug-mode-map "SPC" leader-map)) #+END_SRC And a handy shortcut to hop into a Geiser REPL: #+BEGIN_SRC emacs-lisp (leader-def-key "sg" 'run-geiser) #+END_SRC * Common Lisp [[https://common-lisp.net/project/slime/][SLIME]] is a set of modes and utilities for writing Common Lisp in Emacs. [[https://www.quicklisp.org/beta/][Quicklisp]] is the de-facto Common Lisp package manager. It comes with some Emacs bindings. #+BEGIN_SRC emacs-lisp (use-package slime-company :after (slime)) (use-package slime :commands (slime) :config (setq inferior-lisp-program (executable-find "sbcl") slime-contribs '(slime-repl slime-fancy slime-company)) (when (file-exists-p (expand-file-name "~/quicklisp/slime-helper.el")) (load (expand-file-name "~/quicklisp/slime-helper.el"))) (add-hook 'slime-repl-mode-hook 'smartparens-strict-mode) (add-to-list 'browse-url-browser-function '("lispworks.com/documentation/HyperSpec" . w3m-browse-url)) :general ((normal motion visual insert emacs) slime-mode-map "M-." #'slime-edit-definition)) (add-to-list 'auto-mode-alist '("\\.cl\\'" . lisp-mode)) #+END_SRC Keyboard shortcut to start a SLIME REPL: #+BEGIN_SRC emacs-lisp (leader-def-key "sc" 'slime) #+END_SRC * Haskell #+BEGIN_SRC emacs-lisp (defun jdormit/haskell-setup () (local-set-key (kbd "C-c M-j") 'interactive-haskell-mode)) (use-package haskell-mode :mode (("\\.hs\\'" . haskell-mode))) (add-hook 'haskell-mode-hook 'jdormit/haskell-setup) #+END_SRC * PHP #+BEGIN_SRC emacs-lisp (use-package php-mode :mode "\\.php\\'") (use-package mmm-mode :after php-mode) #+END_SRC Geben is an interface to XDebug allowing debugging PHP in Emacs: #+BEGIN_SRC emacs-lisp (use-package geben :commands (geben)) #+END_SRC Some keybindings to start and end Geben: #+BEGIN_SRC emacs-lisp (general-def php-mode-map "C-c C-d" #'geben) (general-def php-mode-map "C-c C-q" #'geben-end) #+END_SRC LSP for PHP requires [[https://github.com/felixfbecker/php-language-server][php-language-server]] to be installed in ~/.composer: #+BEGIN_SRC emacs-lisp (add-hook 'php-mode-hook #'lsp-deferred) #+END_SRC * YAML #+BEGIN_SRC emacs-lisp (use-package yaml-mode :mode ("//.yml//'")) #+END_SRC * Pharen [[https://pharen.org][Pharen]] is a Lisp that compiles to PHP. It looks a lot like Clojure. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.phn\\'" . clojure-mode)) #+END_SRC * Bash Use LSP if [[https://github.com/mads-hartmann/bash-language-server][bash-language-server]] is installed. #+BEGIN_SRC emacs-lisp (when (executable-find "bash-language-server") (add-hook 'sh-mode-hook #'lsp-deferred)) #+END_SRC * Ruby #+BEGIN_SRC emacs-lisp (add-hook 'ruby-mode-hook #'lsp-deferred) #+END_SRC * Rust #+BEGIN_SRC emacs-lisp (use-package rust-mode :mode "\\.rs\\'" :general (rust-mode-map "C-c " #'rust-format-buffer) :config (add-hook 'rust-mode-hook #'lsp-deferred)) (use-package cargo :after (rust-mode) :config (add-hook 'rust-mode-hook #'cargo-minor-mode)) #+END_SRC * Elixir #+BEGIN_SRC emacs-lisp (use-package elixir-mode :defer t) (use-package alchemist :hook ((elixir-mode . alchemist-mode) (alchemist-iex-mode . company-mode))) #+END_SRC * XML Set up hideshow for nXML mode: #+BEGIN_SRC emacs-lisp (add-hook 'nxml-mode-hook #'hs-minor-mode) (add-to-list 'hs-special-modes-alist '(nxml-mode "\\|]*[^/]>" ;; regexp for end block "