모듈 개발 가이드
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 ()
...))
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 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 ...)
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 )))
defer-feature!
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
Related-Notes
References