diff --git a/docs/getting_started.org b/docs/getting_started.org index 0cba462e1..a6bf856a1 100644 --- a/docs/getting_started.org +++ b/docs/getting_started.org @@ -59,18 +59,21 @@ us know! - [[#reloading-your-config][Reloading your config]] - [[#binding-keys][Binding keys]] - [[#writing-your-own-modules][Writing your own modules]] - - [[#load-order][Load order]] - - [[#location][Location]] - [[#file-structure][File structure]] - [[#initel][=init.el=]] - [[#configel][=config.el=]] - [[#packagesel][=packages.el=]] - [[#autoloadel-or-autoloadel][=autoload/*.el= OR =autoload.el=]] - [[#doctorel][=doctor.el=]] + - [[#cliel][=cli.el=]] + - [[#testtest-el][=test/**/test-*.el=]] - [[#additional-files][Additional files]] + - [[#load-order][Load order]] - [[#flags][Flags]] - - [[#module-cookies][Module cookies]] - - [[#autodefs][Autodefs]] + - [[#doom-cookies][Doom cookies]] + - [[#if][~;;;###if~]] + - [[#package][~;;;###package~]] + - [[#autodef][~;;;###autodef~]] - [[#common-mistakes-when-configuring-doom-emacs][Common mistakes when configuring Doom Emacs]] - [[#packages-are-eagerly-loaded][Packages are eagerly loaded]] - [[#manual-package-management][Manual package management]] @@ -1047,111 +1050,142 @@ also be helpful for debugging. + define-key! ** Writing your own modules -*** Load order -Module files are loaded in a precise order: - -1. =~/.emacs.d/early-init.el= (Emacs 27+ only) -2. =~/.emacs.d/init.el= -3. =$DOOMDIR/init.el= -4. ={~/.emacs.d,$DOOMDIR}/modules/*/*/init.el= -5. ={~/.emacs.d,$DOOMDIR}/modules/*/*/config.el= -6. =$DOOMDIR/config.el= - -*** Location -Doom searches for modules in =~/.emacs.d/modules/CATEGORY/MODULE/= and -=$DOOMDIR/modules/CATEGORY/MODULE/=. If you have a private module with the same -name as an included Doom module, yours will shadow the included one (as if the -included one never existed). +To create your own module you need only create a directory for it in +=~/.doom.d/modules/abc/xyz=, then add =:abc xyz= to your ~doom!~ block in +=~/.doom.d/init.el= to enable it. #+begin_quote -Doom refers to modules in one of two formats: ~:category module~ or -~category/module~. +In this example, =:abc= is called the category and =xyz= is the name of the +module. Doom refers to modules in one of two formats: =:abc xyz= and =abc/xyz=. #+end_quote +If a private module possesses the same name as a built-in Doom module (say, +=:lang org=), it replaces the built-in module. Use this fact to rewrite modules +you don't agree with. + +Of course, an empty module isn't terribly useful, but it goes to show that nothing in a module is required. The typical module will have: + ++ A =packages.el= to declare all the packages it will install, ++ A =config.el= to configure and load those packages, ++ And, sometimes, an =autoload.el= to store that module's functions, to be + loaded when they are used. + +These are a few exceptional examples of a well-rounded module: + ++ [[file:/mnt/projects/conf/doom-emacs/modules/completion/company/README.org][:completion company]] + +The remainder of this guide will go over the technical details of a Doom module. + *** File structure -A module consists of several files, all of which are optional. They are: +Doom recognizes a handful of special file names, none of which are required for +a module to function. They are: #+begin_example -modules/ - category/ - module/ - test/*.el - autoload/*.el - autoload.el - init.el - config.el - packages.el - doctor.el +category/ + module/ + test/*.el + autoload/*.el + autoload.el + init.el + cli.el + config.el + packages.el + doctor.el #+end_example **** =init.el= This file is loaded early, before anything else, but after Doom core is loaded. +It is loaded in both interactive and non-interactive sessions (it's the only +file, besides =cli.el= that is loaded when the =bin/doom= starts up). -Use this file to: - +Do: + Configure Emacs or perform setup/teardown operations that must be set early; before other modules are (or this module is) loaded. + Reconfigure packages defined in Doom modules with ~use-package-hook!~ (as a last resort, when ~after!~ and hooks aren't enough). -+ To change the behavior of ~bin/doom~. ++ Configure behavior of =bin/doom= in a way that must also apply in + interactive sessions. -Do *not* use this file to: - -+ Configure packages with ~use-package!~ or ~after!~ +Don't: ++ Configure packages with ~use-package!~ or ~after!~ from here + Preform expensive or error-prone operations; these files are evaluated - whenever ~bin/doom~ is used. + whenever =bin/doom= is used; a fatal error in this file can make Doom + unbootable (but not irreversibly). ++ Define new =bin/doom= commands here. That's what =cli.el= is for. **** =config.el= -This file is the heart of every module. +The heart of every module. Code in this file should expect dependencies (in +=packages.el=) to be installed and available. Use it to load and configure its +packages. -Code in this file should expect that dependencies (in =packages.el=) are -installed and available, but shouldn't make assumptions about what /modules/ are -activated (use ~featurep!~ to detect them). +Do: ++ Use ~after!~ or ~use-package!~ to configure packages. + #+BEGIN_SRC emacs-lisp + ;; from modules/completion/company/config.el + (use-package! company ; `use-package!' is a thin wrapper around `use-package' + ; it is required that you use this in Doom's modules, + ; but not required to be used in your private config. + :commands (company-mode global-company-mode company-complete + company-complete-common company-manual-begin company-grab-line) + :config + (setq company-idle-delay nil + company-tooltip-limit 10 + company-dabbrev-downcase nil + company-dabbrev-ignore-case nil) + [...]) + #+END_SRC ++ Lazy load packages with ~use-package~'s ~:defer~ property. ++ Use the ~featurep!~ macro to make some configuration conditional based on the + state of another module or the presence of a flag. -Packages should be configured using ~after!~ or ~use-package!~: - -#+BEGIN_SRC emacs-lisp -;; from modules/completion/company/config.el -(use-package! company - :commands (company-mode global-company-mode company-complete - company-complete-common company-manual-begin company-grab-line) - :config - (setq company-idle-delay nil - company-tooltip-limit 10 - company-dabbrev-downcase nil - company-dabbrev-ignore-case nil) - [...]) -#+END_SRC - -#+begin_quote -For anyone already familiar with ~use-package~, ~use-package!~ is merely a thin -wrapper around it. It supports all the same keywords and can be used in much the -same way. -#+end_quote +Don't: ++ Use ~package!~ ++ Install packages with =package.el= or ~use-package~'s ~:ensure~ property. Doom + has its own package manager. That's what =packages.el= is for. **** =packages.el= This file is where package declarations belong. It's also a good place to look if you want to see what packages a module manages (and where they are installed from). -A =packages.el= file shouldn't contain complex logic. Mostly conditional -statements and ~package!~, ~disable-packages!~ or ~depend-on!~ calls. It -shouldn't produce side effects and should be deterministic. Because this file -gets evaluated in an environment isolated from your interactive session, code -within should make no assumptions about the current session. +Do: ++ Declare packages with the ~package!~ macro ++ Disable single packages with ~package!~'s ~:disable~ property or multiple + packages with the ~disable-packages!~ macro. ++ Use the ~featurep!~ macro to make packages conditional based on the state of + another module or the presence of a flag. -See the "[[#package-management][Package Management]]" section for details. +Don't: ++ Configure packages here (definitely no ~use-package!~ or ~after!~ in here!). + This file is read in an isolated environment and will have no lasting effect. + The only exception is configuration targeting =straight.el=. ++ Perform expensive calculations. These files are read often and sometimes + multiple times. ++ Produce any side-effects, for the same reason. + +#+begin_quote +The "[[#package-management][Package Management]]" section goes over the ~package!~ macro and how to deal +with packages. +#+end_quote **** =autoload/*.el= OR =autoload.el= -Functions marked with an autoload cookie (~;;;###autoload~) in these files will -be lazy loaded. +These files are where you'll store functions that shouldn't be loaded until +they're needed and logic that should be autoloaded (evaluated very, very early +at startup). -When you run ~bin/doom autoloads~, Doom scans these files to populate autoload file -in =~/.emacs.d/.local/autoloads.el=, which will tell Emacs where to find these -functions when they are called. +This is all made possible thanks to these autoload cookie: ~;;;###autoload~. +Placing this on top of a lisp form will do one of two things: + +1. Add a ~autoload~ call to Doom's autoload file (found in + =~/.emacs.d/.local/autoloads.el=, which is read very early in the startup + process). +2. Or copy that lisp form to Doom's autoload file verbatim (usually the case for + anything other then ~def*~ forms, like ~defun~ or ~defmacro~). + +Doom's autoload file is generated by scanning these files when you execute ~doom +sync~. For example: - #+BEGIN_SRC emacs-lisp ;; from modules/lang/org/autoload/org.el ;;;###autoload @@ -1167,14 +1201,19 @@ For example: #+END_SRC **** =doctor.el= -This file is used by ~make doctor~, and should test for all that module's -dependencies. If it is missing one, it should use the ~warn!~, ~error!~ and -~explain!~ macros to inform the user why it's a problem and, ideally, a way to -fix it. +When you execute ~doom doctor~, this file defines a series of tests for the +module. These should perform sanity checks on the environment, such as: + ++ Check if the module's dependencies are satisfied, ++ Warn if any of the enabled flags are incompatible, ++ Check if the system has any issues that may interfere with the operation of + this module. + +Use the ~warn!~, ~error!~ and ~explain!~ macros to communicate issues to the +user and, ideally, explain how to fix them. For example, the ~:lang cc~ module's doctor checks to see if the irony server is installed: - #+BEGIN_SRC emacs-lisp ;; from lang/cc/doctor.el (require 'irony) @@ -1182,32 +1221,69 @@ installed: (warn! "Irony server isn't installed. Run M-x irony-install-server")) #+END_SRC +**** TODO =cli.el= +This file is read when =bin/doom= starts up. Use it to define your own CLI +commands or reconfigure existing ones. + +**** TODO =test/**/test-*.el= +Doom's unit tests go here. More information on them to come... + **** Additional files -Sometimes, it is preferable that a module's config.el file be split up into -multiple files. The convention is to name these additional files with a leading -=+=, e.g. =modules/feature/version-control/+git.el=. +Any files beyond the ones I have already named are not given special treatment. +They must be loaded manually to be loaded at all. In this way modules can be +organized in any way you wish. Still, there is one convention that has emerged +in Doom's community that you may choose to adopt: extra files in the root of the +module are prefixed with a plus, e.g. =+extra.el=. There is no syntactical or +functional significance to this convention. -There is no syntactical or functional significance to this convention. -Directories do not have to follow this convention, nor do files within those -directories. - -These additional files are *not* loaded automatically. You will need to use the -~load!~ macro to do so: +These can be loaded with the ~load!~ macro, which will load an elisp file +relative to the file it's used from. e.g. #+BEGIN_SRC emacs-lisp -;; from modules/feature/version-control/config.el -(load! +git) +;; Omitting the file extension allows Emacs to load the byte-compiled version, +;; if it is available: +(load! "+git") ; loads ./+git.el #+END_SRC -The ~load!~ macro will try to load a =+git.el= relative to the current file. +This can be useful for splitting up your configuration into multiple files, +saving you the hassle of creating multiple modules. + +*** Load order +A module's files have a precise load-order, which differs slightly depending on +what kind of session it is. Doom has three types of sessions: + ++ Interactive session :: the typical session you open when you intend to use + Emacs (e.g. for text editing). This loads the most, because you will likely be + using a lot of it. ++ Batch session :: this is a non-interactive session, loaded when you execute + Emacs commands on the command line with no UI, e.g. ~emacs --batch --eval + '(message "Hello world")'~. + + The expectation for these sessions is that it should quickly spin up, run the + command then quit, therefore very little is loaded in this session. ++ CLI session :: this is the same as a batch session /except/ it is what starts + up when you run any =bin/doom= command. + +With that out of the way, here is the load order of Doom's most important files: + +| File | Interactive | Batch | CLI | +|---------------------------------------------+-------------+-------+-----| +| ~/.emacs.d/early-init.el (Emacs 27+ only) | yes | no | no | +| ~/.emacs.d/init.el | yes | no | no | +| $DOOMDIR/init.el | yes | yes | yes | +| {~/.emacs.d,$DOOMDIR}/modules/*/*/init.el | yes | yes | yes | +| $DOOMDIR/cli.el | no | no | yes | +| {~/.emacs.d,$DOOMDIR}/modules/*/*/cli.el | no | no | yes | +| {~/.emacs.d,$DOOMDIR}/modules/*/*/config.el | yes | no | no | +| $DOOMDIR/config.el | yes | no | no | *** Flags -A module flag is an arbitrary symbol. By convention, these symbols are prefixed -with a ~+~ or a ~-~, to respectively denote the addition or removal of a -feature. There is no functional significance to this notation. +A module's flag is an arbitrary symbol. By convention, these symbols are +prefixed with a ~+~ or a ~-~ to denote the addition or removal of a feature, +respectively. There is no functional significance to this notation. -A module may choose to interpret flags however it likes. They can be tested for -with the ~featurep!~ macro: +A module may choose to interpret flags however it wishes, and can be tested for +using the ~featurep!~ macro: #+BEGIN_SRC elisp ;; Has the current module been enabled with the +my-feature flag? @@ -1217,39 +1293,72 @@ with the ~featurep!~ macro: (when (featurep! :lang python +lsp) ...) #+END_SRC -*** Module cookies -A special syntax exists called module cookies. Like autoload cookies -(~;;;###autoload~), module files may have ~;;;###if FORM~ at or near the top of -the file. FORM is read determine whether or not to ignore this file when -scanning it for autoloads (~doom sync~) or byte-compiling it (~doom compile~). +Use this fact to make aspects of a module conditional. e.g. Prevent company +plugins from loading if the =:completion company= module isn't enabled. -Use this to prevent errors that may occur if that file contains (for example) -calls to functions that won't exist if a certain feature isn't available to that -module, e.g. +*** Doom cookies +Autoload cookies were mentioned [[*=autoload/*.el= OR =autoload.el=][earlier]]. A couple more exist that are specific +to Doom Emacs. This section will go over what they do and how to use them. +*** ~;;;###if~ +Any file in a module can have a ~;;;###if FORM~ cookie at or near the top of the +file (must be within the first 256 bytes of the file). =FORM= is evaluated to +determine whether or not to include this file for autoloads scanning (on ~doom +sync~) or byte-compilation (on ~doom compile~). + +i.e. if =FORM= returns ~nil~, Doom will neither index its ~;;;###autoload~ +cookies nor byte-compile the file. + +Use this to prevent errors that would occur if certain conditions aren't met. +For example, say =file.el= is using a certain function that won't be available +if the containing module wasn't enabled with a particular flag. We could safe +guard against this with: #+BEGIN_SRC emacs-lisp -;;;###if (featurep! +lsp) +;;;###if (featurep! +particular-flag) #+END_SRC +This will prevent errors at compile time or if/when that file is loaded. + +Another example, this time contingent on =so-long= *not* being present: #+BEGIN_SRC emacs-lisp ;;;###if (not (locate-library "so-long")) #+END_SRC -Remember that these run in a limited, non-interactive sub-session, so do not -call anything that wouldn't be available in a Doom session without any modules -enabled. +#+begin_quote +Keep in mind that =FORM= runs in a limited, non-interactive sub-session. I don't +recommend doing anything expensive or especially complicated in them. +#+end_quote -*** Autodefs -An autodef is a special kind of autoloaded function or macro which Doom -guarantees will always be defined, whether or not its containing module is -enabled (but will no-op without evaluating its arguments when it is disabled). +*** ~;;;###package~ +This cookie exists solely to assist the ~doom/help-packages~ command. This +command shows you documentation about packages in the Emacs ecosystem, including +the ones that are installed. It also lists a) all the modules that install said +package and b) all the places it is configured. + +It accomplishes A by scanning for at ~package!~ declarations for that package, +but it accomplishes B by scanning for: + ++ ~after!~ calls ++ ~use-package!~ or ~use-package~ calls ++ and ~;;;###package X~ cookies, where X is the name of the package + +Use it to let ~doom/help-packages~ know where to find config for packages where +no ~after!~ or ~use-package!~ call is involved. + +*** ~;;;###autodef~ +An autodef is a special kind of autoloaded function (or macro) which Doom +guarantees will /always/ be defined, whether or not its containing module is +enabled (but will no-op if it is disabled). + +#+begin_quote +If the containing module is disabled the definition is replaced with a macro +that does not process its arguments, so it is a zero-cost abstraction. +#+end_quote You can browse the available autodefs in your current session with ~M-x doom/help-autodefs~ (=SPC h d u= or =C-h d u=). -What distinguishes an autodef from a regular autoload is the ~;;;###autodef~ -cookie: - +An autodef cookie is used in exactly the same way as the autoload cookie: #+BEGIN_SRC elisp ;;;###autodef (defun set-something! (value) @@ -1259,11 +1368,13 @@ cookie: An example would be the ~set-company-backend!~ function that the =:completion company= module exposes. It lets you register company completion backends with certain major modes. For instance: - #+BEGIN_SRC emacs-lisp (set-company-backend! 'python-mode '(company-anaconda)) #+END_SRC +And if =:completion company= is disabled, this call and its arguments are left +unprocessed and ignored. + ** Common mistakes when configuring Doom Emacs Having helped many users configure Doom, I've spotted a few recurring oversights that I will list here, in the hopes that it will help you avoid the same