Update & revise docs/faq.org
This commit is contained in:
parent
827225e331
commit
6cdc060432
1 changed files with 139 additions and 234 deletions
373
docs/faq.org
373
docs/faq.org
|
@ -37,7 +37,7 @@
|
||||||
- [[#can-vimevil-be-removed-for-a-more-vanilla-emacs-experience][Can Vim/Evil be removed for a more vanilla Emacs experience?]]
|
- [[#can-vimevil-be-removed-for-a-more-vanilla-emacs-experience][Can Vim/Evil be removed for a more vanilla Emacs experience?]]
|
||||||
- [[#should-i-use-make-or-bindoom][Should I use ~make~ or ~bin/doom~?]]
|
- [[#should-i-use-make-or-bindoom][Should I use ~make~ or ~bin/doom~?]]
|
||||||
- [[#when-should-and-shouldnt-i-use-bindoom][When should and shouldn't I use ~bin/doom~?]]
|
- [[#when-should-and-shouldnt-i-use-bindoom][When should and shouldn't I use ~bin/doom~?]]
|
||||||
- [[#when-to-run-doom-refresh][When to run ~doom refresh~]]
|
- [[#when-to-run-doom-sync][When to run ~doom sync~]]
|
||||||
- [[#how-to-suppress-confirmation-prompts-while-bindoom-is-running][How to suppress confirmation prompts while ~bin/doom~ is running]]
|
- [[#how-to-suppress-confirmation-prompts-while-bindoom-is-running][How to suppress confirmation prompts while ~bin/doom~ is running]]
|
||||||
- [[#defaults][Defaults]]
|
- [[#defaults][Defaults]]
|
||||||
- [[#why-ivy-over-helm][Why Ivy over Helm?]]
|
- [[#why-ivy-over-helm][Why Ivy over Helm?]]
|
||||||
|
@ -134,15 +134,15 @@ Doom had +four+ *five* goals for its package management system:
|
||||||
are out-of-date through official channels, have changed hands, have a
|
are out-of-date through official channels, have changed hands, have a
|
||||||
superior fork, or aren't available in ELPA repos.
|
superior fork, or aren't available in ELPA repos.
|
||||||
3. *Performance:* lazy-loading the package management system is a tremendous
|
3. *Performance:* lazy-loading the package management system is a tremendous
|
||||||
boon to start up speed. Initializing package.el and quelpa (and/or checking
|
boon to start up speed. Initializing package.el and straight (and/or checking
|
||||||
that your packages are installed) every time you start up is expensive.
|
that your packages are installed) each time you start up is expensive.
|
||||||
4. *Organization:* an Emacs configuration grows so quickly, in complexity and
|
4. *Organization:* an Emacs configuration grows so quickly, in complexity and
|
||||||
size. A clear separation of concerns (configuration of packages from their
|
size. A clear separation of concerns (configuration of packages from their
|
||||||
installation) is more organized.
|
installation) is more organized.
|
||||||
5. *Reproducibility:* /This goal hasn't been implemented yet/, but all our work
|
5. *Reproducibility:* /This goal hasn't been implemented yet/, but all our work
|
||||||
up until now is aimed at this goal. Emacs is a tumultuous ecosystem; packages
|
up until now is aimed at this goal. Emacs is a tumultuous ecosystem; packages
|
||||||
break left and right, and we rely on hundreds of them. Eventually, we want
|
break left and right, and we rely on hundreds of them. Eventually, we want
|
||||||
package versions to be locked to versions of Doom so that Doom installs are
|
package versions to be locked to Doom's releases so that Doom installs are
|
||||||
reproducible.
|
reproducible.
|
||||||
|
|
||||||
** How does Doom start up so quickly?
|
** How does Doom start up so quickly?
|
||||||
|
@ -160,10 +160,10 @@ up ~gc-cons-threshold~ (and perhaps ~gc-cons-percentage~) temporarily:
|
||||||
;; ... your emacs config here ...
|
;; ... your emacs config here ...
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
However, it is important to reset it eventually (as late as possible). Not doing
|
However, it is important to reset it eventually. Not doing so will cause garbage
|
||||||
so will cause garbage collection freezes during long-term interactive use.
|
collection freezes during long-term interactive use. Conversely, a
|
||||||
Conversely, a ~gc-cons-threshold~ that is too small will cause stuttering. We
|
~gc-cons-threshold~ that is too small will cause stuttering. We use 16mb as our
|
||||||
use 16mb as our default.
|
default.
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(add-hook 'emacs-startup-hook
|
(add-hook 'emacs-startup-hook
|
||||||
|
@ -190,14 +190,16 @@ helm and ivy). Here is how Doom does it:
|
||||||
(add-hook 'minibuffer-exit-hook #'doom-restore-garbage-collection-h)
|
(add-hook 'minibuffer-exit-hook #'doom-restore-garbage-collection-h)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
Another alternative (which is [[https://github.com/hlissner/doom-emacs/blob/develop/core/core.el#L269-L274][what Doom uses]]) is to use the [[https://gitlab.com/koral/gcmh/][gcmh]] package to
|
||||||
|
stave off the GC until you are idle or unfocus the Emacs frame.
|
||||||
|
|
||||||
*** Unset ~file-name-handler-alist~ temporarily
|
*** Unset ~file-name-handler-alist~ temporarily
|
||||||
Emacs consults this variable every time a file is read or library loaded, or
|
Emacs consults this variable every time a file is read or library loaded, or
|
||||||
when certain functions in the file API are used (like ~expand-file-name~ or
|
when certain functions in the file API are used (like ~expand-file-name~ or
|
||||||
~file-truename~).
|
~file-truename~).
|
||||||
|
|
||||||
They do so to check if a special handler is needed to read it, but none of these
|
Emacs does to check if a special handler is needed to read that file, but none
|
||||||
handlers are necessary for the initialization work we do at startup, so it is
|
of them are (typically) necessary at startup, so we disable them (temporarily!):
|
||||||
generally safe to disable it (temporarily!):
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar doom--file-name-handler-alist file-name-handler-alist)
|
(defvar doom--file-name-handler-alist file-name-handler-alist)
|
||||||
|
@ -214,60 +216,38 @@ generally safe to disable it (temporarily!):
|
||||||
(setq file-name-handler-alist doom--file-name-handler-alist)))
|
(setq file-name-handler-alist doom--file-name-handler-alist)))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
It is important to restore this variable, otherwise you won't be able to use
|
Don't forget to restore ~file-name-handler-alist~, otherwise TRAMP won't work
|
||||||
TRAMP and Emacs will be unable to read compressed/encrypted files.
|
and compressed/encrypted files won't open.
|
||||||
|
|
||||||
*** Cut down on ~load-path~ lookups
|
|
||||||
Each ~load~ and ~require~ call (without an second argument) costs an O(n) lookup
|
|
||||||
on ~load-path~. The average Doom config has approximately 260 packages including
|
|
||||||
dependencies, and around 40 built-in packages. That means a minimum of 300
|
|
||||||
entries in ~load-path~ with a worst case of =n=300= for /each/ package load (but
|
|
||||||
realistically, =n= will be somewhere between =2= and =20=).
|
|
||||||
|
|
||||||
The cost isn't great, but it does add up. There isn't much to do about this,
|
|
||||||
except be mindful of it where we can:
|
|
||||||
|
|
||||||
+ Paths in Doom's autoloads file are replaced with absolute ones, thus incurring
|
|
||||||
no lookup cost to lazy load them.
|
|
||||||
+ The ~load!~ macro is used instead of ~require~ where possible. This builds
|
|
||||||
paths with string concatenation (which is baked in at compile time, removing
|
|
||||||
most of the associated cost).
|
|
||||||
+ ~load-path~ is let-bound to a subset of itself where possible (the
|
|
||||||
~doom--initial-load-path~ variable contains the value of ~load-path~ before it
|
|
||||||
was touched by Doom).
|
|
||||||
|
|
||||||
*** Concatenate package autoloads
|
*** Concatenate package autoloads
|
||||||
When you install a package, a PACKAGE-autoloads.el file is generated. This file
|
When you install a package, a PACKAGE-autoloads.el file is generated. This file
|
||||||
contains a map of autoloaded functions and snippets declared by the package
|
contains a map of autoloaded functions and snippets declared by the package.
|
||||||
(that's what those ~;;;###autoload~ comments are for in packages). They tell
|
They tell Emacs where to find them when they are eventually called. In your
|
||||||
Emacs where to find them, when they are eventually called. In your conventional
|
conventional Emacs config, every one of these autoloads files are loaded
|
||||||
Emacs config, every single one of these autoloads files are loaded immediately
|
immediately at startup (when ~package-initialize~ is called).
|
||||||
at startup.
|
|
||||||
|
|
||||||
Since you'll commonly have hundreds of packages, loading hundreds of autoloads
|
Since you'll commonly have hundreds of packages, loading hundreds of autoloads
|
||||||
file can hurt startup times. We get around this by concatenating these autoloads
|
file can hurt startup times, especially without an SSD. We get around this by
|
||||||
files into one giant one (in =~/.emacs.d/.local/autoloads.pkg.el=) when you run
|
concatenating these files into one giant one when you run ~doom sync~.
|
||||||
~doom refresh~.
|
|
||||||
|
|
||||||
Emacs 27+ will introduce a ~package-quickstart~ feature that will do this for
|
Emacs 27+ introduces a ~package-quickstart~ command does this for you, and
|
||||||
you -- the =straight= package manager does this for you too -- but Doom Emacs
|
=straight=, our package manager, does this for you too, but [[https://github.com/hlissner/doom-emacs/tree/develop/core/cli/autoloads.el][Doom Emacs has its
|
||||||
has its own specialized mechanism for doing this, and has tacked a number of
|
own specialized mechanism]] for this, topped off with a few Doom-specific
|
||||||
Doom-specific optimizations on top of it.
|
optimizations.
|
||||||
|
|
||||||
*** Lazy load package management system(s)
|
*** Lazy load package management system(s)
|
||||||
Initializing package.el or straight.el at startup is expensive. We can save some
|
Initializing package.el or straight.el at startup is expensive. We can save some
|
||||||
time by delaying that initialization until we actually need these libraries (and
|
time by delaying that initialization until we actually need these libraries (and
|
||||||
only eagerly load them when we're doing package management, e.g. when we run
|
load them only when we're doing package management, e.g. when we run ~doom
|
||||||
~doom refresh~).
|
sync~).
|
||||||
|
|
||||||
Among other things, ~doom refresh~ does a lot for us. It generates concatenated
|
Among other things, ~doom sync~ does a lot for us. It generates concatenated
|
||||||
autoloads files; caches expensive variables like caches ~load-path~,
|
autoloads files; caches expensive variables like caches ~load-path~,
|
||||||
~Info-directory-list~ and ~auto-mode-alist~; and preforms all your package
|
~Info-directory-list~ and ~auto-mode-alist~; and preforms all your package
|
||||||
management activities there -- far away from your interactive sessions.
|
management activities there -- far away from your interactive sessions.
|
||||||
|
|
||||||
How exactly Doom accomplishes all this is a little complex, so instead, here is
|
How exactly Doom accomplishes all this is a long story, so here is a boiled-down
|
||||||
a boiled-down version you can use in your own configs (for package.el, not
|
version you can use in your own configs (for package.el, not straight.el):
|
||||||
straight.el):
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar cache-file "~/.emacs.d/cache/autoloads")
|
(defvar cache-file "~/.emacs.d/cache/autoloads")
|
||||||
|
@ -278,26 +258,12 @@ straight.el):
|
||||||
(package-initialize)
|
(package-initialize)
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(cl-pushnew doom-core-dir load-path :test #'string=)
|
(cl-pushnew doom-core-dir load-path :test #'string=)
|
||||||
(dolist (spec package-alist)
|
(dolist (desc (delq nil (mapcar #'cdr package-alist)))
|
||||||
(when-let (desc (cdr spec))
|
(let ((load-file-name (concat (package--autoloads-file-name desc) ".el")))
|
||||||
(let ((file (concat (package--autoloads-file-name desc) ".el")))
|
(when (file-readable-p load-file-name)
|
||||||
(when (file-readable-p file)
|
(condition-case _
|
||||||
;; Ensure that the contents of this autoloads file believes they
|
(while t (insert (read (current-buffer))))
|
||||||
;; haven't been moved:
|
(end-of-file)))))
|
||||||
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
|
|
||||||
(insert-file-contents file)
|
|
||||||
(save-excursion
|
|
||||||
;; Delete forms that modify `load-path' and `auto-mode-alist', we
|
|
||||||
;; will set them once, later.
|
|
||||||
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
|
|
||||||
(goto-char (match-beginning 1))
|
|
||||||
(kill-sexp)))
|
|
||||||
;; Remove unnecessary comment lines and (provide ...) forms
|
|
||||||
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
|
|
||||||
(unless (nth 8 (syntax-ppss))
|
|
||||||
(replace-match "" t t)))
|
|
||||||
(unless (bolp) (insert "\n"))
|
|
||||||
(insert ")\n")))))
|
|
||||||
(prin1 `(setq load-path ',load-path
|
(prin1 `(setq load-path ',load-path
|
||||||
auto-mode-alist ',auto-mode-alist
|
auto-mode-alist ',auto-mode-alist
|
||||||
Info-directory-list ',Info-directory-list)
|
Info-directory-list ',Info-directory-list)
|
||||||
|
@ -309,10 +275,9 @@ straight.el):
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
You'll need to delete ~cache-files~ any time you install, remove, or update a
|
You'll need to delete ~cache-files~ any time you install, remove, or update a
|
||||||
new package, however. In that case you could advise ~package-install~ and
|
new package. You could advise ~package-install~ and ~package-delete~ to call
|
||||||
~package-delete~ to call ~initialize~ when they succeed. Or, you could make
|
~initialize~ when they succeed, or make ~initialize~ interactive and call it
|
||||||
~initialize~ interactive and call it manually when you determine it's necessary.
|
manually when necessary. Up to you!
|
||||||
Up to you!
|
|
||||||
|
|
||||||
Note: package.el is sneaky, and will initialize itself if you're not careful.
|
Note: package.el is sneaky, and will initialize itself if you're not careful.
|
||||||
*Not on my watch, criminal scum!*
|
*Not on my watch, criminal scum!*
|
||||||
|
@ -327,20 +292,21 @@ Note: package.el is sneaky, and will initialize itself if you're not careful.
|
||||||
*** Lazy load more than everything
|
*** Lazy load more than everything
|
||||||
~use-package~ can defer your packages. Using it is a no-brainer, but Doom goes a
|
~use-package~ can defer your packages. Using it is a no-brainer, but Doom goes a
|
||||||
little further with lazy loading. There are some massive plugins out there. For
|
little further with lazy loading. There are some massive plugins out there. For
|
||||||
many of them, ordinary lazy loading techniques simply don't work. To name a few:
|
some of them, ordinary lazy loading techniques don't work. To name a few:
|
||||||
|
|
||||||
+ The =lang/org= module defers loading babel packages until their src blocks are
|
+ The =lang/org= module defers loading babel packages until their src blocks are
|
||||||
executed. You no longer need ~org-babel-do-load-languages~ in your config.
|
executed or read. You no longer need ~org-babel-do-load-languages~ in your
|
||||||
|
config -- in fact, you shouldn't use it at all!
|
||||||
+ Company and yasnippet are loaded as late as possible (waiting until the user
|
+ Company and yasnippet are loaded as late as possible (waiting until the user
|
||||||
opens a non-read-only, file-visiting buffer (that isn't in fundamental-mode)).
|
opens a non-read-only, file-visiting buffer (that isn't in fundamental-mode)).
|
||||||
+ The =evil-easymotion= package has many keybinds. You'd need to load the
|
+ The =evil-easymotion= package binds many keys, none of which are available
|
||||||
package for them to all take effect, so instead, =gs= is bound to a command
|
until you load the package. Instead of loading it at startup, =gs= is bound to
|
||||||
that loads the package and then invisibly populates =gs=, then simulates the
|
a command that loads the package, populates =gs=, then simulates the =gs= key
|
||||||
=gs= keypress as though those new keys had always been there.
|
press as though those new keys had always been there.
|
||||||
+ A number of packages are "incrementally" loaded. This is a Doom feature where,
|
+ Doom loads some packages "incrementally". i.e. after a few seconds of idle
|
||||||
after a few seconds of idle time post-startup, Doom will load packages
|
time post-startup, Doom loads packages piecemeal (one dependency at a time)
|
||||||
piecemeal while Emacs. It will quickly abort if it detects input, as to make
|
while Emacs. It aborts if it detects input, as to make the process as subtle
|
||||||
the process as subtle as possible.
|
as possible.
|
||||||
|
|
||||||
For example, instead of loading =org= (a giant package), it will load these
|
For example, instead of loading =org= (a giant package), it will load these
|
||||||
dependencies, one at a time, before finally loading =org=:
|
dependencies, one at a time, before finally loading =org=:
|
||||||
|
@ -354,21 +320,6 @@ many of them, ordinary lazy loading techniques simply don't work. To name a few:
|
||||||
This ensures packages load as quickly as possible when you first load an org
|
This ensures packages load as quickly as possible when you first load an org
|
||||||
file.
|
file.
|
||||||
|
|
||||||
*** +Exploit byte-compilation!+
|
|
||||||
It used to be that byte-compilation bought a 40-60% improvement in startup
|
|
||||||
times, because expensive operations (like ~package-initialize~ or
|
|
||||||
~exec-path-from-shell~) were evaluated at compile time, but Doom has changed.
|
|
||||||
|
|
||||||
I've since adopted a pre-cache approach (when running ~doom refresh~), which
|
|
||||||
brings these startup benefits to uncompiled Emacs. This renders byte-compilation
|
|
||||||
significantly less beneficial for startup time.
|
|
||||||
|
|
||||||
That said, compilation will still benefit Doom's snappiness in general.
|
|
||||||
|
|
||||||
Run ~doom compile :core~ to only compile Doom's core files, or ~doom compile~ to
|
|
||||||
compile the /entire/ config (=~/.emacs.d= and =~/.doom.d=) -- which may take a
|
|
||||||
while.
|
|
||||||
|
|
||||||
*** Use [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical-binding]] everywhere
|
*** Use [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical-binding]] everywhere
|
||||||
Add ~;; -*- lexical-binding: t; -*-~ to the top of your elisp files. This can
|
Add ~;; -*- lexical-binding: t; -*-~ to the top of your elisp files. This can
|
||||||
break code if you've written it to depend on undeclared dynamic variables, but
|
break code if you've written it to depend on undeclared dynamic variables, but
|
||||||
|
@ -381,32 +332,30 @@ find more about it in:
|
||||||
+ [[http://nullprogram.com/blog/2016/12/22/]["Some Performance Advantages of Lexical Scope."]]
|
+ [[http://nullprogram.com/blog/2016/12/22/]["Some Performance Advantages of Lexical Scope."]]
|
||||||
|
|
||||||
** Why is startup time important? Why not use the daemon?
|
** Why is startup time important? Why not use the daemon?
|
||||||
One of my motivations for a config that starts up fast (aside from the learning
|
The central motivation for a config that starts up fast (aside from the learning
|
||||||
experience) was to shape Emacs into a viable alternative to vim for one-shot
|
experience) was to have a viable alternative to vim for quick, one-shot editing
|
||||||
editing in the terminal (without ~-Q~). This also facilitates:
|
in the terminal (without ~-Q~).
|
||||||
|
|
||||||
- Running multiple, independent instances of Emacs (e.g. on a per-project basis, or
|
Besides that, it happens to facilitate:
|
||||||
for nix-shell users, or to isolate one instance for IRC from an instance for
|
|
||||||
writing code, etc).
|
- Running multiple, independent instances of Emacs (e.g. on a per-project basis,
|
||||||
|
or for nix-shell users, or to isolate one instance for IRC from an instance
|
||||||
|
for writing code, etc).
|
||||||
- Quicker restarting of Emacs, to reload package settings or recover from
|
- Quicker restarting of Emacs, to reload package settings or recover from
|
||||||
disastrous errors which can leave Emacs in a broken state.
|
disastrous errors which can leave Emacs in a broken state.
|
||||||
- Faster integration with "edit in Emacs" solutions (like [[https://github.com/alpha22jp/atomic-chrome][atomic-chrome]]), and
|
- Faster integration with "edit in Emacs" solutions (like [[https://github.com/alpha22jp/atomic-chrome][atomic-chrome]]), and
|
||||||
the potential to use them without a running daemon.
|
without a daemon.
|
||||||
|
|
||||||
What's more, I don't like using more tools than I need. We should not need a
|
What's more, I believe a daemon shouldn't be necessary to get a sane startup
|
||||||
second program just to make the first run comfortably.
|
time out of Emacs.
|
||||||
|
|
||||||
** How do I use Doom alongside other Emacs configs?
|
** How do I use Doom alongside other Emacs configs?
|
||||||
I recommend [[https://github.com/plexus/chemacs][Chemacs]]. You can think of it as a bootloader for Emacs. You'll [[file:getting_started.org::*Alongside other Emacs configs (with Chemacs)][find
|
I recommend [[https://github.com/plexus/chemacs][Chemacs]]. You can think of it as a bootloader for Emacs. You'll [[file:getting_started.org::*Alongside other Emacs configs (with Chemacs)][find
|
||||||
instructions on how to use it with Doom in the user manual]].
|
instructions on how to use it with Doom in the user manual]].
|
||||||
|
|
||||||
If you only want to try it out without affecting your current config, it is safe
|
|
||||||
to install Doom anywhere you like. The ~bin/doom~ utility will only address the
|
|
||||||
config the script is located under.
|
|
||||||
|
|
||||||
You'll still need a separate folder for personal configuration (=~/.doom.d= or
|
You'll still need a separate folder for personal configuration (=~/.doom.d= or
|
||||||
=~/.config/doom= by default), but the =-p PATH= flag (or ~DOOMDIR~ environment
|
=~/.config/doom= by default), but the =--doomdir PATH= switch (or ~DOOMDIR~
|
||||||
variable) will allow you to use a different location:
|
environment variable) will allow you to use a different location:
|
||||||
|
|
||||||
#+BEGIN_SRC bash
|
#+BEGIN_SRC bash
|
||||||
# First install Doom somewhere
|
# First install Doom somewhere
|
||||||
|
@ -479,80 +428,28 @@ first ran ~doom install~.
|
||||||
Check out the [[file:getting_started.org::Customize][Customize section]] in the [[file:getting_started.org][Getting Started]] guide for details.
|
Check out the [[file:getting_started.org::Customize][Customize section]] in the [[file:getting_started.org][Getting Started]] guide for details.
|
||||||
|
|
||||||
** How do I enable or disable a Doom module?
|
** How do I enable or disable a Doom module?
|
||||||
You'll find your ~doom!~ block in =~/.doom.d/init.el=. This block contains a
|
Comment or uncomment the module in your ~doom!~ block, found in
|
||||||
list of modules you want enabled and what order to load them in. Disable modules
|
=$DOOMDIR/init.el=.
|
||||||
by commenting them out with semicolons. To enable them, remove those leading
|
|
||||||
semicolons:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
Remember to run ~bin/doom sync~ afterwards, on the command line, to sync your
|
||||||
(doom! :lang
|
|
||||||
python ; this is enabled
|
|
||||||
;;ruby ; this is disabled
|
|
||||||
rust)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Remember to run ~bin/doom refresh~ afterwards, on the command line, to sync your
|
|
||||||
module list with Doom.
|
module list with Doom.
|
||||||
|
|
||||||
You can find a comprehensive list of modules in the [[file:index.org::*Module list][Module Index]].
|
See the "[[file:getting_started.org::*Configuration modules][Configuration modules]]" section of the [[file:getting_started.org][Getting Started]] guide for more
|
||||||
|
information.
|
||||||
|
|
||||||
** How do I install a package from ELPA?
|
** How do I install a package from ELPA?
|
||||||
Add a ~package!~ declaration to =~/.doom.d/packages.el= for each package you
|
See the "[[file:getting_started.org::*Installing%20packages][Installing packages]]" section of the [[file:getting_started.org][Getting Started]] guide.
|
||||||
want installed.
|
|
||||||
|
|
||||||
#+BEGIN_SRC elisp
|
|
||||||
(package! winum)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Remember to run ~doom refresh~ afterwards to ensure the package is installed.
|
|
||||||
|
|
||||||
You'll find more information in the "[[file:getting_started.org::*Installing%20packages][Installing packages]]" section of the [[file:getting_started.org][Getting
|
|
||||||
Started]] guide.
|
|
||||||
|
|
||||||
** How do I install a package from github/another source?
|
** How do I install a package from github/another source?
|
||||||
The ~package!~ macro can be passed a MELPA style recipe, allowing you to install
|
See the "[[file:getting_started.org::*Installing%20packages%20from%20external%20sources][Installing packages from external sources]]" section of the [[file:getting_started.org][Getting
|
||||||
packages from just about anywhere:
|
Started]] guide.
|
||||||
|
|
||||||
#+BEGIN_SRC elisp
|
|
||||||
(package! evil :recipe (:host github :repo "hlissner/my-evil-fork"))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Remember to run ~doom refresh~ every time you modify you package list, to ensure
|
|
||||||
your packages are set up and installed.
|
|
||||||
|
|
||||||
You can find more information about the recipe format [[https://github.com/raxod502/straight.el#the-recipe-format][in the straight.el package
|
|
||||||
readme]].
|
|
||||||
|
|
||||||
#+begin_quote
|
|
||||||
If a MELPA recipe exists for the package you are writing a ~package!~
|
|
||||||
declaration for, you may omit keywords and Doom's package manager will fill them
|
|
||||||
in with values from its original recipe.
|
|
||||||
#+end_quote
|
|
||||||
|
|
||||||
You'll find more information in the "[[file:getting_started.org::*Installing%20packages%20from%20external%20sources][Installing packages from external sources]]"
|
|
||||||
section of the [[file:getting_started.org][Getting Started]] guide.
|
|
||||||
|
|
||||||
** How do I change where an existing package is installed from?
|
** How do I change where an existing package is installed from?
|
||||||
~package!~ declarations in your private =packages.el= file have precedence over
|
See the "[[file:getting_started.org::*Changing%20a%20built-in%20recipe%20for%20a%20package][Changing a built-in recipe for a package]]" section of the [[file:getting_started.org][Getting
|
||||||
modules (even your own). Simply add a new one for that package with the new
|
Started]] guide.
|
||||||
recipe.
|
|
||||||
|
|
||||||
You'll find more information in the "[[file:getting_started.org::*Changing%20a%20built-in%20recipe%20for%20a%20package][Changing a built-in recipe for a package]]"
|
|
||||||
section of the [[file:getting_started.org][Getting Started]] guide.
|
|
||||||
|
|
||||||
** How do I disable a package completely?
|
** How do I disable a package completely?
|
||||||
With the ~package!~ macro's ~:disable~ property:
|
See the "[[file:getting_started.org::*Disabling%20packages][disabling packages]]" section of the [[file:getting_started.org][Getting Started]] guide.
|
||||||
|
|
||||||
#+BEGIN_SRC elisp
|
|
||||||
;;; add to DOOMDIR/packages.el
|
|
||||||
(package! irony :disable t)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Remember to run ~doom refresh~ afterwards to ensure that the package is
|
|
||||||
uninstalled and disabled.
|
|
||||||
|
|
||||||
You'll find more information in the "[[file:getting_started.org::*Disabling%20packages][Disabling packages]]" section of the [[file:getting_started.org][Getting
|
|
||||||
Started]] guide.
|
|
||||||
|
|
||||||
** How do I reconfigure a package included in Doom?
|
** How do I reconfigure a package included in Doom?
|
||||||
~use-package!~ and ~after!~ (wrappers around ~use-package~ and
|
~use-package!~ and ~after!~ (wrappers around ~use-package~ and
|
||||||
|
@ -620,8 +517,8 @@ install it, then load it:
|
||||||
(setq doom-theme 'solarized-dark)
|
(setq doom-theme 'solarized-dark)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
Don't forget to run ~doom refresh~ after adding that ~package!~ statement to
|
Don't forget to run ~doom sync~ after adding that ~package!~ statement to ensure
|
||||||
ensure the package is installed.
|
the package is installed.
|
||||||
|
|
||||||
** How do I change the fonts?
|
** How do I change the fonts?
|
||||||
Doom exposes five (optional) variables for controlling fonts in Doom, they are:
|
Doom exposes five (optional) variables for controlling fonts in Doom, they are:
|
||||||
|
@ -632,8 +529,8 @@ Doom exposes five (optional) variables for controlling fonts in Doom, they are:
|
||||||
+ ~doom-unicode-font~
|
+ ~doom-unicode-font~
|
||||||
+ ~doom-big-font~ (used for ~doom-big-font-mode~)
|
+ ~doom-big-font~ (used for ~doom-big-font-mode~)
|
||||||
|
|
||||||
Each of these will accept either a =font-spec=, font string (="Input Mono-12"=),
|
They all accept either a =font-spec=, font string (="Input Mono-12"=), or [[https://wiki.archlinux.org/index.php/X_Logical_Font_Description][xlfd
|
||||||
or [[https://wiki.archlinux.org/index.php/X_Logical_Font_Description][xlfd font string]].
|
font string]].
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
@ -760,6 +657,7 @@ rules.
|
||||||
|
|
||||||
You'll find more comprehensive documentation on ~set-popup-rule!~ in its
|
You'll find more comprehensive documentation on ~set-popup-rule!~ in its
|
||||||
docstring (available through =SPC h f= -- or =C-h f= for non-evil users).
|
docstring (available through =SPC h f= -- or =C-h f= for non-evil users).
|
||||||
|
|
||||||
** How do I change the appearance a face (or faces)?
|
** How do I change the appearance a face (or faces)?
|
||||||
Doom provides the ~custom-set-faces!~ and ~custom-theme-set-faces!~ macros as a
|
Doom provides the ~custom-set-faces!~ and ~custom-theme-set-faces!~ macros as a
|
||||||
convenience.
|
convenience.
|
||||||
|
@ -785,15 +683,14 @@ tools for experienced Emacs users to skirt around it (most of the time):
|
||||||
- On-the-fly evaluation won't work for all changes. e.g. Changing your ~doom!~
|
- On-the-fly evaluation won't work for all changes. e.g. Changing your ~doom!~
|
||||||
block (i.e. the list of modules for Doom to enable).
|
block (i.e. the list of modules for Doom to enable).
|
||||||
|
|
||||||
But rather than running ~doom refresh~ and restarting Emacs, Doom provides
|
But rather than running ~doom sync~ and restarting Emacs, Doom provides ~M-x
|
||||||
~M-x doom/reload~ for your convenience (bound to =SPC h r r= and =C-h r r=).
|
doom/reload~ for your convenience (bound to =SPC h r r= and =C-h r r=). This
|
||||||
This runs ~doom refresh~, restarts the Doom initialization process and
|
runs ~doom sync~, restarts the Doom initialization process and re-evaluates
|
||||||
re-evaluates your personal config. However, this won't clear pre-existing
|
your personal config. However, this won't clear pre-existing state; Doom won't
|
||||||
state; Doom won't unload modules/packages that have already been loaded and it
|
unload modules/packages that have already been loaded and it can't anticipate
|
||||||
can't anticipate complications arising from a private config that isn't
|
complications arising from a private config that isn't idempotent.
|
||||||
idempotent.
|
- Some ~bin/doom~ commands are available as elisp commands. e.g. ~doom/reload~
|
||||||
- Many ~bin/doom~ commands are available as elisp commands with the ~doom//*~
|
for ~doom sync~, ~doom/upgrade~ for ~doom upgrade~ ~doom//s~, ~doom//update~, etc. Feel free to use them, but
|
||||||
prefix. e.g. ~doom//refresh~, ~doom//update~, etc. Feel free to use them, but
|
|
||||||
consider them highly experimental and subject to change without notice.
|
consider them highly experimental and subject to change without notice.
|
||||||
- You can quickly restart Emacs and restore the last session with
|
- You can quickly restart Emacs and restore the last session with
|
||||||
~doom/restart-and-restore~ (bound to =SPC q r=).
|
~doom/restart-and-restore~ (bound to =SPC q r=).
|
||||||
|
@ -815,7 +712,7 @@ commands that you may find particularly useful:
|
||||||
|
|
||||||
+ ~doom doctor~ :: Diagnose common issues in your environment and list missing
|
+ ~doom doctor~ :: Diagnose common issues in your environment and list missing
|
||||||
external dependencies for your enabled modules.
|
external dependencies for your enabled modules.
|
||||||
+ ~doom refresh~ :: Ensures that all missing packages are installed, orphaned
|
+ ~doom sync~ :: Ensures that all missing packages are installed, orphaned
|
||||||
packages are removed, and metadata properly generated.
|
packages are removed, and metadata properly generated.
|
||||||
+ ~doom install~ :: Install any missing packages.
|
+ ~doom install~ :: Install any missing packages.
|
||||||
+ ~doom update~ :: Update all packages that Doom's (enabled) modules use.
|
+ ~doom update~ :: Update all packages that Doom's (enabled) modules use.
|
||||||
|
@ -829,12 +726,12 @@ commands that you may find particularly useful:
|
||||||
|
|
||||||
#+BEGIN_SRC bash
|
#+BEGIN_SRC bash
|
||||||
git pull
|
git pull
|
||||||
doom refresh
|
doom sync
|
||||||
doom update
|
doom update
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** When to run ~doom refresh~
|
** When to run ~doom sync~
|
||||||
As a rule of thumb you should run ~doom refresh~ whenever you:
|
As a rule of thumb you should run ~doom sync~ whenever you:
|
||||||
|
|
||||||
+ Update Doom with ~git pull~ instead of ~doom upgrade~,
|
+ Update Doom with ~git pull~ instead of ~doom upgrade~,
|
||||||
+ Change your ~doom!~ block in =$DOOMDIR/init.el=,
|
+ Change your ~doom!~ block in =$DOOMDIR/init.el=,
|
||||||
|
@ -843,8 +740,8 @@ As a rule of thumb you should run ~doom refresh~ whenever you:
|
||||||
+ Install an Emacs package or dependency outside of Emacs (i.e. through your OS
|
+ Install an Emacs package or dependency outside of Emacs (i.e. through your OS
|
||||||
package manager).
|
package manager).
|
||||||
|
|
||||||
If anything is misbehaving, it's a good idea to run ~doom refresh~ first. ~doom
|
If anything is misbehaving, it's a good idea to run ~doom sync~ first. ~doom
|
||||||
refresh~ is responsible for regenerating your autoloads file (which tells Doom
|
sync~ is responsible for regenerating your autoloads file (which tells Doom
|
||||||
where to find lazy-loaded functions and libraries), installing missing packages,
|
where to find lazy-loaded functions and libraries), installing missing packages,
|
||||||
and uninstall orphaned (unneeded) packages.
|
and uninstall orphaned (unneeded) packages.
|
||||||
|
|
||||||
|
@ -860,7 +757,7 @@ YES=1 doom update
|
||||||
|
|
||||||
* Defaults
|
* Defaults
|
||||||
** Why Ivy over Helm?
|
** Why Ivy over Helm?
|
||||||
Short answer: I chose ivy because it is the simpler of the two.
|
Short answer: ivy is simpler to maintain.
|
||||||
|
|
||||||
Long answer: Features and performance appear to be the main talking points when
|
Long answer: Features and performance appear to be the main talking points when
|
||||||
comparing the two, but as far as I'm concerned they are equal in both respects
|
comparing the two, but as far as I'm concerned they are equal in both respects
|
||||||
|
@ -930,21 +827,20 @@ Otherwise, it is trivial to install expand-region and binds keys to it yourself:
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Why not use exec-path-from-shell instead of ~doom env~?
|
** Why not use exec-path-from-shell instead of ~doom env~?
|
||||||
In a nutshell, the ~doom env~ approach is a faster and more robust solution.
|
The ~doom env~ approach is a faster and more reliable solution.
|
||||||
|
|
||||||
1. ~exec-path-from-shell~ must spawn (at least) one process at startup to scrape
|
1. ~exec-path-from-shell~ must spawn (at least) one process at startup to scrape
|
||||||
your shell environment. This can be arbitrarily slow depending on the user's
|
your shell environment. This can be slow depending on the user's shell
|
||||||
shell configuration. A single program (like pyenv or nvm) or config framework
|
configuration. A single program (like pyenv or nvm) or config framework (like
|
||||||
(like oh-my-zsh) could undo all of Doom's startup optimizations in one fell
|
oh-my-zsh) could undo Doom's startup optimizations in one fell swoop.
|
||||||
swoop.
|
|
||||||
|
|
||||||
2. ~exec-path-from-shell~ only scrapes /some/ state from your shell. You have to
|
2. ~exec-path-from-shell~ takes a whitelist approach and captures only ~PATH~
|
||||||
be proactive in order to get it to capture all the envvars relevant to your
|
and ~MANPATH~ by default. You must be proactive in order to capture all the
|
||||||
development environment.
|
envvars relevant to your development environment and tools.
|
||||||
|
|
||||||
I'd rather it inherit your shell environment /correctly/ (and /completely/)
|
~doom env~ takes the blacklist approach and captures all of your shell
|
||||||
or not at all. It frontloads the debugging process rather than hiding it
|
environment. This front loads the debugging process, which is nicer than dealing
|
||||||
until it you least want to deal with it.
|
with it later, while you're getting work done.
|
||||||
|
|
||||||
That said, if you still want ~exec-path-from-shell~, it is trivial to install
|
That said, if you still want ~exec-path-from-shell~, it is trivial to install
|
||||||
yourself:
|
yourself:
|
||||||
|
@ -964,16 +860,16 @@ TL;DR: =ws-butler= is less imposing.
|
||||||
Don't be that guy who PRs 99 whitespace adjustments around his one-line
|
Don't be that guy who PRs 99 whitespace adjustments around his one-line
|
||||||
contribution. Don't automate this aggressive behavior by attaching
|
contribution. Don't automate this aggressive behavior by attaching
|
||||||
~delete-trailing-whitespace~ (or ~whitespace-cleanup~) to ~before-save-hook~. If
|
~delete-trailing-whitespace~ (or ~whitespace-cleanup~) to ~before-save-hook~. If
|
||||||
you have rambunctious colleagues peppering trailing whitespace into your project,
|
you have rambunctious colleagues peppering trailing whitespace into your
|
||||||
you need to have a talk (with wiffle bats, preferably) rather than play this
|
project, you need to have a talk (with wiffle bats, preferably) rather than play
|
||||||
passive-aggressive game of whack-a-mole.
|
a passive-aggressive game of whack-a-mole.
|
||||||
|
|
||||||
Here at Doom Inc we believe that operations that mutate entire files should
|
Here at Doom Inc we believe that operations that mutate entire files should not
|
||||||
never be automated. Rather, they should be invoked deliberately -- by someone
|
be automated. Rather, they should be invoked deliberately, when and where it is
|
||||||
that is aware of the potential consequences. This is where =ws-butler= comes in.
|
needed, by someone that is aware of the potential consequences. This is where
|
||||||
It only cleans up whitespace /on the lines you've touched/ *and* it leaves
|
=ws-butler= comes in. It only cleans up whitespace /on the lines you've touched/
|
||||||
behind virtual whitespace (which is never written to the file, but remains there
|
*and* it leaves behind virtual whitespace (which is never written to the file)
|
||||||
so your cursor doesn't get thrown around in all that cleanup work).
|
so your cursor doesn't get thrown around in all that cleanup work.
|
||||||
|
|
||||||
In any case, if you had used =ws-butler= from the beginning, trailing whitespace
|
In any case, if you had used =ws-butler= from the beginning, trailing whitespace
|
||||||
and newlines would never be a problem!
|
and newlines would never be a problem!
|
||||||
|
@ -1010,14 +906,14 @@ manually (e.g. by double-clicking each file in explorer).
|
||||||
** ~void-variable~ and ~void-function~ errors on startup
|
** ~void-variable~ and ~void-function~ errors on startup
|
||||||
The most common culprit for these types of errors are:
|
The most common culprit for these types of errors are:
|
||||||
|
|
||||||
1. An out-of-date autoloads file. To regenerate it, run ~doom refresh~.
|
1. An out-of-date autoloads file. Run ~doom sync~ to regenerate them.
|
||||||
|
|
||||||
To avoid this issue, remember to run ~doom refresh~ whenever you modify your
|
To avoid this issue, remember to run ~doom sync~ whenever you modify your
|
||||||
~doom!~ block in =~/.doom.d/init.el=, or add ~package!~ declarations to
|
~doom!~ block in =~/.doom.d/init.el=, or add ~package!~ declarations to
|
||||||
=~/.doom.d/packages.el=. Or if you modify =~/.emacs.d/.local= by hand, for
|
=~/.doom.d/packages.el=. Or if you modify =~/.emacs.d/.local= by hand, for
|
||||||
whatever reason.
|
whatever reason.
|
||||||
|
|
||||||
See ~doom help refresh~ for details on what this command does and when you
|
See ~doom help sync~ for details on what this command does and when you
|
||||||
should use it.
|
should use it.
|
||||||
|
|
||||||
2. Emacs byte-code isn't forward compatible. If you've recently switched to a
|
2. Emacs byte-code isn't forward compatible. If you've recently switched to a
|
||||||
|
@ -1075,8 +971,8 @@ If you still want to restore the old behavior, simply disable evil-snipe-mode:
|
||||||
1. Make sure you don't have both =~/.doom.d= and =~/.config/doom= directories.
|
1. Make sure you don't have both =~/.doom.d= and =~/.config/doom= directories.
|
||||||
Doom will ignore the former if the latter exists.
|
Doom will ignore the former if the latter exists.
|
||||||
|
|
||||||
2. Remember to run ~doom refresh~ when it is necessary. To get to know when,
|
2. Remember to run ~doom sync~ when it is necessary. To get to know when,
|
||||||
exactly, you should run this command, run ~doom help refresh~.
|
exactly, you should run this command, run ~doom help sync~.
|
||||||
|
|
||||||
If neither of these solve your issue, try ~bin/doom doctor~. It will detect a
|
If neither of these solve your issue, try ~bin/doom doctor~. It will detect a
|
||||||
variety of common issues, and may give you some clues as to what is wrong.
|
variety of common issues, and may give you some clues as to what is wrong.
|
||||||
|
@ -1138,7 +1034,7 @@ Emacs will search for this file in ~custom-theme-load-path~ and
|
||||||
~doom-theme~ to ~'third-party-theme~, it will search for
|
~doom-theme~ to ~'third-party-theme~, it will search for
|
||||||
~third-party-theme-theme.el~. This is rarely intentional. Omit the ~-theme~
|
~third-party-theme-theme.el~. This is rarely intentional. Omit the ~-theme~
|
||||||
suffix.
|
suffix.
|
||||||
3. Did you run ~doom refresh~ after adding your third party theme plugin's
|
3. Did you run ~doom sync~ after adding your third party theme plugin's
|
||||||
~package!~ declaration to =~/.doom.d/packages.el=?
|
~package!~ declaration to =~/.doom.d/packages.el=?
|
||||||
** TRAMP connections hang forever when connecting
|
** TRAMP connections hang forever when connecting
|
||||||
You'll find solutions [[https://www.emacswiki.org/emacs/TrampMode#toc7][on the emacswiki]].
|
You'll find solutions [[https://www.emacswiki.org/emacs/TrampMode#toc7][on the emacswiki]].
|
||||||
|
@ -1148,21 +1044,30 @@ upstream, you can't run ~doom update~ to get the latest fixes due to evaluation
|
||||||
errors.
|
errors.
|
||||||
|
|
||||||
In those cases, you need to delete the broken local copy before you can install
|
In those cases, you need to delete the broken local copy before you can install
|
||||||
the new one, which is achieved thus:
|
the new one, which is achieved by either deleting it from
|
||||||
|
=~/.emacs.d/.local/straight/repos=, or by cycling the module that installs it:
|
||||||
|
|
||||||
1. Comment out the broken module/package.
|
1. Comment out the broken module/package.
|
||||||
2. Run ~doom refresh -p~.
|
2. Run ~doom sync~.
|
||||||
3. Uncomment the module/package.
|
3. Uncomment the module/package.
|
||||||
4. Run ~doom refresh~.
|
4. Run ~doom sync~.
|
||||||
|
|
||||||
** Why do I see ugly indentation highlights for tabs?
|
** Why do I see ugly indentation highlights for tabs?
|
||||||
[[https://github.com/hlissner/doom-emacs/blob/develop/core/core-ui.el#L132-L150][Doom highlights non-standard indentation]]. i.e. Indentation that doesn't match
|
[[https://github.com/hlissner/doom-emacs/blob/develop/core/core-ui.el#L132-L150][Doom highlights non-standard indentation]]. i.e. Indentation that doesn't match
|
||||||
the indent style you've set for that file. Doom uses space indentation for most
|
the indent style you've set for that file. Spaces are Doom's default style for
|
||||||
languages by default (excluding languages where tabs are the norm, like Go).
|
most languages (excluding languages where tabs are the norm, like Go).
|
||||||
|
|
||||||
There are a couple ways to address this:
|
There are a couple ways to address this:
|
||||||
|
|
||||||
1. Change ~indent-tabs-mode~ (nil = spaces, t = tabs).
|
1. Fix your indentation! If it's highlighted, you have tabs when you should have
|
||||||
|
spaces (or spaces when you should be using tabs).
|
||||||
|
|
||||||
|
Two easy commands for that:
|
||||||
|
|
||||||
|
- =M-x tabify=
|
||||||
|
- =M-x untabify=
|
||||||
|
|
||||||
|
2. Change ~indent-tabs-mode~ (nil = spaces, t = tabs) in =~/.doom.d/config.el=:
|
||||||
|
|
||||||
#+BEGIN_SRC elisp
|
#+BEGIN_SRC elisp
|
||||||
;; use tab indentation everywhere
|
;; use tab indentation everywhere
|
||||||
|
@ -1173,13 +1078,13 @@ There are a couple ways to address this:
|
||||||
(setq-hook! '(c-mode-hook c++-mode-hook) indent-tabs-mode t) ; C/C++
|
(setq-hook! '(c-mode-hook c++-mode-hook) indent-tabs-mode t) ; C/C++
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
2. Use [[https://editorconfig.org/][editorconfig]] to configure code style on a per-project basis. If you
|
3. Use [[https://editorconfig.org/][editorconfig]] to configure code style on a per-project basis. If you
|
||||||
enable Doom's =:tools editorconfig= module, Doom will recognize
|
enable Doom's =:tools editorconfig= module, Doom will recognize
|
||||||
=.editorconfigrc= files.
|
=.editorconfigrc= files.
|
||||||
|
|
||||||
3. Or trust in dtrt-indent; a plugin Doom uses to analyze and detect
|
4. Or trust in dtrt-indent; a plugin Doom uses to analyze and detect indentation
|
||||||
indentation when you open a file (that isn't in a project with an
|
when you open a file (that isn't in a project with an editorconfig file).
|
||||||
editorconfig file). This isn't foolproof, and won't work for files that have
|
This isn't foolproof, and won't work for files that have no content in them,
|
||||||
no content in them, but it can help in one-off scenarios.
|
but it can help in one-off scenarios.
|
||||||
|
|
||||||
* TODO Contributing
|
* TODO Contributing
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue