Professional Documents
Culture Documents
Examples
Examples
Examples
:ID: e103c1bc-be8e-4451-8e43-a93d9e35e692
:END:
#+title: Examples
#+subtitle: Samples of Emacs/Doom dotfiles, concepts, and sub-projects
#+property: header-args:elisp :results pp
* Introduction
Examples speak louder than technical explanations, so this file exists to house
examples of Doom's (and Emacs') concepts, libraries, dotfiles, and more, for
your own reference. They are divided into Emacs-specific and Doom-specific
examples; where the former can also be useful to users who don't use Doom.
Some of Doom's components will read this file to generate documentation for you,
for example:
* TODO Emacs
This section is dedicated to examples of concepts and libraries that can benefit
all Emacs users, whether or not they use Doom.
#+RESULTS:
: some/file.h
**** file-name-concat
:PROPERTIES:
:added: 28.1
:END:
#+begin_src emacs-lisp
(file-name-concat user-emacs-directory "lisp" "file.el")
#+end_src
#+begin_src emacs-lisp
(file-name-concat "foo" "bar" "baz")
#+end_src
#+RESULTS:
: foo/bar/baz
** TODO Templates
*** TODO Emacs package
*** TODO Dynamic module
#+RESULTS:
: (a b c c d e)
#+begin_src emacs-lisp
(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!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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))
;; 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)))
#+end_src
**** custom-theme-set-faces!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
(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))
;; 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)))
#+end_src
**** file-exists-p!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp
(file-exists-p! "init.el" doom-emacs-dir)
#+end_src
#+RESULTS:
: /home/hlissner/.emacs.d/init.el
#+begin_src emacs-lisp
(file-exists-p! (and (or "doesnotexist" "init.el")
"LICENSE")
doom-emacs-dir)
#+end_src
#+RESULTS:
: /home/hlissner/.emacs.d/LICENSE
**** cmd!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
(map! "C-j" (cmd! (newline) (indent-according-to-mode)))
#+end_src
**** cmd!!
:PROPERTIES:
:added: 3.0.0-pre
:END:
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:
;; 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
**** cmds!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
(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))
#+end_src
**** kbd!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
(map! "," (kbd! "SPC")
";" (kbd! ":"))
#+end_src
**** lambda!
#+begin_src emacs-lisp
(mapcar (lambda! ((&key foo bar baz))
(list foo bar baz))
'((:foo 10 :bar 25)
(:baz hello :boop nil)
(:bar 42)))
#+end_src
**** fn!
#+begin_src emacs-lisp
(mapcar (fn! (symbol-name %)) '(hello world))
#+end_src
#+begin_src emacs-lisp
(seq-sort (fn! (string-lessp (symbol-name %1)
(symbol-name %2)))
'(bonzo foo bar buddy doomguy baz zombies))
#+end_src
#+begin_src emacs-lisp
(format "You passed %d arguments to this function"
(funcall (fn! (length %*)) :foo :bar :baz "hello" 123 t))
#+end_src
**** load!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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-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)
#+end_src
**** map!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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" (cmd! (message "Hi"))))
These are side-by-side comparisons, showing how to bind keys with and without
~map!~:
;; 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)
**** pushnew!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp
(let ((list '(a b c)))
(pushnew! list 'c 'd 'e)
list)
#+end_src
#+RESULTS:
: (e d a b c)
**** prependq!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp
(let ((x '(a b c)))
(prependq! x '(c d e))
x)
#+end_src
#+RESULTS:
: (c d e a b c)
#+begin_src emacs-lisp
(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!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
;; Enters recentf-mode without extra output
(quiet! (recentf-mode +1))
#+end_src
**** remove-hook!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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)
**** unsetq-hook!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :eval no
(unsetq-hook! 'markdown-mode-hook line-spacing)
#+RESULTS:
: nil
#+begin_src emacs-lisp
(versionp! "28.0" <= emacs-version <= "28.1")
#+end_src
#+RESULTS:
: t
*** doom-modules
**** doom!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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
**** use-package!
:PROPERTIES:
:added: 3.0.0-pre
:END:
#+begin_src emacs-lisp :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))
;; 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)
;; 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))
;; 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
*** doom-cli
**** TODO defcli!
**** TODO defcli-alias!
**** TODO defcli-obsolete!
**** TODO defcli-stub!
**** TODO defcli-autoload!
**** TODO defcli-group!
**** TODO exit!
**** TODO call!
**** TODO run!
**** TODO sh!
**** TODO sh!!
**** TODO git!
**** TODO def-cli-context-get
**** TODO def-cli-context-put
**** TODO def-cli-context-find-option
**** TODO def-cli-call
**** TODO def-cli-exit
**** TODO def-cli-load
**** TODO def-cli-load-all
**** TODO doom-cli-find
**** TODO doom-cli-get
**** TODO doom-cli-prop
**** TODO doom-cli-subcommands
**** TODO doom-cli-aliases
*** TODO lib/files.el
**** TODO doom-path
**** TODO doom-glob
**** TODO doom-dir
**** TODO doom-files-in
**** TODO doom-file-cookie-p
**** TODO file-exists-p!
**** TODO doom-file-size
**** TODO doom-file-line-count
**** TODO doom-directory-size
**** TODO doom-file-read
**** TODO doom-file-write
**** TODO with-file-contents!
- =$DOOMDIR/profiles.el=
- =$EMACSDIR/profiles.el=
- =~/.config/doom-profiles.el=
- =~/.doom-profiles.el=
(profile2
...)
(profile3
...))
#+end_src
*** =.doomprofile=
:PROPERTIES:
:ID: ac37ac6f-6082-4c34-b98c-962bc1e528c9
:END:
This file takes after the second level of =profiles.el='s format (see a more
complete example in [[id:f9bce7da-d155-4727-9b6f-b566b5b8d824][the previous
section]]). For example:
#+begin_src emacs-lisp
;;; -*- mode: emacs-lisp -*-
;; A .doomprofile can be placed under an implicit profile. Same rules as
;; .doom-profiles.el, but one level deeper.
((var . value)
("envvar" . value)
(var :directive values...))
#+end_src
***** ~mkdir~
#+begin_src emacs-lisp :eval no
#!/usr/bin/env doomscript
(defcli! mkdir
((mode ("-m" "--mode" mode))
(parents? ("-p" "--parents"))
(verbose? ("-v" "--verbose"))
&args directories)
"Create the DIRECTORIES, if do not already exist.
Mandatory arguments to long options are mandatory for short options too.
OPTIONS:
-m, --mode
set file mode (as in chmod), not a=rwx - umask.
-p, --parents
no error if existing, make parent directories as needed, with their file
modes unaffected by any `-m' option.
-v, --verbose
print a message for each created directory
AUTHOR:
Original program by David MacKenzie. Doomscript port by Henrik Lissner.
SEE ALSO:
`mkdir(2)`
Packaged by https://nixos.org
Copyright © 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/li‐
censes/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."
(dolist (dir directories)
(unless (file-directory-p dir)
(make-directory dir parents?)
(when mode
(set-file-modes dir mode))
(when verbose?
(print! "mkdir: created directory '%s'" dir)))))
#+end_src
****** Notes
- Docstrings for Doom CLIs recognize indented sections with a capitalized
heading followed by a colon (like ~SEE ALSO:~, ~OPTIONS:~, etc). They will be
appended to the --help output for this command. ~OPTIONS~ and ~ARGUMENTS~ are
special, in that they decorate pre-existing documentation for referenced
options/arguments.
- The options were documented in the CLI's docstring, instead of inline like so:
#+begin_src emacs-lisp
((mode ("-m" "--mode" mode) "set file modes (as in chmod), not a=rwx -
umask.")
(parents? ("-p" "--parents") "no error if existing, make parent directories
as needed, with their file modes unaffected by any `-m' option.")
(verbose? ("-v" "--verbose") "print a message for each created directory")
&args directories)
#+end_src
Either is acceptable, but for long docs like this, it's better suited to the
docstring. If both were present, Doom's help docs would have concatenated them
(separated by two newlines).
- The ~mode~ option takes one argument, a chmod mask. I indicate this with
~"`MODE'"~. This is a special syntax for highlighting arguments in the help
docs of this command. If I had used a symbol, instead (one of the predefined
types in [[var:][doom-cli-argument-value-types]]), I would've gotten free type-
checking
and error handling, but there is no predefined type for chmod masks (yet), so
I'd have to do my own checks:
That said, set-file-modes will throw its own type error, but it likely won't
be as user friendly.
(defcli! say
((name ("--speaker" name) "Who is speaking?")
&args args)
"This command repeats what you say to it.
It serves as an example of the bare minimum you need to write a Doom-based CLI.
Naturally, it could be more useful; it could process more complex options and
arguments, call other Doom CLIs, read/write data from files or over networks --
but that can wait for more complicated examples.
ARGUMENTS:
ARGS
The message to be repeated back at you.
OPTIONS:
--speaker
If not specified, it is assumed that Emacs is speaking."
(print! "%s says: %S"
(or name "Emacs")
(string-join args " ")))
***** emacs
This isn't useful, but it should hopefully demonstrate the full spectrum of
Doom's CLI, by reimplementing a subset of ~emacs~'s options and arguments (and
none of its documentation). It will simply forward them to the real program
afterwards.
Since I don't want to override the real ~emacs~ in the ~$PATH~, I'll just call
it ~demacs~:
(defcli! demacs
((cd ("--chdir" dir))
(quick? ("-Q" "--quick"))
(no-init? ("-q" "--no-init-file"))
(no-slisp? ("-nsl" "--no-site-lisp"))
(no-sfile? ("--no-site-file"))
(initdir ("--init-directory" dir))
(batch? ("--batch"))
(batch (("-l" "--load" (file) ...))
(("-e" "--eval" (form) ...))
(("-f" "--funcall" (fn) ...))
(("-L" "--directory" (dir) ...))
(("--kill")))
(script ("--script" (file)))
&args (args (file linecol)))
"Demacs is a thin wrapper around Emacs, made to demo of Doom's CLI Framework.
Since documentation isn't the focus of this example, this is all you'll get!"
(cond (script (load script))
(batch?
(dolist (do batch)
(pcase do
(`(,(or "-l" "--load") . ,file) (load file))
(`(,(or "-e" "--eval") . ,form) (eval (read form) t))
(`(,(or "-f" "--funcall") . ,fn) (funcall (read fn)))
(`("--kill" . t) (kill-emacs 0)))))
((exit! :then (cons "emacs"
(append (if quick '("-Q"))
(if no-init? '("-q"))
(if no-slisp? '("-nsl"))
(if no-sfile? '("--no-site-file"))
(if initdir `("--init-directory" ,initdir))
args))))))
#+end_src
****** Notes
There's a lot of (intentional) redundancy here, for posterity. A *much* simpler
(and more reliable) version of this command would've looked like this:
#+begin_src emacs-lisp
(defcli! demacs (&rest args)
(exit! :then (cons "emacs" args)))
#+end_src