doomemacs/lisp/demos.org
Henrik Lissner b405225b90
refactor!(vc-gutter): drop git-gutter for diff-hl
BREAKING CHANGE: This removes git-gutter as an implementation for the
`:ui vc-gutter` module, leaving only the diff-hl implementation. There
are no longer any +git-gutter or +diff-hl flags for this module. Users
don't have to do anything to keep the vc gutter, unless they prefer
git-gutter for any reason (in which case they'll need to install and set
it up themselves).

This has been planned for some time, because of a roadmap goal for Doom
to lean into native/built-in functionality where it's equal or better
than the third party alternatives. diff-hl relies on the built-in vc.el
library instead of talking to git directly (thus expanding support to
whatever VCS's vc.el supports, and not git alone), which also means it
can take advantage of its caching and other user configuration for
vc.el. Overall, it is faster and lighter.

What I've also been waiting for was a stage-hunk command, similar to
git-gutter:stage-hunk, which arrived in dgutov/diff-hl@a0560551cd and
dgutov/diff-hl@133538973b, and have evolved since.

Ref: dgutov/diff-hl@a0560551cd
Ref: dgutov/diff-hl@133538973b
Ref: https://github.com/orgs/doomemacs/projects/5/views/1?pane=issue&itemId=58747789
2024-06-22 18:14:04 -04:00

18 KiB

Doom Emacs API Demos

This module installs the elisp-demos package, which adds code examples to documentation buffers (the ones help.el or helpful produces). The built-in demos are great, but this file exists to add demos for Doom's API and beyond.

󰐃 Please make sure new additions to this file are arranged alphabetically and has a :PROPERTIES: drawer that includes in what version of Doom that the symbol/function was added.

󰐃 Please don't add demos for code outside of Doom Emacs. PR those to the parent project: https://github.com/xuchunyang/elisp-demos. And please don't add functions from modules, put those in that module's demo.org file, or your own in $DOOMDIR/demos.org.

add-hook!

;; 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 ()
    ...))

TODO add-transient-hook!

after!

