The first thing to do, after following the doom installation instructions, is to start configuring. Since I had seen others do literate configs, I knew that an org file could output an elisp file, and that could be used as the config. This is also called “tangling”. This is a great way to maintain code. It looks nicer than comments, and can be exported to html for others to peruse online:). I also think it will benefit your future self to see the thinking or explaination of why a particular configuration was made, and having the file in this format really helps to enforce that behavior. There are a few steps to take before even starting to configure emacs in this way, however.
- uncomment the line that contains “literal” in `~/.doom.d/init.el`.
- move config.el to config.org
- prepend the file with two lines: `#+TITLE: Doom Config`, then `#+BEGIN_SRC emacs-lisp`
- append `#+END_SRC` to the bottom of the file
Now you have a literate file; the source block will be exported to `config.el` and loaded. Next, Doom needs to restart. (`doom/reload` might work here)
- exit emacs `SPC q q`
- run `~/.doom.d/bin/doom sync`
- start emacs and continue configuring (ad inifitum)
Keep adding source blocks with descriptive text above it like this one:) To evaluate the source block, i.e.: test it, type `C-c C-c` with the cursor inside it.
And you’re off! Enjoy life as an emacs hacker with a productive boost from Doom.
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;; Place your private configuration here! Remember, you do not need to run 'doom
;; sync' after modifying this file!
;; Some functionality uses this to identify you, e.g. GPG configuration, email
;; clients, file templates and snippets.
;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
;; are the three important ones:
;;
;; + `doom-font'
;; + ` doom-variable-pitch-font '
;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for
;; presentations or streaming.
;;
;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
;; font string. You generally only need these two:
(setq doom-font (font-spec :family "monospace" :name "Inconsolata"
:size 19 :weight 'normal :width 'normal))
;; There are two ways to load a theme. Both assume the theme is installed and
;; available. You can either set `doom-theme' or manually load a theme with the
;; `load-theme' function. This is the default:
(setq doom-theme 'doom-one)
;; If you use `org' and don't want your org files in the default location below,
;; change `org-directory'. It must be set before org loads!
(setq org-directory "~/org/")
;; This determines the style of line numbers in effect. If set to `nil', line
;; numbers are disabled. For relative line numbers, set this to `relative'.
(setq display-line-numbers-type t)
;; Here are some additional functions/macros that could help you configure Doom:
;;
;; - `load!' for loading external *.el files relative to this one
;; - `use-package!' for configuring packages
;; - `after!' for running code after a package has loaded
;; - `add-load-path!' for adding directories to the `load-path', relative to
;; this file. Emacs searches the `load-path' when you load packages with
;; `require' or `use-package'.
;; - `map!' for binding new keys
;;
;; To get information about any of these functions/macros, move the cursor over
;; the highlighted symbol at press 'K' (non-evil users must press 'C-c c k').
;; This will open documentation for it, including demos of how they are used.
;;
;; You can also try 'gd' (or 'C-c c d') to jump to their definition and see how
;; they are implemented.
This was copied from Doom docs. I used `SPC h d f`(doom-help-faq), then typed “maximize”, and it brought me straight to some code I could copy. Neat!
(add-to-list 'initial-frame-alist '(fullscreen . maximized))
These are shortcut ideas combined with things that I got used to from Spacemacs. Let’s consider the general operations a person does with a cursor before organizing key mappings.
hjkl
With the combination of the Colemak layout, and evil, I’ve got a sort of joystick orientation when moving a single charactor or line. `j`(down) is `r0u`(right index finger up) and `k`(up) is `r0d`(right index finger down). And then `h`(left) and `l`(right) are between those, albet kind of crooked, at `r0` and `r1u`, respectivly.
moving further
The most fun way to move around, that I’ve seen, is using avy. In this technique, you simply look at where you want to go, activate avy, type the first two letters that start the word you are looking at, then type the letters that avy overlays onto the letters you are looking at. That is a lot of words for something that happens very fast. I’ve overwriten `t`, on which Doom had placed `evil-snipe` which operates similarly, but it is initially configured to scope to the current line, which is much too small of a scope for the precious position of `t`, which is at `l1` on Colemak, as well as the precious operation of navigating text. Another consideration here is that `evil-snipe` can be configured to scope to all the visual text, and it could be a way to get to text faster in some cases.
(setq evil-snipe-scope 'whole-visible)
(setq avy-keys '(?a ?r ?s ?t ?o ?i ?e ?n))
(map! :nv "t" #'evil-avy-goto-char-2)
moving even further
`n` is the other most precious position at `r1`. Doom has `evil-ex-search-next` and I’ve never liked having to choose between forward and backward when searching within a file - just show me the whole file, you know?. Swiper will accomodate that nicely, so we can use `n` for something common. The next most common action I can think of is scrolling. So, both scroll up and scroll down(aka page up/page down) are set to `n` at the coveting `r1` position.
(map!
:n "n" #'evil-scroll-down
:n "N" #'evil-scroll-up
)
Go-to
These functions help you get to where you want to go. There is a search aspect to them, but mostly, they help you if you know where you want to go, and there is only one thing to jump to. I’d say they can be considered navigation commands.
M-g g goto-line SPC s r counsel-mark-ring jump back to previous(marked) positions SPC s i counsel-imenu - things defined in file SPC p f find-file-in-project SPC p p switch-to-project
Go-to line `M-g g` is set to `goto-line`(by number), which might be an emacs default. That is good enough, and I will use it, but it is slightly easier to use avy to go straight to the position, which includes the line. This is the only function for jumping around that I’m assigning, and this is partly to remove what appears to be a broken `evil-goto-line` which Doom puts on `G`. Because of the duplication this is a great candidate for something else, but I don’t have anything else at the moment, so I’ll try this out.
(map! :n "G" #'avy-goto-line)
I thought I wanted fuzzy matching with `(ivy +fuzzy)` in init.el but then I learned you can put a space as a glob which accomplishes a similar effect.
These functions help you find things.
s-f (super-f) which is Commnad key on mac runs swiper, awesome interactive search within file, “find in file” SPC s b counsel-grep-or-swiper a little slow SPC s S swiper-isearch-thing-at-point SPC s p +ivy/project-search use vio, then SPC s p for search project for thing-at-point
I didn’t really have to add anything here(yet!)(except for search and replace below).
Replace in buffer
M-% query-replace - a big player, but be sure to navigate to the top of the buffer first
There does not seem to be a `projectile-rename` like thing in which all occurances of a symbol in a project get renamed. But you can do it with two commands - there is a supported workflow that we can use. It is complicated, but the power is worth it, I think.
Project-wide rename workflow
- `SPC s R` counsel-git-grep *Don’t hit enter yet! (or SPC s p)
- `C-c C-e` turn on wgrep(for edits)
- `M-%` search and replace interactivly
- `y` to replace or `n` to skip
- `C-c C-c` to write changes to files `C-c C-k` to abort
Search the project for any string
`counsel-git-grep` does not have a key mapping in Doom so let’s set one. `SPC s R` is unused.
(map!
:nv "SPC s R" #'counsel-git-grep
)
Commenting
`g c ?` evil-comment-operator - needs practice/research I’m not sure what the third key is supposed to do, but `g c c` seems to do what I want - comment a line. With a selection(a.k.a. in visual mode), `G c` will comment the region, so that is nice. To bad that wasn’t easier to figure out. There isn’t anything helpful in the faq or docs via a search for “comment”.
I’m used to this key mapping from Spacemacs. There is both :n(normal) and :v(visual) so a selected region can be commented. Even though this is redundant, I’ll keep it, maybe it will help with the transition.
(map!
:nv "SPC c l" #'evilnc-comment-or-uncomment-lines
)
I’ve been wanting this and didn’t know that it’s been there all along. I found this in Doom’s FAQ:
There’s a text object for every “step” of expansion that expand-region provides (and more). To select the word at point =
viw
, symbol at point =vio
, line at point =V
, the block at point (by indentation) =vii
, the block at point (by braces) =vib
, sentence at point =vis
, paragraph =vip
, and so on.
- Selection expansion can be emulated by using text objects consecutively:
viw
to select a word, followed byio
to expand to a symbol, thenib
expands to the surrounding brackets/parentheses, etc. There is no reverse of this however; you’d have to restart visual state.
It would be cool if this was complete, but it is not. It is just a lot of work. I mostly did this as a review excersize to familiarize myself with Doom’s default keymapping.
;; a evil-append used sometimes, but less than i ;; r evil-replace not used much ;; s evil-snipe-s not used much, practicing
;; d evil-delete used often
;; o evil-open-below used often ;; i evil-insert used often ;; e evil-forward-word-end used often ;; n evil-scroll-up/down ;; b evil-backward-word-begin used often ;; v evil-visual used often (repeat to expand) ;; c evil-change not used often; a,del instead ;; x evil-delete used sometimes ;; z prefix for a bunch of stuff
;; m evil-set-marker not used, but probably should try it out ;; , evil-snipe-revers-repeat depends on use of evil-snipe ;; . evil-repeat not used, don’t want to repeat much ;; u evil-undo ;; y evil-yank
;; g prefix to a bunch of stuff ;; p evil-paste-after used often (paste) ;; f evil-swipe-f not used - must replace dup of s ;; w evil-forward-word-begin - must replace dup of b ;; q evil-record-macro not used, would like to practice though
;; L evil-window-bottom not used, scroll or avy instead ;; Y evil-yank-line practice ;; E evil-forward-WORD-end ;; B evil-backward-WORD-begin ;; H evil-window-top not used, scroll or avy instead ;; D evil-delete-line not used, but makes sense ;; R evil-replace-state not used ;; A evil-append-line ;; Z prefix to exit stuff ;; W evil-forward-WORD-begin not used ;; F evil-snipe-f not used ;; P evil-paste-before not used but makes sense ;; G evil-goto-line broken set magit status here perhaps? ;; J evil-join used a lot ;; K +lookup/documentation
;; note: need pop-mark-command
I followed this setup: https://www.masteringemacs.org/article/keeping-secrets-in-emacs-gnupg-auth-sources This didn’t work before setting file-name-handler-alist - thanks doomemacs/doomemacs#3339
; I don't have that secret on this machine currently
;(let ((file-name-handler-alist (cons epa-file-handler nil)))
; (load-library (expand-file-name "~/.doom.d/secrets.el.gpg")))
Ruby
I had to go back and add rbenv support in init.el, which looks like: `(ruby +rails +rbenv)`. With a `doom/reload`, `projectile-rails-console` worked(via `SPC m r r`). I’m noticing `robe-jump` is not bound however. There are probably not any better ways to jump to a definition than asking the thing that is actually running the code, so let’s set that.
(map!
:n "SPC m g" #'robe-jump)
How should rbenv fit in rbenv.el?
I didn’t see an option in Rubocop or Rufo to format a common situation in code: the long argument function call. I would like to replace commas with commas and newlines so each argument is on it’s own line. Then indent, or format the whole method/def.
(defun my-format-args-in-defun ()
(interactive)
(save-excursion
(er/mark-defun)
(goto-char (region-beginning))
(while (re-search-forward ", " (region-end) t)
(replace-match ",
")
(indent-according-to-mode))
))
;; Control-Super-f a.k.a. Control and Command and f at the same time
(map! :nv "C-s-f" 'my-format-args-in-defun )
;; todo use asdf
It is common to want to indent more than one line at once in order to clean things up. This could be done upon save with a formatter, but, I like to see it before I save it, so I’ll do this workflow: `v i p` to select the paragraph(or text between two emty lines), or `v i g` for whole buffer, then `=` for `evil-indent`. These could be combined into one action, but this is good enough for now, so no key mapping needed here.
SPC SPC for find file is fine, but I like the mnemonic version `SPC f f`.
(map!
:leader :desc "M-x" :g "SPC" #'counsel-M-x)
The author of doom seems to purport to have a good alternative to smartparens, but I’m not seeing it. Smartparens is great, it is already enabled, let’s get the benefits. It will delete both boundary chars and not leave one hanging, forcing you to delete two boundary chars(i.e.: “’)]}, etc.) Plus, I only see “inner” selections such as `evil-textobj-anyblock-inner-block`which won’t select the boundary chars, which means to delete those is more key presses. That gets to be a lot of key presses to delete something.
!! Update this is incompatable with the lispy doom module -> learn lispy?
(smartparens-global-strict-mode t)
(map!
:leader
:prefix ("k" . "smartparens")
:n "x" #'sp-kill-sexp
:n "c" #'sp-copy-sexp
:n "s" #'sp-forward-slurp-sexp
:n "b" #'sp-forward-barf-sexp
:n "p" #'sp-wrap-square
:n "t" #'sp-wrap-round
:n "v" #'sp-wrap-curly
:n "R" #'sp-rewrap-sexp
:n "," #'sp-transpose-sexp
:n "f" #'cider-format-buffer
:n "d" #'cider-format-defun
)
(map!
;; :prefix ("C-s" . "Control-Super")
:ginv "C-s-r" #'er/expand-region
)
(setq +workspaces-switch-project-function
(lambda (dir)
;;(let ((magit-inhibit-save-previous-winconf 't))
;; (magit-status dir))
(magit-status dir)
;;(project-shell)
;;(spawn-shell (concat "*" (this-dir-name dir) "*" ) '("h") )
;; (+shell/toggle)
))
;; fullframe magit buffer
(setq magit-display-buffer-function
(lambda (buffer)
(display-buffer buffer '(magit--display-buffer-fullframe))))
;; undo doom's magit setting for this, this is the default
(setq magit-bury-buffer-function 'magit-restore-window-configuration)
(setq magit-list-refs-sortby "-creatordate")
(eval-after-load 'magit-diff
(lambda ()
(keymap-set magit-diff-section-base-map "<return>" 'magit-diff-visit-worktree-file)
(keymap-set magit-diff-section-base-map "C-<return>" 'magit-diff-visit-file)))
The zen doom module might be neat, for this file in particular. I uncommented zen in `init.el` and reload doom with `SPC h r r`. In addition the fullscreen option does not seem to work but there is a function for that, and it is mapped to `SPC t F`(toggle-frame-fullscreeen). Maybe a mode hook is in order here? I’m noticing line numbers and the slightest green edge line to indicate additions not yet staged in git, and bigger and different font too. so, neat!
More modules!
I poked around what else is available in init.el and I noticed a few things I should have as a web developer. So I uncommented these modules: web, yaml, docker. I also have been wanting to port my shell popwin function, but I see that doom has one in the shell, and eshell modules so I’ll try both. I uncommented shell,eshell.
I also want to explore doing more in emacs, so I’m trying these modules: emacs-everywhere and deft.
(defun ct/ws-handler ()
(lambda (request)
(with-slots (process headers) request
;; (ws-response-header process 200 '("Content-Type" . "text/html"))
(ws-send-file process (expand-file-name "~/.doom.d/index.html"))
;; (process-send-string process (file ))
)))
;; (ws-stop-all)
(defun ct/ws-stop ()
(ws-stop ct/ws)
(setq ct/ws nil))
(use-package web-server
:config
(defvar ct/ws nil)
(if ct/ws
(message "web server is running")
(setq ct/ws
(ws-start (ct/ws-handler)
9000))))
This is so useful. What am I doing wrong here? Am I supposed to use `avy-pop-mark`? That function doesn’t do what I want though. So here.
(map! :nv "C-." #'pop-to-mark-command )
(map! :leader :n "ff" #'ffap)
(defun aws-vault-exec ()
(interactive)
(let ((aws-vault-output-buffer-name "aws-vault output"))
(shell-command "AWS_VAULT= aws-vault exec ve -- env | grep AWS"
(get-buffer-create aws-vault-output-buffer-name))
(with-current-buffer aws-vault-output-buffer-name
(dolist (line (split-string (buffer-string) "\n"))
(destructuring-bind (name . value) (split-string line "=")
(setenv name (first value)))))
(kill-buffer aws-vault-output-buffer-name)))
(setq ffap-newfile-prompt t)
Spawn shell is no more.
Really this can be simplified to an advice on `project-shell`
(eval-after-load 'project
(progn ()
(advice-add 'project-shell :after #'+popup/buffer)
(map! :leader :nv "p s" 'project-shell)))
(defun minus-user-path (path)
(let ((in-project-path (s-chop-prefix (projectile-project-root) path)))
(s-concat (projectile-project-name) "/" in-project-path)))
(defun where-am-i ()
"copies present location into kill-ring and clipboard"
(interactive)
(kill-new (message (concat (minus-user-path buffer-file-name) ":" (int-to-string (line-number-at-pos))))))
(defun share-line ()
(concat
(where-am-i) (copy-line-as)))
;; (use-package org-journal)
;; need to install this:
;; (use-package org-journal-tags
;; :after (org-journal cl-lib)
;; :config
;; (org-journal-tags-autosync-mode))
;; thanks https://stackoverflow.com/a/6174107/714357
(defun my-random-sort-lines (beg end)
"Sort lines in region randomly."
(interactive "r")
(save-excursion
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(let ;; To make `end-of-line' and etc. to ignore fields.
((inhibit-field-text-motion t))
(sort-subr nil 'forward-line 'end-of-line nil nil
(lambda (s1 s2) (eq (random 2) 0)))))))
(use-package browse-kill-ring)
not to be confused with the lisp object system I don’t think this is neccessary due to env-from-shell or whatever thats called
;; (use-package asdf
;; :after (asdf-enable))
save them all
(add-hook 'bookmark-set 'bookmark-save)
global aliases to eval tests in the repl
(setq cider-clojure-cli-global-aliases ":dev:test")
`C-c ret` is insert new heading
After `SPC p c` to compile a project, use `M-i` to edit the command
(global-auto-revert-mode t)