Canonical links to this document
- HTML version
- const.no/init (courtesy of
ox-hugo
andhugo
) - Git repo
- github.com/staticaland/doom-emacs-config
About Doom
I switched to Doom from my bespoke config. My main takeaways:
- Doom is very well organized and is easy to reason about (easier than Spacemacs)
- Doom hides away unneeded complexity for the most common use-cases
- I prefer Doom over my previous handcrafted config
Someone with a lot more experience than me put it well:
After 30+ years of using Emacs, I’m more than happy to use a distro with a well curated set of built-in packages, and a clear set of config mechanisms. Also the startup time! I was happy with my handcrafted config when I got <10s, with Doom I get ~1.5s and a lot more functionality ready to go. Amazing.
When you own a car, you get a sense of freedom. Even though you theoretically could travel everywhere without one, you don’t. Why? To me, Emacs is like a car while something like VSCode is like a bus. It limits you on a subconscious level that is hard to even notice.
Some people really enjoy riding a good car and is motivated by the thrill. Seeing the task at hand getting done extremely fast is inherently motivating. Doom Emacs happens to be the best car I know. Especially when you ride hours every day. It’s like using Iron Man’s powered exoskeleton.
I sense there’s two types of developers. Some focus on creating products. The others love seeing machines do the work that humans shouldn’t have to do.
I do not buy excuses for using poor tools. The only thing it reveals to me is an inability to sense limitations. I automate to save mental energy, not time. Luke Smith also talks about these concepts in «Are You Just TOO SMART to Learn Anything?». To quote John D. Cook:
I suspect the time I’ve put into learning some features of Emacs, for example, will not pay for itself in terms of time invested versus time saved. But I’ve invested leisure time to save time when I’m working hard, not to save keystrokes but to save mental energy for the project at hand.
Besides, you have The Magical Number Seven, Plus or Minus Two:
The number of objects an average human can hold in short-term memory is 7 ± 2
I optimize to keep this in check.
Studies on highly successful people have proven again and again that success is not the result of strong willpower and the ability to overcome resistance, but rather the result of smart working environments that avoid resistance in the first place.
Instead of struggling with adverse dynamics, highly productive people deflect resistance, very much like judo champions. This is not just about having the right mindset, it is also about having the right workflow. It is the way Luhmann and his slip-box worked together that allowed him to move freely and flexibly between different tasks and levels of thinking. It is about having the right tools and knowing how to use them – and very few understand that you need both.
Anyway, back to Doom. Doom’s configuration framework is well thought out!
Doom uses straight.el
and general.el
under the hood. They are actually
hiding behind package!
and map!
. One improvement over my config is that
straight.el
is used outside of Emacs itself - with the doom install
CLI
command. I like this a lot - especially since I would never bother to do that
myself.
It seems easier to pin packages with Doom. I just use the package!
with the
:pin
property. I was never a big fan of how straight-freeze-versions
and
straight-thaw-versions
worked or how pinning packages is described in the
straight.el
README. I assume Doom does something clever to keep everything
under control. To bump a package pin, run doom/bump-package-at-point
while the
cursor is on a package!
expression. Again, something I’d never implement
myself.
As always, use-package
is used for configuring packages. Doom has its own
version (use-package!
) that will avoid loading disabled packages. However, I
am not really able to use the :general
property as before so I opted to
combine :init
with map!
. Although one might argue that :map
should not be
a part of the use-package!
declaration if you consider +evil-bindings.el.
Perhaps you should rather create your own config
module. The downside to that
is that you could have keybindings to packages that may not be installed.
use-package!
is a thin wrapper arounduse-package
it is required that you use this in Doom’s modules, but not required to be used in your private config
It is important to note that omitting :defer
, :hook
, :commands
or :after
will make your package load immediately. This is bad for Doom’s startup time.
I like how !map
hides away the complexities of general.el
when you want to
use leader keys. You get sane defaults with a pleasant wrapper.
I am unsure how to best utilize the after!
function for configuring packages.
I may have to move things away from use-package!
’s :config
property to
after!
instead.
The after!
macro just expands to eval-after-load
.
It’s pretty cool to have a community around your Emacs configuration. There is a friendly and active Discord community.
Hop on our Discord server and say hi! Help others, hang out or talk to me about Emacs, gamedev, programming, physics, pixel art, anime, gaming – anything you like. Nourish this lonely soul.
I have learnt a lot from in a short time by following the discourse.
Discoveries
aya-create
with aya-expand
is very useful:
count_of_~red = get_total("~red");
aya-persist-snippet
wil save whatever is currently in aya-current
.
org-re-reveal
is set up to download the JS files locally. Great! Making
presentations are a breeze now.
#+REVEAL_EXTRA_CSS: url-to-custom-stylesheet.css
git-timemachine
(g t
) is nice. C-j
and C-k
to browse the revisions.
What is a module?
A module is something you can tell Doom to load by listing the module name in
init.el
.
A module can have a packages.el
and a config.el
, so: only use package!
in
the former and use-package!
in the latter. use-package!
is formerly known as
def-package!
(which is deprecated).
Doom consists of around 160 modules and growing. A Doom module is a bundle of packages, configuration and commands, organized into a unit that can be toggled easily by tweaking your
doom!
block (found in$DOOMDIR/init.el
).
You can think of your $DOOMDIR
kind of like a Doom module.
package!
is declarative: meant only for telling Doom what this module’s
dependencies are (and optionally, where to find them).
!map
is a convenience macro for defining keybinds, powered by general.el
.
The canonical way to reconfigure packages is with (after! PACKAGE ...)
.
Base settings
Make this file run (slightly) faster with lexical binding (see this blog post for more info).
;;; config.el -*- lexical-binding: t; -*-
Personal Information
It’s useful to have some basic personal information
(setq user-full-name "Anders K. Pettersen"
user-mail-address "john@doe.com")
Line numbers…
(setq display-line-numbers-type nil)
Modus themes
I prefer light themes when working and writing.
(setq doom-theme 'modus-operandi)
Base typeface configurations
Variable pitch means that the distance between characters can vary, as opposed to fixed pitch. The small features on the ends of strokes in some fonts are known as serifs. It is typically what is used for printed text.
(setq doom-themes-treemacs-enable-variable-pitch nil)
(when (eq system-type 'darwin)
(setq doom-font (font-spec :family "Menlo" :size 14))
(setq doom-big-font (font-spec :family "Menlo" :size 36))
(setq doom-big-font-increment 4)
(setq doom-serif-font (font-spec :family "EtBembo"))
(setq doom-variable-pitch-font (font-spec :family "EtBembo"))
(font-put doom-font :weight 'semi-light))
On Linux, I just go with the defaults for now.
(when (eq system-type 'gnu/linux)
(set-fontset-font t 'symbol "Noto Color Emoji" nil 'append))
General movements and motions
Evil
(use-package! evil
:init
(setq evil-want-Y-yank-to-eol t))
Evil-snipe
Zaiste Programming has a nice video about Evil-snipe.
(after! evil-snipe
(setq evil-snipe-scope 'visible))
Try it here with f
followed by ;
and ,
to traverse matches:
Alice took up the fan and gloves, and, as the hall was very hot, she kept fanning herself all the time she went on talking: `Dear, dear! How queer everything is to-day! And yesterday things went on just as usual. I wonder if I’ve been changed in the night? Let me think: was I the same when I got up this morning? I almost think I can remember feeling a little different. But if I’m not the same, the next question is, Who in the world am I? Ah, that’s the great puzzle!' And she began thinking over all the children she knew that were of the same age as herself, to see if she could have been changed for any of them.
I think avy-goto-char-2
is better though (g s s
). Speaking of jumping
around, SPC s s
is a fine command for invoking Swiper. Most of the times I am
looking for a file in a project, I should probably use SPC s p
instead of
using SPC p f
.
Directory, buffer, window management
Projectile project management
I want to always create a new (or matching existing) project when switching to a project.
I also want to ignore the package repositories cloned by straight.el
. Having a
lot of projects seem to slow down Emacs.
(after! projectile
(setq +workspaces-on-switch-project-behavior t)
(setq projectile-ignored-projects '("~/" "/tmp" "~/.emacs.d/.local/straight/repos/"))
(defun projectile-ignored-project-function (filepath)
"Return t if FILEPATH is within any of `projectile-ignored-projects'"
(or (mapcar (lambda (p) (s-starts-with-p p filepath)) projectile-ignored-projects))))
Dired file explorer
(after! dired
(add-hook! 'dired-mode-hook 'dired-hide-details-mode)
(add-hook! 'dired-mode-hook 'hl-line-mode)
(if (executable-find "gls")
(progn
(setq insert-directory-program "gls")
(setq dired-listing-switches "-lFaGh1v --group-directories-first"))
(setq dired-listing-switches "-ahlF"))
(setq ls-lisp-dirs-first t)
(setq dired-listing-switches "-lat") ; sort by date (new first)
(put 'dired-find-alternate-file 'disabled nil)
(setq delete-by-moving-to-trash t)
(setq dired-dwim-target t)
(setq dired-recursive-copies (quote always))
(setq dired-recursive-deletes (quote top)))
(use-package! dired-narrow
:after dired
:config
(map! :map dired-mode-map
:n "/" 'dired-narrow-fuzzy))
(use-package! dired-open
:after dired
:config
(setq open-extensions
'(("webm" . "mpv")
("avi" . "mpv")
("mp3" . "mpv")
("mp4" . "mpv")
("m4a" . "mpv")
("mkv" . "mpv")
("ogv" . "mpv")
("pdf" . "zathura")))
(setq dired-open-extensions open-extensions))
Treemacs file tree
(use-package! treemacs
:commands treemacs
:init
(map! :leader
(:prefix ("f" . "file")
:desc "Open Treemacs" "t" #'+treemacs/toggle))
:config
(treemacs-git-mode 'extended)
(setq treemacs-is-never-other-window nil)
(add-to-list 'treemacs-pre-file-insert-predicates #'treemacs-is-file-git-ignored?))
General interface and interactions
Font locks and faces
Working with faces…
(set-face-attribute 'cursor nil :background "red")
Theme
(use-package! modus-operandi-theme
:defer
:init
(setq modus-operandi-theme-scale-headings t)
(setq modus-operandi-theme-diffs 'desaturated)
(setq modus-operandi-theme-intense-paren-match t))
Focus
Dim the font color of text in surrounding sections. I like to use this on chaotic Terraform code.
(use-package focus
:hook terraform-mode
:config
;; Modes inheriting prog-mode will focus on functions.
(add-to-list 'focus-mode-to-thing '(prog-mode . defun))
;; Modes inheriting text-mode will focus on sentences.
(add-to-list 'focus-mode-to-thing '(text-mode . sentence))
;; Terraform
(add-to-list 'focus-mode-to-thing '(terraform-mode . paragraph)))
Window splits
(setq evil-vsplit-window-right t)
(setq evil-split-window-below t)
(defadvice! prompt-for-buffer (&rest _)
:after '(evil-window-split evil-window-vsplit)
(+ivy/switch-workspace-buffer))
The Mode Line
I use the +light
Doom mode line.
Outline
I first learned about outline-minor-mode
from Emacs: outline-minor-mode and
imenu.
imenu
is a very simple package that builds index of interesting positions in
the current buffer and presents them as a menu. You pick the item and the point
moves there. There is a built-in interface and also one in sallet, helm or
counsel.
In Doom, it is bound to SPC s i
by default (counsel-imenu
).
The most common way to add items to the index is by modifying
imenu-generic-expression
which is a list of lists of the form (GROUP-NAME REGEX MATCH-GROUP)
. Then imenu searches for the REGEX
and adds the
corresponding MATCH-GROUP
and its match position to the index. This is done by
imenu-default-create-index-function
which is the default value of
imenu-create-index-function
.
https://tychoish.com/post/imenu-for-markdown-and-writing/
The form is (nil "regex" 1)
.
In Python, PEP8 style guidelines recommend two lines between functions (see
blank lines). I agree that this makes the code easier to navigate (with the
eyes). Therefore it makes little sense for outline-minor-mode
to remove this
spacing. By setting outline-blank-line
to non-nil I get at least one line of
space between the headings. I do not know how to add more space.
(use-package! outshine)
(use-package! outline
:config
(setq outline-blank-line t))
(use-package! outline-minor-faces
:after outline
:config (add-hook 'outline-minor-mode-hook
'outline-minor-faces-add-font-lock-keywords))
(use-package backline
:after outline
:config (advice-add 'outline-flag-region :after 'backline-update))
Related concepts:
- Code readability
- The Magical Number Seven, Plus or Minus Two - Wikipedia
The number of objects an average human can hold in short-term memory is 7 ± 2
Finally, pretty-outlines
for prettier ellipses. I did not get the pretty
bullets working. Some variants I have tried:
Symbol | Description |
---|---|
+ | Plus |
• | Bullet |
… | Horizontal ellipsis |
↴ | Rightwards arrow with corner downwards |
⋯ | Midline horizontal ellipsis |
▾ | Black down-pointing small triangle |
▿ | White down-pointing small triangle |
◦ | White bullet |
⤵ | Arrow pointing rightwards then curving downwards |
⤷ | Arrow pointing downwards then curving rightwards |
⤸ | Right-side arc clockwise arrow |
⬎ | Rightwards arrow with tip downwards |
| Lightning |
https://github.com/integral-dw/org-superstar-mode#where-do-i-find-utf8-bullets-to-use
(use-package! pretty-outlines
:config
(setq pretty-outlines-ellipsis " ↴")
;; (setq pretty-outlines-bullets-bullet-list '("⁖"))
:hook (outline-minor-mode . pretty-outlines-set-display-table))
Bicycle
Bicycle provides commands for cycling the visibility of outline sections and code blocks.
(use-package! bicycle
:config
(map! :map outline-minor-mode-map
:n "<tab>" #'bicycle-cycle
:n "<backtab>" #'bicycle-cycle-global))
By running M-x macrostep-expand
over the map!
call you will get this:
(general-define-key :states 'normal :keymaps
'(outline-minor-mode-map)
"<tab>"
(function bicycle-cycle)
"<backtab>"
(function bicycle-cycle-global))
Proving that the map!
macro is syntactic sugar for general.el
(see
macrostep). Also see Doom naming conventions for rationale behind the use of the
exclamation point.
Outline for Python code
Note: I disabled outline for Python for now. It caused too much trouble. Leaving the config here for posterity.
Here I set up a outline-regexp
for python-mode
.
A great tip for rx
is to place the cursor at the last parenthesis and do C-x C-e
(eval-last-sexp
) to see what regex is being produced. To get Perl
Compatible Regular Expressions you can do counsel--elisp-to-pcre
on the regex
string. Now you can explore it with regex101.com. In the same vein, pcre2el is
probably worth checking out.
(add-hook 'outline-minor-mode-hook
(defun contrib/outline-overview ()
(outline-show-all)
(outline-hide-body)))
(add-hook 'org-src-mode-hook
(defun const/show-all-outlines-in-org-src ()
(outline-show-all)))
(defun python-mode-outline-hook ()
"Fold only definitions in Python."
(setq-local outline-regexp
(rx (or
;; Definitions
(group (group (* space)) bow (or "class" "def" "async") eow)
;; Decorators
(group (group (* space)) "@"))))
(outline-minor-mode))
;; (add-hook 'python-mode-hook 'python-mode-outline-hook)
outline-mode
needs some way to know what a heading looks like. It uses
outline-regexp
for this. At this point it does not know the level of the
heading. The default behaviour is either to look at the length of the
outline-regexp
match, or an association in outline-heading-alist
. You can
override the logic by setting outline-level
to a function that returns a
integer based on your calculation of choice. You can also set
outline-heading-alist
to whatever you’d like. For example:
(setq outline-heading-alist
'(("@chapter" . 2) ("@section" . 3) ("@subsection" . 4)
("@subsubsection" . 5)
("@unnumbered" . 2) ("@unnumberedsec" . 3)
("@unnumberedsubsec" . 4) ("@unnumberedsubsubsec" . 5)
("@appendix" . 2) ("@appendixsec" . 3)...
("@appendixsubsec" . 4) ("@appendixsubsubsec" . 5) ..))
I have used this for my Cloud Custodian config below.
Since the Python regex accounts for whitespace at the beginning of the line, the level will be set accordingly.
If Outline should cover 100% of Python, it would need more work. If you define a variable after a function, the variable would be nested under the function, even though it is not a part of the function:
def something():
print("hello")
some_variable = "hi"
However, I find the benefits outweigh the drawbacks.
Outline for Terraform
Terraform is a declarative configuratiion language for cloud resources - you write down what you want and Terraform performs the correct API calls.
For terraform-mode
I have decided to use a function that always returns
level 1. If you do not do this, you may find that some blocks get nested in a
way that doesn’t make sense.
(defun terraform-mode-outline-hook ()
(setq-local outline-regexp (rx
(or "resource" "data" "provider" "module" "variable" "output")
(one-or-more (not "{"))
"{"
line-end))
(defun terraform-outline-level () 1)
(setq-local outline-level 'terraform-outline-level)
(outline-minor-mode t))
(add-hook 'terraform-mode-hook 'terraform-mode-outline-hook)
The regular expression for Terraform looks like this:
(counsel--elisp-to-pcre (rx
(or "resource" "data" "provider" "module" "variable" "output")
(one-or-more (not "{"))
"{"
line-end))
(?:data|module|output|provider|(?:resourc|variabl)e)[^{]+{$
Or:
(rxt-elisp-to-pcre (rx
(or "resource" "data" "provider" "module" "variable" "output")
(one-or-more (not "{"))
"{"
line-end))
Outline for Cloud Custodian policy files
Low effort solution that gets the job done.
(defun c7n-outline-hook ()
(setq-local outline-heading-alist '(("policies:" . 1)
("- name:" . 2)))
(setq-local outline-regexp (rx (or "policies:" "- name:")))
(outline-minor-mode))
(add-hook 'yaml-mode-hook 'c7n-outline-hook)
Outline for Terragrunt (HCL) files
Another low effort solution that gets the job done.
(defun terragrunt-outline-hook ()
(setq-local outline-regexp "^in")
(outline-minor-mode))
(add-hook 'hcl-mode-hook 'terragrunt-outline-hook)
Olivetti mode
Olivetti is a Italian manufacturer of typewriters, so I suppose the goal of
olivetti
is to capture the feeling of typing on one.
(use-package! olivetti
:init
(setq-default olivetti-body-width 0.618)
:commands olivetti-mode)
About the value chosen for olivetti-body-width
:
The first known decimal approximation of the (inverse) golden ratio was stated as “about 0.6180340” in 1597 by Michael Maestlin of the University of Tübingen in a letter to Kepler, his former student.
About the use of setq-default
:
You can set any Lisp variable with setq, but with certain variables setq won’t do what you probably want in the .emacs file. Some variables automatically become buffer-local when set with setq; what you want in .emacs is to set the default value, using setq-default.
Applications and utilities
Epub reader
(use-package! nov
:mode ("\\.epub\\'" . nov-mode)
:config
(setq nov-save-place-file (concat doom-cache-dir "nov-places")))
Occur mode
(use-package! replace
:init
(map! :map occur-mode-map
:n "e" 'occur-edit-mode)
(add-hook 'occur-hook
'(lambda ()
(switch-to-buffer-other-window "*Occur*"))))
Characters
(use-package! emacs
:config
;; Got those numbers from `string-to-char'
(defconst contrib/insert-pair-alist
'(("' Single quote" . (39 39)) ; ' '
("« Εισαγωγικά Gr quote" . (171 187)) ; « »
("\" Double quotes" . (34 34)) ; " "
("` Elisp quote" . (96 39)) ; ` '
("‘ Single apostrophe" . (8216 8217)) ; ‘ ’
("“ Double apostrophes" . (8220 8221)) ; “ ”
("( Parentheses" . (40 41)) ; ( )
("{ Curly brackets" . (123 125)) ; { }
("[ Square brackets" . (91 93)) ; [ ]
("< Angled brackets" . (60 62)) ; < >
("= Equals signs" . (61 61)) ; = =
("* Asterisks" . (42 42)) ; * *
("_ underscores" . (95 95))) ; _ _
"Alist of pairs for use with `prot/insert-pair-completion'.")
(defun contrib/insert-pair-completion (&optional arg)
"Insert pair from `contrib/insert-pair-alist'."
(interactive "P")
(let* ((data contrib/insert-pair-alist)
(chars (mapcar #'car data))
(choice (completing-read "Select character: " chars nil t))
(left (cadr (assoc choice data)))
(right (caddr (assoc choice data))))
(insert-pair arg left right))))
Gnus
(after! gnus
(setq gnus-select-method '(nntp "news.gwene.org")))
Tmux
Sometimes I want to dump the current tmux
pane into Emacs.
(use-package! emacs
:init
(map! :leader
(:prefix ("ø" . "utils")
:desc "tmux buffer" "t" #'const/tmux-capture-pane))
:config
(setq display-line-numbers-type nil)
(defun const/tmux-capture-pane()
(interactive)
(with-output-to-temp-buffer "*tmux-capture-pane*"
(shell-command "tmux capture-pane -p -S -"
"*tmux-capture-pane*"
"*Messages*")
(pop-to-buffer "*tmux-capture-pane*"))))
(map! :leader
(:prefix ("f" . "file")
:desc "tmux cd to here" "T" #'+tmux/cd-to-here))
Elfeed
I use the Doom RSS module and just set some keybinds here. I opt for SPC m r
for elfeed-update
which is a pleasing left, right, left key sequence. Yes, I
press SPC
with my left thumb.
(use-package! elfeed
:commands elfeed
:init
(map! :leader
(:prefix ("o" . "open")
:desc "Open elfeed" "e" #'=rss)))
(after! elfeed
(map! :map elfeed-search-mode-map
:localleader
:desc "Elfeed update" "r" #'elfeed-update))
(use-package! elfeed-web
:defer t
:commands elfeed-web-stop)
Keycast
(use-package! keycast
:commands keycast-mode
:config
(define-minor-mode keycast-mode
"Show current command and its key binding in the mode line."
:global t
(if keycast-mode
(progn
(add-hook 'pre-command-hook 'keycast-mode-line-update t)
(add-to-list 'global-mode-string '("" mode-line-keycast " ")))
(remove-hook 'pre-command-hook 'keycast-mode-line-update)
(setq global-mode-string (remove '("" mode-line-keycast " ") global-mode-string))))
(custom-set-faces!
'(keycast-command :inherit doom-modeline-debug
:height 0.9)
'(keycast-key :inherit custom-modified
:height 1.1
:weight bold)))
Regular expressions: re-builder
Rex the dog…
(use-package re-builder
:config
(setq reb-re-syntax 'string))
Visual regex:
(use-package visual-regexp
:commands vr/query-replace
:config
(setq vr/default-replace-preview nil)
(setq vr/match-separator-use-custom-face t))
Emoji cheat sheet
(use-package emoji-cheat-sheet-plus
:commands emoji-cheat-sheet-plus-insert)
(use-package ivy-emoji
:commands ivy-emoji)
Org-mode (personal information manager)
Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system.
I have these feature flags enabled:
+hugo
- For exporting my blog from Org to Hugo flavoured markdown
+pretty
- Mainly for pretty headings
+journal
- For daily journals saved to
~/org/journal
with one file for each day +roam
- For Zettelkasten style note taking
+present
- For making
reveal.js
presentations from Org documents (reveal-md is a fine alternative btw)
I want to use https://sandyuraz.com/articles/orgmode-css/ (see https://news.ycombinator.com/item?id=23130104)
(after! org-journal (setq org-journal-file-format "%Y%m%d.org"))
First I set my org-directory
and bind SPC f o
to open my main Org-mode file.
(setq org-directory "~/org/")
(map! :leader
(:prefix ("f" . "file")
:desc "Open init.org" "o" '(lambda () (interactive) (find-file "~/org/org.org"))))
- Use
mixed-pitch-mode
by default - Use
olivetti-mode
by default - Show at least one line break between headings
- Never indent SRC blocks
(after! org
(add-hook 'org-mode-hook (lambda () (electric-indent-local-mode -1)))
(setq org-todo-keywords
'((sequence "TODO(t!)" "TODAY(a!)" "NEXT(n!)" "STARTED(s!)" "IN-PROGRESS(p!)" "UNDERWAY(u!)" "WAITING(w@)" "SOMEDAY(o!)" "MAYBE(m!)" "|" "DONE(d@)" "CANCELED(c@)")
(sequence "CHECK(k!)" "|" "DONE(d@)")
(sequence "TO-READ(r!)" "READING(R!)" "|" "HAVE-READ(d@)")
(sequence "TO-WATCH(!)" "WATCHING(!)" "SEEN(!)")))
(setq org-imenu-depth 7)
(setq org-ellipsis " ▾ ")
(setq org-superstar-headline-bullets-list '("⁖"))
;;(add-hook! 'org-mode-hook #'mixed-pitch-mode)
;;(add-hook! 'org-mode-hook #'olivetti-mode)
(setq org-babel-python-command "python3")
(setq org-cycle-separator-lines 1)
(setq org-edit-src-content-indentation 0)
(setq org-export-initial-scope 'subtree)
(setq org-image-actual-width 400)
(setq org-src-window-setup 'current-window)
(setq org-startup-indented t))
Org-capture templates from Protesilaos Stavrou be serving me well.
A quoted list of lists:
- The key
- The description
- The type of entry (a symbol)
entry
: An Org mode node, with a headline. Will be filed as the child of the target entry or as a top-level entry. The target file should be an Org file.
- The target
- Like
file+headline
- Like
- The template
If you say file+headline
you would give it a filename and a headline to put
the template under.
(after! org-capture
(setq org-capture-templates
'(("b" "Basic task for future review" entry
(file+headline "tasks.org" "Basic tasks that need to be reviewed")
"* %^{Title}\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n\n%i%l"
:empty-lines 1)
("w" "Work")
("wt" "Task or assignment" entry
(file+headline "work.org" "Tasks and assignments")
"\n\n* TODO [#A] %^{Title} :@work:\nSCHEDULED: %^t\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n\n%i%?"
:empty-lines 1)
("wm" "Meeting, event, appointment" entry
(file+headline "work.org" "Meetings, events, and appointments")
"\n\n* MEET [#A] %^{Title} :@work:\nSCHEDULED: %^T\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n\n%i%?"
:empty-lines 1)
("t" "Task with a due date" entry
(file+headline "tasks.org" "Task list with a date")
"\n\n* %^{Scope of task||TODO|STUDY|MEET} %^{Title} %^g\nSCHEDULED: %^t\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n\n%i%?"
:empty-lines 1)
("j" "Journal" entry
(file+olp+datetree "journal.org")
"* %?\n"
:empty-lines 1)
("r" "Reply to an email" entry
(file+headline "tasks.org" "Mail correspondence")
"\n\n* TODO [#B] %:subject :mail:\nSCHEDULED: %t\n:PROPERTIES:\n:CONTEXT: %a\n:END:\n\n%i%?"
:empty-lines 1)))
(defun org-hugo-new-subtree-post-capture-template ()
(let* ((title (read-from-minibuffer "Post Title: "))
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(
,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_FILE_NAME: " fname)
":END:"
"%?\n")
"\n")))
(add-to-list 'org-capture-templates
'("h" "Hugo blog post" entry
(file "~/Projects/org-blog/blog.org")
(function org-hugo-new-subtree-post-capture-template)
:empty-lines 1)))
(use-package org-web-tools
:commands org-web-tools-insert-link-for-url)
The org-roam capture template:
(after! org-roam
(defun +org-notes-project-p ()
"Return non-nil if current buffer has any todo entry.
TODO entries marked as done are ignored, meaning the this
function returns nil if current buffer contains only completed
tasks."
(seq-find ; (3)
(lambda (type)
(eq type 'todo))
(org-element-map ; (2)
(org-element-parse-buffer 'headline) ; (1)
'headline
(lambda (h)
(org-element-property :todo-type h)))))
(defun +org-notes-project-update-tag ()
"Update PROJECT tag in the current buffer."
(when (and (not (active-minibuffer-window))
(+org-notes-buffer-p))
(let* ((file (buffer-file-name (buffer-base-buffer)))
(all-tags (org-roam--extract-tags file))
(prop-tags (org-roam--extract-tags-prop file))
(tags prop-tags))
(if (+org-notes-project-p)
(setq tags (cons "project" tags))
(setq tags (remove "project" tags)))
(unless (eq prop-tags tags)
(org-roam--set-global-prop
"ROAM_TAGS"
(combine-and-quote-strings (seq-uniq tags)))))))
(defun +org-notes-buffer-p ()
"Return non-nil if the currently visited buffer is a note."
(and buffer-file-name
(string-prefix-p
(expand-file-name (file-name-as-directory org-roam-directory))
(file-name-directory buffer-file-name))))
(defun +org-notes-project-files ()
"Return a list of note files containing Project tag."
(seq-map
#'car
(org-roam-db-query
[:select file
:from tags
:where (like tags (quote "%\"project\"%"))])))
(defun +agenda-files-update (&rest _)
"Update the value of `org-agenda-files'."
(setq org-agenda-files (+org-notes-project-files)))
(add-hook 'find-file-hook #'+org-notes-project-update-tag)
(add-hook 'before-save-hook #'+org-notes-project-update-tag)
(advice-add 'org-agenda :before #'+agenda-files-update)
(push 'company-capf company-backends)
(setq org-roam-graph-viewer "/Applications/Firefox.app/Contents/MacOS/firefox")
(setq org-roam-completion-everywhere t)
(setq org-roam-capture-templates
'(("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "${slug}"
:head "#+title: ${title}\n"
:unnarrowed t))))
(use-package! org-transclusion)
(use-package! org-drill
:after org)
(use-package! company
:config
(setq company-idle-delay 0.2)
(setq company-minimum-prefix-length 3))
Selectrum
(use-package! ivy-prescient
:config
;; to make sorting and filtering more intelligent
(ivy-prescient-mode +1)
(prescient-persist-mode +1))
(use-package! marginalia
:config
(setq marginalia-annotators
'(marginalia-annotators-heavy
marginalia-annotators-light))
(marginalia-mode 1))
Languages
Flycheck
(after! flycheck
(add-to-list 'flycheck-checkers 'terraform-tflint)
(add-to-list 'flycheck-checkers 'terraform))
The list already contain these items, so the above is useless. I just want to remember that they exist.
Terraform
Snippets.
I want some snippets:
# -*- mode: terraform-mode -*-
# name: sb1-module
# --
source="${1:$$(yas-choose-value (directory-files "/Users/anders/projects"))}"
Anyway…
(after! yasnippet
(add-to-list 'yas-snippet-dirs
(concat
(file-name-as-directory straight-base-dir)
(file-name-as-directory "straight")
(file-name-as-directory "build")
(file-name-as-directory "yasnippet-terraform/terraform-mode"))))
Markdown
:mode
is an implicit defer in any case.
(use-package! markdown-mode
:mode (("README\\.md\\'" . gfm-mode)
("\\.md$" . markdown-mode)
("\\.pmd$" . markdown-mode)
("\\.cbmd$" . markdown-mode)
("\\.markdown\\'" . markdown-mode)))
Python 🐍
I don’t understand why setting the right virtual environment is not a common use
case for people using lsp-mode
. Anyway, I use poetry
and enable
poetry-tracking-mode
which will set the correct environment right before
starting lsp-mode
. The function goes at the front of the hook list.
I also add some Poetry commands to the local leader of python-mode
. It’s nice
to be in control of the LSP beast.
There’s some other ways to set the virtual environment:
poetry-venv-toggle
pyvenv-activate
You must run lsp-workspace-restart
for changes to take effect.
(add-hook! 'python-mode-hook 'poetry-tracking-mode)
(after! poetry
(setq poetry-tracking-strategy 'projectile)
(map! :map python-mode-map
:localleader
:desc "Activate Poetry tracking mode" "c" #'poetry-tracking-mode
:desc "Restart LSP workspace" "r" #'lsp-workspace-restart
:desc "Workon/off the Poetry venv" "w" #'poetry-venv-toggle
:desc "Poetry menu" "p" #'poetry))
Actually, the logic for choosing a virtual environment depends on which language
server you use. Consider lsp-pyright-locate-venv
in lsp-pyright.el. Now what
is the point of that?
The Spacemacs Python layer seems very nice - take a look at the Spacemacs Python layer and Python Development in Spacemacs - YouTube.
Keybindings
In this section I will plan out my keybinding strategy. The current one looks like this:
(after! elfeed
(map! :map elfeed-search-mode-map
:localleader
:desc "Elfeed update" "r" #'elfeed-update))
Mastering Key Bindings in Emacs - Mastering Emacs
general-override-mode-map
is a minor mode map that will take precedence over
all maps. The alias is :override
.
https://github.com/noctuid/general.el#override-keymaps-and-buffer-local-keybindings
https://github.com/hlissner/doom-emacs/issues?q=override+label%3Are%3Akeybinds
Hi! Thanks for the PR but I cannot accept it. It is redundant with hideshow which the :editor fold module configures, and will handle folding lisp blocks, while outline-minor-mode is relegated to handling comment headings, and vimish-fold for arbitrary folds.