;;; `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 diff-hl have loaded)
(after! (magit diff-hl) ...)

;; 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 ...)

appendq!

(let ((x '(a b c)))
  (appendq! x '(c d e))
  x)
(a b c c d e)
(let ((x '(a b c))
      (y '(c d e))
      (z '(f g)))
  (appendq! x y z '(h))
  x)
(a b c c d e f g h)

cmd!

(map! "C-j" (cmd! (newline) (indent-according-to-mode)))

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:

(map! "C-j" (cmd!! #'newline 5))

Or to create aliases for functions that behave differently:

(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))

cmds!

(map! :i [tab] (cmds! (and (modulep! :editor snippets)
                           (bound-and-true-p yas-minor-mode)
                           (yas-maybe-expand-abbrev-key-filter 'yas-expand))
                      #'yas-expand
                      (modulep! :completion company +tng)
                      #'company-indent-or-complete-common)
      :m [tab] (cmds! (and (bound-and-true-p yas-minor-mode)
                           (evil-visual-state-p)
                           (or (eq evil-visual-selection 'line)
                               (not (memq (char-after) (list ?\( ?\[ ?\{ ?\} ?\] ?\))))))
                      #'yas-insert-snippet
                      (and (modulep! :editor fold)
                           (save-excursion (end-of-line) (invisible-p (point))))
                      #'+fold/toggle
                      (fboundp 'evil-jump-item)
                      #'evil-jump-item))

custom-set-faces!

(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)))

;; You may utilise `doom-themes's theme API to fetch or tweak colors from their
;; palettes. No need to wait until the theme or package is loaded. e.g.
(custom-set-faces!
 `(outline-1 :foreground ,(doom-color 'red))
 `(outline-2 :background ,(doom-color 'blue)))

custom-theme-set-faces!

(custom-theme-set-faces! 'doom-one
 '(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)))

;; You may utilise `doom-themes's theme API to fetch or tweak colors from their
;; palettes. No need to wait until the theme or package is loaded. e.g.
(custom-theme-set-faces! 'doom-one
 `(outline-1 :foreground ,(doom-color 'red))
 `(outline-2 :background ,(doom-color 'blue)))

TODO defer-feature!

TODO defer-until!

disable-packages!

;; Disable packages enabled by DOOM
(disable-packages! some-package second-package)

doom!

(doom! :completion
       company
       ivy
       ;;helm

       :tools
       (:if (featurep :system 'macos) macos)
       docker
       lsp

       :lang
       (cc +lsp)
       (:cond ((string= system-name "work-pc")
               python
               rust
               web)
              ((string= system-name "writing-pc")
               (org +dragndrop)
               ruby))
       (:if (featurep :system 'linux)
           (web +lsp)
         web)

       :config
       literate
       (default +bindings +smartparens))

(doom!
 (pin "v3.0.0")

 (source "modules/")  ; Modules in $DOOMDIR/modules/*/*
 (source :name doom :repo "doomemacs/modules" :pin "v21.12.0")  ; Doom's default modules
 (source :name contrib :repo "doomemacs/contrib-modules" :pin "v21.12.0")  ; Community-contributed
 ;; Examples:
 (source :name my :repo "my/modules" :root "unorthodox/path/to/modules/")
 (source :name more :host gitlab :repo "more/modules")

 ;;; To enable (or disable) flags globally, they can be given at top-level:
 +lsp -tree-sitter

 ;;;; Examples of explicit and/or remote modules:
 :lang
 (rust   :repo "doomemacs/modules" :branch "somePR" :pin "1a2b3c4d"
         :path "lang/rust"
         :flags '(-lsp))
 ;; A local, out-of-tree module
 (python :path "/nonstandard/location/for/modules/python"
         :flags '(+pyenv +conda))
 (cc     :source 'doom)         ; explicit source
 (agda   :source '(doom more))  ; multiple sources

 :lang
 (clojure +lsp)  ; shorthand format still works for flags
 nix

 ;; Conditional modules
 (:os :if (featurep :system 'macos))  ; category-wide
 macos
 (tty :if (equal (system-name) "headless-machine"))  ; per-module

 ...)

file-exists-p!

(file-exists-p! "init.el" doom-emacs-dir)
(file-exists-p! (and (or "doesnotexist" "init.el")
                     "LICENSE")
                doom-emacs-dir)

fn!

(mapcar (fn! (symbol-name %)) '(hello world))
(seq-sort (fn! (string-lessp (symbol-name %1)
                             (symbol-name %2)))
          '(bonzo foo bar buddy doomguy baz zombies))
(format "You passed %d arguments to this function"
        (funcall (fn! (length %*)) :foo :bar :baz "hello" 123 t))

kbd!

(map! "," (kbd! "SPC")
      ";" (kbd! ":"))

lambda!

(mapcar (lambda! ((&key foo bar baz))
          (list foo bar baz))
        '((:foo 10 :bar 25)
          (:baz hello :boop nil)
          (:bar 42)))

load!

;;; 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-user-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)

map!

(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 (featurep :system 'macos)
        :n "M-s" 'some-fn
        :i "M-o" (cmd! (message "Hi"))))

(map! (:when (modulep! :completion company) ; Conditional loading
        :i "C-@" #'+company/complete
        (:prefix "C-x"                       ; Use a prefix key
          :i "C-l" #'+company/whole-lines)))

(map! (:when (modulep! :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)

These are side-by-side comparisons, showing how to bind keys with and without map!:

;; 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-map (kbd "C-c p") #'do-something)
(map! :map emacs-lisp-mode-map "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))

package!

;; 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 :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 :nonrecursive t)

;; To pin a package to a specific commit:
(package! evil :pin "e7bc39de2f9")
;; ...or branch:
(package! evil :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")))

prependq!

(let ((x '(a b c)))
  (prependq! x '(c d e))
  x)
(c d e a b c)
(let ((x '(a b c))
      (y '(c d e))
      (z '(f g)))
  (prependq! x y z '(h))
  x)
(c d e f g h a b c)

pushnew!

(let ((list '(a b c)))
  (pushnew! list 'c 'd 'e)
  list)
(e d a b c)

quiet!

;; Enters recentf-mode without extra output
(quiet! (recentf-mode +1))

remove-hook!

;; 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))

setq!

;; 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)

setq-hook!

;; 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)

unsetq-hook!

(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)

use-package!

;; 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)

versionp!

(versionp! "25.3" > "27.1")
nil
(versionp! "28.0" <= emacs-version <= "28.1")
t