#+TITLE: API Demos #+PROPERTY: header-args:elisp :results pp This appendix serves as a reference on how to use Doom Emacs' standard library. It is integrated into Helpful, in Doom. * Table of Contents :TOC_3: - [[#examples-for-dooms-library][Examples for Doom's library]] - [[#core-lib][core-lib]] - [[#add-hook][add-hook!]] - [[#add-transient-hook][add-transient-hook!]] - [[#after][after!]] - [[#appendq][appendq!]] - [[#custom-set-faces][custom-set-faces!]] - [[#custom-theme-set-faces][custom-theme-set-faces!]] - [[#defer-feature][defer-feature!]] - [[#defer-until][defer-until!]] - [[#disable-packages][disable-packages!]] - [[#doom][doom!]] - [[#file-exists-p][file-exists-p!]] - [[#cmd][cmd!]] - [[#cmd-1][cmd!!]] - [[#letenv][letenv!]] - [[#load][load!]] - [[#map][map!]] - [[#package][package!]] - [[#pushnew][pushnew!]] - [[#prependq][prependq!]] - [[#quiet][quiet!]] - [[#remove-hook][remove-hook!]] - [[#setq][setq!]] - [[#setq-hook][setq-hook!]] - [[#unsetq-hook][unsetq-hook!]] - [[#use-package][use-package!]] - [[#interesting-snippets][Interesting snippets]] - [[#center-emacs-initial-frame-with-a-fixed-size][Center Emacs' initial frame with a fixed size]] - [[#persist-emacs-initial-frame-position-dimensions-andor-full-screen-state-across-sessions][Persist Emacs' initial frame position, dimensions and/or full-screen state across sessions]] - [[#update-cursor-shape-under-terminal-emacs][Update cursor shape under terminal Emacs]] * Examples for Doom's library ** core-lib *** add-hook! #+BEGIN_SRC elisp :eval no ;; With only one hook and one function, this is identical to `add-hook'. In that ;; case, use that instead. (add-hook! 'some-mode-hook #'enable-something) ;; Adding many-to-many functions to hooks (add-hook! some-mode #'enable-something #'and-another) (add-hook! some-mode #'(enable-something and-another)) (add-hook! '(one-mode-hook second-mode-hook) #'enable-something) (add-hook! (one-mode second-mode) #'enable-something) ;; Appending and local hooks (add-hook! (one-mode second-mode) :append #'enable-something) (add-hook! (one-mode second-mode) :local #'enable-something) ;; With arbitrary forms (add-hook! (one-mode second-mode) (setq v 5) (setq a 2)) (add-hook! (one-mode second-mode) :append :local (setq v 5) (setq a 2)) ;; Inline named hook functions (add-hook! '(one-mode-hook second-mode-hook) (defun do-something () ...) (defun do-another-thing () ...)) #+END_SRC *** TODO add-transient-hook! *** after! #+BEGIN_SRC elisp :eval no ;;; `after!' will take: ;; An unquoted package symbol (the name of a package) (after! helm ...) ;; An unquoted list of package symbols (i.e. BODY is evaluated once both magit ;; and git-gutter have loaded) (after! (magit git-gutter) ...) ;; An unquoted, nested list of compound package lists, using any combination of ;; :or/:any and :and/:all (after! (:or package-a package-b ...) ...) (after! (:and package-a package-b ...) ...) (after! (:and package-a (:or package-b package-c) ...) ...) ;; (Without :or/:any/:and/:all, :and/:all are implied.) ;; A common mistake is to pass it the names of major or minor modes, e.g. (after! rustic-mode ...) (after! python-mode ...) ;; But the code in them will never run! rustic-mode is in the `rustic' package ;; and python-mode is in the `python' package. This is what you want: (after! rustic ...) (after! python ...) #+END_SRC *** appendq! #+BEGIN_SRC elisp (let ((x '(a b c))) (appendq! x '(c d e)) x) #+END_SRC #+RESULTS: : (a b c c d e) #+BEGIN_SRC elisp (let ((x '(a b c)) (y '(c d e)) (z '(f g))) (appendq! x y z '(h)) x) #+END_SRC #+RESULTS: : (a b c c d e f g h) *** custom-set-faces! #+BEGIN_SRC elisp :eval no (custom-set-faces! '(outline-1 :weight normal) '(outline-2 :weight normal) '(outline-3 :weight normal) '(outline-4 :weight normal) '(outline-5 :weight normal) '(outline-6 :weight normal) '(default :background "red" :weight bold) '(region :background "red" :weight bold)) (custom-set-faces! '((outline-1 outline-2 outline-3 outline-4 outline-5 outline-6) :weight normal) '((default region) :background "red" :weight bold)) (let ((red-bg-faces '(default region))) (custom-set-faces! `(,(cl-loop for i from 0 to 6 collect (intern (format "outline-%d" i))) :weight normal) `(,red-bg-faces :background "red" :weight bold))) ;; If you want to make use of the `doom-themes' package API (e.g. `doom-color', ;; `doom-lighten', `doom-darken', etc.), you must use `custom-set-faces!' ;; *after* the theme has been loaded. e.g. (load-theme 'doom-one t) (custom-set-faces! `(outline-1 :foreground ,(doom-color 'red)) `(outline-2 :background ,(doom-color 'blue))) #+END_SRC *** custom-theme-set-faces! #+BEGIN_SRC elisp :eval no (custom-theme-set-faces! 'doom-one-theme '(outline-1 :weight normal) '(outline-2 :weight normal) '(outline-3 :weight normal) '(outline-4 :weight normal) '(outline-5 :weight normal) '(outline-6 :weight normal) '(default :background "red" :weight bold) '(region :background "red" :weight bold)) (custom-theme-set-faces! '(doom-one-theme doom-one-light-theme) '((outline-1 outline-2 outline-3 outline-4 outline-5 outline-6) :weight normal) '((default region) :background "red" :weight bold)) (let ((red-bg-faces '(default region))) (custom-theme-set-faces! '(doom-one-theme doom-one-light-theme) `(,(cl-loop for i from 0 to 6 collect (intern (format "outline-%d" i))) :weight normal) `(,red-bg-faces :background "red" :weight bold))) ;; If you want to make use of the `doom-themes' package API (e.g. `doom-color', ;; `doom-lighten', `doom-darken', etc.), you must use `custom-set-faces!' ;; *after* the theme has been loaded. e.g. (load-theme 'doom-one t) (custom-theme-set-faces! 'doom-one `(outline-1 :foreground ,(doom-color 'red)) `(outline-2 :background ,(doom-color 'blue))) #+END_SRC *** TODO defer-feature! *** TODO defer-until! *** disable-packages! #+BEGIN_SRC elisp :eval no ;; Disable packages enabled by DOOM (disable-packages! some-package second-package) #+END_SRC *** doom! #+BEGIN_SRC elisp :eval no (doom! :completion company ivy ;;helm :tools (:if IS-MAC macos) docker lsp :lang (cc +lsp) (:cond ((string= system-name "work-pc") python rust web) ((string= system-name "writing-pc") (org +dragndrop) ruby)) (:if IS-LINUX (web +lsp) web) :config literate (default +bindings +smartparens)) #+END_SRC *** file-exists-p! #+BEGIN_SRC elisp (file-exists-p! "init.el" doom-emacs-dir) #+END_SRC #+RESULTS: : /home/hlissner/.emacs.d/init.el #+BEGIN_SRC elisp (file-exists-p! (and (or "doesnotexist" "init.el") "LICENSE") doom-emacs-dir) #+END_SRC #+RESULTS: : /home/hlissner/.emacs.d/LICENSE *** cmd! #+BEGIN_SRC elisp :eval no (map! "C-j" (cmd! (newline) (indent-according-to-mode))) #+END_SRC *** cmd!! When ~newline~ is passed a numerical prefix argument (=C-u 5 M-x newline=), it inserts N newlines. We can use ~cmd!!~ to easily create a keybinds that bakes in the prefix arg into the command call: #+BEGIN_SRC elisp :eval no (map! "C-j" (cmd!! #'newline 5)) #+END_SRC Or to create aliases for functions that behave differently: #+BEGIN_SRC elisp :eval no (fset 'insert-5-newlines (cmd!! #'newline 5)) ;; The equivalent of C-u M-x org-global-cycle, which resets the org document to ;; its startup visibility settings. (fset 'org-reset-global-visibility (cmd!! #'org-global-cycle '(4)) #+END_SRC *** letenv! #+BEGIN_SRC elisp (letenv! (("SHELL" "/bin/sh")) (shell-command-to-string "echo $SHELL")) #+END_SRC #+RESULTS: : "/bin/sh\n" *** load! #+BEGIN_SRC elisp :eval no ;;; Lets say we're in ~/.doom.d/config.el (load! "lisp/module") ; loads ~/.doom.d/lisp/module.el (load! "somefile" doom-emacs-dir) ; loads ~/.emacs.d/somefile.el (load! "anotherfile" doom-private-dir) ; loads ~/.doom.d/anotherfile.el ;; If you don't want a `load!' call to throw an error if the file doesn't exist: (load! "~/.maynotexist" nil t) #+END_SRC *** map! #+BEGIN_SRC elisp :eval no (map! :map magit-mode-map :m "C-r" 'do-something ; C-r in motion state :nv "q" 'magit-mode-quit-window ; q in normal+visual states "C-x C-r" 'a-global-keybind :g "C-x C-r" 'another-global-keybind ; same as above (:when IS-MAC :n "M-s" 'some-fn :i "M-o" (lambda (interactive) (message "Hi")))) (map! (:when (featurep! :completion company) ; Conditional loading :i "C-@" #'+company/complete (:prefix "C-x" ; Use a prefix key :i "C-l" #'+company/whole-lines))) (map! (:when (featurep! :lang latex) ; local conditional (:map LaTeX-mode-map :localleader ; Use local leader :desc "View" "v" #'TeX-view)) ; Add which-key description :leader ; Use leader key from now on :desc "Eval expression" ";" #'eval-expression) #+END_SRC These are side-by-side comparisons, showing how to bind keys with and without ~map!~: #+BEGIN_SRC elisp :eval no ;; bind a global key (global-set-key (kbd "C-x y") #'do-something) (map! "C-x y" #'do-something) ;; bind a key on a keymap (define-key emacs-lisp-mode (kbd "C-c p") #'do-something) (map! :map emacs-lisp-mode "C-c p" #'do-something) ;; unbind a key defined elsewhere (define-key lua-mode-map (kbd "SPC m b") nil) (map! :map lua-mode-map "SPC m b" nil) ;; bind multiple keys (global-set-key (kbd "C-x x") #'do-something) (global-set-key (kbd "C-x y") #'do-something-else) (global-set-key (kbd "C-x z") #'do-another-thing) (map! "C-x x" #'do-something "C-x y" #'do-something-else "C-x z" #'do-another-thing) ;; bind global keys in normal mode (evil-define-key* 'normal 'global (kbd "C-x x") #'do-something (kbd "C-x y") #'do-something-else (kbd "C-x z") #'do-another-thing) (map! :n "C-x x" #'do-something :n "C-x y" #'do-something-else :n "C-x z" #'do-another-thing) ;; or on a deferred keymap (evil-define-key 'normal emacs-lisp-mode-map (kbd "C-x x") #'do-something (kbd "C-x y") #'do-something-else (kbd "C-x z") #'do-another-thing) (map! :map emacs-lisp-mode-map :n "C-x x" #'do-something :n "C-x y" #'do-something-else :n "C-x z" #'do-another-thing) ;; or multiple maps (dolist (map (list emacs-lisp-mode go-mode-map ivy-minibuffer-map)) (evil-define-key '(normal insert) map "a" #'a "b" #'b "c" #'c)) (map! :map (emacs-lisp-mode go-mode-map ivy-minibuffer-map) :ni "a" #'a :ni "b" #'b :ni "c" #'c) ;; or in multiple states (order of states doesn't matter) (evil-define-key* '(normal visual) emacs-lisp-mode-map (kbd "C-x x") #'do-something) (evil-define-key* 'insert emacs-lisp-mode-map (kbd "C-x x") #'do-something-else) (evil-define-key* '(visual normal insert emacs) emacs-lisp-mode-map (kbd "C-x z") #'do-another-thing) (map! :map emacs-lisp-mode :nv "C-x x" #'do-something ; normal+visual :i "C-x y" #'do-something-else ; insert :vnie "C-x z" #'do-another-thing) ; visual+normal+insert+emacs ;; You can nest map! calls: (evil-define-key* '(normal visual) emacs-lisp-mode-map (kbd "C-x x") #'do-something) (evil-define-key* 'normal go-lisp-mode-map (kbd "C-x x") #'do-something-else) (map! (:map emacs-lisp-mode :nv "C-x x" #'do-something) (:map go-lisp-mode :n "C-x x" #'do-something-else)) #+END_SRC *** package! #+BEGIN_SRC elisp :eval no ;; To install a package that can be found on ELPA or any of the sources ;; specified in `straight-recipe-repositories': (package! evil) (package! js2-mode) (package! rainbow-delimiters) ;; To disable a package included with Doom (which will no-op all its `after!' ;; and `use-package!' blocks): (package! evil :disable t) (package! rainbow-delimiters :disable t) ;; To install a package from a github repo (package! so-long :recipe (:host github :repo "hlissner/emacs-so-long")) ;; If a package is particularly big and comes with submodules you don't need, ;; you can tell the package manager not to clone the repo recursively: (package! ansible :recipe (:nonrecursive t)) ;; To pin a package to a specific commit: (package! evil :pin "e7bc39de2f9") ;; ...or branch: (package! evil :recipe (:branch "stable")) ;; To unpin a pinned package: (package! evil :pin nil) ;; If you share your config between two computers, and don't want bin/doom ;; refresh to delete packages used only on one system, use :ignore (package! evil :ignore (not (equal system-name "my-desktop"))) #+END_SRC *** pushnew! #+BEGIN_SRC elisp (let ((list '(a b c))) (pushnew! list 'c 'd 'e) list) #+END_SRC #+RESULTS: : (e d a b c) *** prependq! #+BEGIN_SRC elisp (let ((x '(a b c))) (prependq! x '(c d e)) x) #+END_SRC #+RESULTS: : (c d e a b c) #+BEGIN_SRC elisp (let ((x '(a b c)) (y '(c d e)) (z '(f g))) (prependq! x y z '(h)) x) #+END_SRC #+RESULTS: : (c d e f g h a b c) *** quiet! #+BEGIN_SRC elisp :eval no ;; Enters recentf-mode without extra output (quiet! (recentf-mode +1)) #+END_SRC *** remove-hook! #+BEGIN_SRC elisp :eval no ;; With only one hook and one function, this is identical to `remove-hook'. In ;; that case, use that instead. (remove-hook! 'some-mode-hook #'enable-something) ;; Removing N functions from M hooks (remove-hook! some-mode #'enable-something #'and-another) (remove-hook! some-mode #'(enable-something and-another)) (remove-hook! '(one-mode-hook second-mode-hook) #'enable-something) (remove-hook! (one-mode second-mode) #'enable-something) ;; Removing buffer-local hooks (remove-hook! (one-mode second-mode) :local #'enable-something) ;; Removing arbitrary forms (must be exactly the same as the definition) (remove-hook! (one-mode second-mode) (setq v 5) (setq a 2)) #+END_SRC *** setq! #+BEGIN_SRC elisp ;; Each of these have a setter associated with them, which must be triggered in ;; order for their new values to have an effect. (setq! evil-want-Y-yank-to-eol nil evil-want-C-u-scroll nil evil-want-C-d-scroll nil) #+END_SRC *** setq-hook! #+BEGIN_SRC elisp :eval no ;; Set multiple variables after a hook (setq-hook! 'markdown-mode-hook line-spacing 2 fill-column 80) ;; Set variables after multiple hooks (setq-hook! '(eshell-mode-hook term-mode-hook) hscroll-margin 0) #+END_SRC *** unsetq-hook! #+BEGIN_SRC elisp :eval no (unsetq-hook! 'markdown-mode-hook line-spacing) ;; Removes the following variable hook (setq-hook! 'markdown-mode-hook line-spacing 2) ;; Removing N variables from M hooks (unsetq-hook! some-mode enable-something and-another) (unsetq-hook! some-mode (enable-something and-another)) (unsetq-hook! '(one-mode-hook second-mode-hook) enable-something) (unsetq-hook! (one-mode second-mode) enable-something) #+END_SRC *** use-package! #+BEGIN_SRC elisp :eval no ;; Use after-call to load package before hook (use-package! projectile :after-call (pre-command-hook after-find-file dired-before-readin-hook)) ;; defer recentf packages one by one (use-package! recentf :defer-incrementally easymenu tree-widget timer :after-call after-find-file) ;; This is equivalent to :defer-incrementally (abc) (use-package! abc :defer-incrementally t) #+END_SRC * Interesting snippets ** Center Emacs' initial frame with a fixed size #+BEGIN_SRC elisp (let ((display-height (display-pixel-height)) (display-width (display-pixel-width))) (pushnew! initial-frame-alist `(left . ,(/ display-width 2)) `(top . ,(/ display-height 2)) `(width . ,display-width) `(height . ,display-height))) #+END_SRC ** Persist Emacs' initial frame position, dimensions and/or full-screen state across sessions #+BEGIN_SRC elisp ;; add to ~/.doom.d/config.el (when-let (dims (doom-store-get 'last-frame-size)) (cl-destructuring-bind ((left . top) width height fullscreen) dims (setq initial-frame-alist (append initial-frame-alist `((left . ,left) (top . ,top) (width . ,width) (height . ,height) (fullscreen . ,fullscreen)))))) (defun save-frame-dimensions () (doom-store-put 'last-frame-size (list (frame-position) (frame-width) (frame-height) (frame-parameter nil 'fullscreen)))) (add-hook 'kill-emacs-hook #'save-frame-dimensions) #+END_SRC ** Update cursor shape under terminal Emacs Install [[https://github.com/7696122/evil-terminal-cursor-changer][evil-terminal-cursor-changer]]. The package isn't included in Doom because it is not maintained, unreasonably buggy, and lacks support for a number of terminals. Where it fails, it inserts unexpected characters into the buffer. To uphold the principle of least surprise, an unchanging cursor is less surprising than unwarranted characters. #+BEGIN_SRC elisp ;; ~/.doom.d/packages.el (package! evil-terminal-cursor-changer) ;; ~/.doom.d/config.el (use-package! evil-terminal-cursor-changer :hook (tty-setup . evil-terminal-cursor-changer-activate)) #+END_SRC Alternatively, an updated version exists at [[https://github.com/amosbird/evil-terminal-cursor-changer][amosbird/evil-terminal-cursor-changer]], with support for urxvt and tmux. ** Create a paste-transient-state to cycle through kill ring on paste Replaces the default evil-paste binding to paste then let you cycle through entries in your kill ring. Gives you more flexibility when copying to your clipboard, making edits, then deciding to paste after. You will need to enable the `hydra` module first. #+BEGIN_SRC elisp (defhydra hydra-paste (:color red :hint nil) "\n[%s(length kill-ring-yank-pointer)/%s(length kill-ring)] \ [_C-j_/_C-k_] cycles through yanked text, [_p_/_P_] pastes the same text \ above or below. Anything else exits." ("C-j" evil-paste-pop) ("C-k" evil-paste-pop-next) ("p" evil-paste-after) ("P" evil-paste-before)) (map! :nv "p" #'hydra-paste/evil-paste-after :nv "P" #'hydra-paste/evil-paste-before) #+END_SRC