#+PROPERTY: header-args :results silent #+PROPERTY: header-args:emacs-lisp :lexical t This init file is based on [[https://medium.com/@CBowdon/pinching-the-best-bits-from-spacemacs-869b8c793ad3][this blog post]]. It's meant to be loaded from init.el like so: #+BEGIN_SRC emacs-lisp :tangle no (require 'org) (org-babel-load-file (expand-file-name "path/to/init.org")) #+END_SRC * Prelude Enables lexical binding for everything in init.el: #+BEGIN_SRC emacs-lisp ;;; -*- lexical-binding: t; -*- #+END_SRC Requires: #+BEGIN_SRC emacs-lisp (require 'json) #+END_SRC * Default directory #+BEGIN_SRC emacs-lisp (cd "~") #+END_SRC * Packages Use [[https://github.com/raxod502/straight.el][straight.el]] to manage packages: #+BEGIN_SRC emacs-lisp (defvar bootstrap-version) (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. `use-package-always-ensure` makes sure that all packages will be installed before loading is attempted. #+BEGIN_SRC emacs-lisp (straight-use-package 'use-package) (setq straight-use-package-by-default t) #+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 * 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")) (exec-path-from-shell-initialize)) #+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 Delete trailing whitespace on save: #+BEGIN_SRC emacs-lisp (add-hook 'before-save-hook #'delete-trailing-whitespace) #+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 :config (setq company-idle-delay 0.3) (add-hook 'after-init-hook #'global-company-mode)) (general-def "C-M-i" #'company-complete) (general-def "M-" #'company-complete) #+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 ** evil-collection A collection of evil bindings for various modes #+BEGIN_SRC emacs-lisp (use-package evil-collection :after (evil) :config (setq evil-collection-company-use-tng nil) (evil-collection-init)) #+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) (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 ** 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 * Magit Magit is objectively the best Git interface. #+BEGIN_SRC emacs-lisp (use-package magit :commands (magit-status magit-blame magit-find-file)) #+END_SRC #+BEGIN_SRC emacs-lisp (jdormit/define-prefix "g" "git") (leader-def-key "gs" #'magit-status) (leader-def-key "gg" #'magit-file-dispatch) (leader-def-key "gf" #'magit-find-file) #+END_SRC Use ido-mode for completion within Magit: #+BEGIN_SRC emacs-lisp ;; (setq magit-completing-read-function 'magit-ido-completing-read) #+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)) (add-to-list 'evil-emacs-state-modes 'forge-topic-list-mode) :general (forge-topic-list-mode-map "SPC" leader-map)) #+END_SRC ** evil-magit Evil keybindings for magit! #+BEGIN_SRC emacs-lisp (use-package evil-magit :after (evil magit forge) :general ('normal magit-mode-map "SPC" leader-map)) #+END_SRC ** Transient #+BEGIN_SRC emacs-lisp (setq transient-default-level 7) #+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 (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 :if (executable-find "pass") :commands (password-store-list password-store-get password-store-copy) :config (setq password-store-password-length 20) (leader-def-key "P" 'password-store-copy)) (use-package pass :if (executable-find "pass") :commands pass :general ('(normal motion visual) pass-mode-map "S" #'password-store-synchronize)) (leader-def-key "ap" #'pass) #+END_SRC * Emacs Lisp Requires: #+BEGIN_SRC emacs-lisp (eval-when-compile (require 'subr-x)) #+END_SRC ** 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) (use-package dash) (use-package dash-functional) (use-package f) (use-package request) #+END_SRC ** Editing Elisp #+BEGIN_SRC emacs-lisp (general-def '(normal motion) emacs-lisp-mode "C-c C-c" #'eval-defun :keymaps 'override) #+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 (defun extract-vars-from-env-file (file) "Extracts an alist of variable name to value from a bash script that exports environment variables." (let ((var-re "\\(.+?\\)=\\(.+\\)$") (env '())) (with-temp-buffer (shell-command (concat "source " file " > /dev/null && env") (current-buffer)) (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))))) (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))) (defun call-with-env-from-file (file callback) (let* ((env (extract-vars-from-env-file file)) (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 ** 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 * Dropbox I put lots of stuff in Dropbox, but the actual folder location differs on my different computers. This function resolves to the Dropbox directory: #+BEGIN_SRC emacs-lisp (defun get-dropbox-directory () (cond ((file-exists-p (expand-file-name "~/Dropbox")) (expand-file-name "~/Dropbox")) ((file-exists-p (expand-file-name "~/Dropbox (Personal)")) (expand-file-name "~/Dropbox (Personal)")))) #+END_SRC Load up libraries from Dropbox, if there are any: #+BEGIN_SRC emacs-lisp (add-to-list 'load-path (concat (file-name-as-directory (get-dropbox-directory)) "site-lisp")) #+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 "~/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 "fs" 'sudo-find-file) (leader-def-key "ft" 'auto-revert-tail-mode) #+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) #+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) #+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) #+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 * 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 * 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 * 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-neotree () (when (string-match-p (buffer-name) ".*\\NeoTree\\*.*") 10)) (use-package winum :config (winum-mode) (add-to-list 'winum-assign-functions #'winum-assign-0-to-neotree) (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)) #+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 * NeoTree A package to browse files in a tree view #+BEGIN_SRC emacs-lisp (use-package neotree :commands (neotree-project-dir neotree-toggle) :init (leader-def-key "d" #'neotree-toggle) :general ('normal neotree-mode-map "SPC" leader-map) :config (defun neotree-project-dir () "Open NeoTree using the git root." (interactive) (let ((project-dir (projectile-project-root)) (file-name (buffer-file-name)))) (neotree-toggle) (if project-dir (if (neo-global--window-exists-p) (progn (neotree-dir project-dir) (neotree-find file-name)) (message "Could not find git project root.")))) (setq neo-smart-open t neo-theme (if (display-graphic-p) 'icons 'arrow) neo-autorefresh t)) (use-package all-the-icons :after (neotree)) #+END_SRC And while we're here let's enable all-the-icons for dired as well: #+BEGIN_SRC emacs-lisp (use-package all-the-icons-dired :after (all-the-icons) :config (add-hook 'dired-mode-hook #'all-the-icons-dired-mode)) #+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 * Paredit/Parinfer Paredit enables structured editing of s-expressions #+BEGIN_SRC emacs-lisp (use-package paredit :hook ((emacs-lisp-mode . enable-paredit-mode) (lisp-mode . enable-paredit-mode) (clojure-mode . enable-paredit-mode) (cider-repl-mode . enable-paredit-mode) (ielm-mode . enable-paredit-mode) (scheme-mode . enable-paredit-mode) (geiser-repl-mode . enable-paredit-mode) (slime-repl-mode . enable-paredit-mode))) (jdormit/define-prefix "l" "lisp") (jdormit/define-prefix "lw" "wrap") (leader-def-key "lwr" 'paredit-wrap-round) (leader-def-key "lws" 'paredit-wrap-square) (leader-def-key "lwc" 'paredit-wrap-curly) (leader-def-key "ls" 'paredit-forward-slurp-sexp) (leader-def-key "lb" 'paredit-forward-barf-sexp) #+END_SRC Parinfer infers parens from indentation and vice-versa: #+BEGIN_SRC emacs-lisp (use-package parinfer :init (leader-def-key "lt" 'parinfer-toggle-mode) (setq parinfer-extensions '(defaults pretty-parens evil paredit 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 * Org Mode Notes, agenda, calendar, blogging, journaling, etc. #+BEGIN_SRC emacs-lisp (use-package org :mode ("\\.org\\'" . org-mode) :commands (org-agenda org-capture)) (jdormit/define-prefix "o" "org") (leader-def-key "oa" 'org-agenda) (leader-def-key "oc" 'org-capture) (setq org-src-fontify-natively t) #+END_SRC Use RET to follow links: #+BEGIN_SRC emacs-lisp (setq org-return-follows-link t) #+END_SRC Always show inline images: #+BEGIN_SRC emacs-lisp (add-hook 'org-mode-hook (lambda () (org-display-inline-images nil t) (org-redisplay-inline-images))) #+END_SRC ** Agenda files #+BEGIN_SRC emacs-lisp (defun agenda-files (&optional file) (let ((agenda-dir (concat (file-name-as-directory (get-dropbox-directory)) "org"))) (if file (concat (file-name-as-directory agenda-dir) file) agenda-dir))) (setq org-agenda-files `(,(agenda-files))) #+END_SRC ** Capture templates #+BEGIN_SRC emacs-lisp (setq 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%?") ("j" "Journal entry" entry (file ,(agenda-files "journal.org")) "* %<%Y-%m-%d %H:%M:%S>%?") ("p" "Project" entry (file ,(agenda-files "notes.org")) "* %^{Project name}\n\n** What's it supposed to do?\n%?\n** How can I test that it works?\n\n** How can I test that it didn't break anything?\n\n** What alternative approaches are there - what trade-offs can be made?\n\n** What assumptions have I made?\n\n** What deployables will I need to deploy?\n") ("b" "Brain" plain (function org-brain-goto-end) "* %i%?" :empty-lines 1) ("l" "Log" entry (file ,(agenda-files "log.org")) "* %<%Y-%m-%d %H:%M:%S>%?"))) #+END_SRC ** Refile targets #+BEGIN_SRC emacs-lisp (setq 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))) #+END_SRC ** Todo keywords #+BEGIN_SRC emacs-lisp (setq org-todo-keywords '((sequence "TODO(t)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)"))) #+END_SRC ** Agenda views #+BEGIN_SRC emacs-lisp (setq org-agenda-todo-ignore-scheduled 'future) (setq org-agenda-tags-todo-honor-ignore-options t) (setq org-agenda-custom-commands '(("L" "Lola" ((tags-todo "@lola"))) ("T" "Today's list" ((agenda) (tags-todo "today"))))) #+END_SRC ** Keybindings #+BEGIN_SRC emacs-lisp (general-def 'normal org-mode-map "T" #'org-insert-todo-heading) (general-def 'normal org-mode-map "K" #'org-move-subtree-up) (general-def 'normal org-mode-map "J" #'org-move-subtree-down) (general-def 'normal org-mode-map "" #'org-return) (general-def 'normal org-mode-map "TAB" #'org-cycle) (general-def 'normal org-mode-map "SPC" leader-map) (general-def org-mode-map "C-c e" #'org-preview-latex-fragment) (general-def "C-c l" #'org-store-link) #+END_SRC Set up evil keybindings: #+BEGIN_SRC emacs-lisp (use-package evil-org :after (evil org) :config (add-hook 'org-mode-hook #'evil-org-mode) (add-hook 'evil-org-mode-hook (lambda () (evil-org-set-key-theme '(textobjects insert navigation additional shift todo heading)))) (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)) #+END_SRC And a global keybinding to open an org file: #+BEGIN_SRC emacs-lisp (defun find-org-file (file) (interactive (list (completing-read "Find org file: " (directory-files (agenda-files) t)))) (find-file file)) (leader-def-key "fo" #'find-org-file) #+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 ** 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))))) #+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")) #+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) (concat (file-name-as-directory (get-dropbox-directory)) "org/" name)) (use-package org-gcal :after (org) :commands (org-gcal-sync org-gcal-fetch org-gcal-post-at-point org-gcal-delete-at-point org-gcal-refresh-token) :config (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-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)) (add-hook 'org-agenda-mode-hook #'org-gcal-fetch) (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))) (general-def '(normal motion) org-agenda-mode-map "gr" #'org-agenda-redo-and-fetch-gcal) #+END_SRC * Projectile #+BEGIN_SRC emacs-lisp (use-package projectile :commands (projectile-find-file projectile-grep projectile-switch-project projectile-project-root) :config (projectile-mode) (jdormit/define-prefix "p" "projectile") (leader-def-key "pf" #'projectile-find-file) (leader-def-key "pg" #'projectile-grep) (leader-def-key "pp" #'projectile-switch-project)) #+END_SRC * Mode line A sexy mode line for maximum geek cred: #+BEGIN_SRC emacs-lisp (use-package smart-mode-line :init (sml/setup)) #+END_SRC * 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 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") (use-package solarized-theme))) (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 * 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 * EShell Easy keybinding to open EShell: #+BEGIN_SRC emacs-lisp (leader-def-key "'" 'eshell) #+END_SRC Make EShell's tab completion work like Bash's: #+BEGIN_SRC emacs-lisp (setq eshell-cmpl-cycle-completions nil) #+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-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-directory-change-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))) (with-eval-after-load 'eshell (general-def eshell-mode-map "C-c C-o" #'clear-eshell)) #+END_SRC Some aliases: #+BEGIN_SRC emacs-lisp (defvar eshell-aliases '(("k" . "kubectl $*") ("kctx" . "kubectx $*"))) (add-hook 'eshell-mode-hook (lambda () (dolist (alias eshell-aliases) (eshell/alias (car alias) (cdr alias))))) #+END_SRC * JSON #+BEGIN_SRC emacs-lisp (use-package json-mode :mode (("\\.json\\'" . json-mode))) (use-package json-navigator :commands (json-navigator-navigator json-navigator-navigate-after-point json-navigator-navigate-region)) (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)) :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))))) #+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 * Typescript #+BEGIN_SRC emacs-lisp (use-package typescript-mode :mode ("\\.ts\\'") :config (with-eval-after-load 'lsp (add-hook 'typescript-mode-hook 'lsp))) #+END_SRC * LSP Mode Emacs support for the Language Server Protocol #+BEGIN_SRC emacs-lisp (use-package lsp-mode :commands lsp-mode) (use-package company-lsp :after (lsp-mode company) :config (setq company-lsp-cache-candidates t)) (use-package lsp-ui :after (lsp-mode) :config (setq 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)) (use-package dap-mode :after (lsp-mode)) (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 * Java LSP Java uses the Eclipse JDT Language Server as a backend to enable Java IDE features. #+BEGIN_SRC emacs-lisp (defun jdormit/set-up-java () (lsp-java-enable) (flycheck-mode t) (push 'company-lsp company-backends) (company-mode t) (lsp-ui-flycheck-enable t) (set-variable 'c-basic-offset 2) (lsp-ui-sideline-mode)) (use-package lsp-java :requires (lsp-ui-flycheck lsp-ui-sideline) :config (add-hook 'java-mode-hook 'jdormit/set-up-java) (setq lsp-inhibit-message t lsp-java-import-maven-enabled t)) #+END_SRC Configure Java project sources: #+BEGIN_SRC emacs-lisp (setq lsp-java--workspace-folders (list (expand-file-name "~/src/Automation") (expand-file-name "~/src/AutomationSharedExecution") (expand-file-name "~/src/AutomationPlatform"))) #+END_SRC * Python #+BEGIN_SRC emacs-lisp ;; (leader-def-key "sp" #'elpy-shell-switch-to-shell) (leader-def-key "sp" #'run-python) #+END_SRC Elpy is a python IDE package: #+BEGIN_SRC emacs-lisp ;; (use-package elpy ;; :init (elpy-enable) ;; :config (setq elpy-rpc-python-command "python3")) #+END_SRC Alternatively, use the LSP python client: #+BEGIN_SRC emacs-lisp (add-hook 'python-mode-hook #'lsp) (general-def 'normal python-mode-map "C-c C-d" #'lsp-describe-thing-at-point) #+END_SRC Support pyvenv within Emacs: #+BEGIN_SRC emacs-lisp (use-package pyvenv :commands (pyvenv-workon pyvenv-activate) :config (pyvenv-mode)) (defun eshell/workon (name) (pyvenv-workon name)) (defun eshell/activate (dir) (pyvenv-activate dir)) (defun eshell/deactivate () (pyvenv-deactivate)) #+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 Pipenv is the Python standard dependency management/virtual environment tool. pipenv.el teaches Emacs its ways: #+BEGIN_SRC emacs-lisp (use-package pipenv :hook (python-mode . pipenv-mode) :commands (pipenv-mode pipenv-activate pipenv-run)) #+END_SRC A function to run a pipenv-aware python repl: #+BEGIN_SRC emacs-lisp (defun run-pipenv () "Runs a pipenv-aware Python shell" (interactive) (pipenv-activate) (run-python nil nil t)) #+END_SRC Run black on the current buffer: #+BEGIN_SRC emacs-lisp (defun blacken () (interactive) (let ((start (if (use-region-p) (region-beginning) (point-min))) (end (if (use-region-p) (region-end) (point-max)))) (shell-command-on-region start end "black -q -" nil t))) (general-def 'normal python-mode-map "C-M-\\" #'blacken) #+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) #+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))) #+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) :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)) (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 Enable Org-mode Clojure evaluation: #+BEGIN_SRC emacs-lisp (require 'ob-clojure) (setq org-babel-clojure-backend 'cider) #+END_SRC Integrate with cljfmt, the Clojure code formatter: #+BEGIN_SRC emacs-lisp (defun cljfmt () (interactive) (let* ((start (if (use-region-p) (region-beginning) (point-min))) (end (if (use-region-p) (region-end) (point-max))) (text (buffer-substring start end)) (file (make-temp-file "cljfmt")) (fmted (with-temp-buffer (insert text) (write-file file) (shell-command (concat "clojure " "-Sdeps " "'{:aliases {:fmt {:extra-deps {cljfmt {:mvn/version \"0.6.4\"}} :main-opts [\"-m\" \"cljfmt.main\"]}}}' " "-A:fmt " "fix " file)) (revert-buffer nil t) (buffer-substring (point-min) (point-max))))) (delete-region start end) (goto-char start) (insert fmted))) (general-def clojure-mode-map "C-M-\\" #'cljfmt) #+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) :general (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-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 An Eshell alias to start PHP using XDebug: #+BEGIN_SRC emacs-lisp (add-hook 'eshell-mode-hook (lambda () (eshell/alias "php-debug" "php -d xdebug.remote_enable=on -d xdebug.remote_host=127.0.0.1 -d xdebug.remote_port=9000 -d xdebug.remote_handler=dbgp -d xdebug.idekey=geben -d xdebug.remote_autostart=On $*"))) #+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) #+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)) #+END_SRC * Ruby #+BEGIN_SRC emacs-lisp (add-hook 'ruby-mode-hook #'lsp) #+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)) (use-package cargo :after (rust-mode) :config (add-hook 'rust-mode-hook #'cargo-minor-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 "