Inspired by Haruki Murakami’s “What I Talk About When I Talk About Running”. Well I was reading this book when writing this.

and many of the Emacs configs out there. Here are my personal journal of how I setup my Emacs environment for writing and coding.



This is kinda personal preference but it will effect the whole setup. I used to be a vim typer. I use \ as keyleader instead of Space.

(setq doom-leader-key "\\"
      doom-leader-alt-key "M-\\"
      doom-localleader-key "M-,"
      doom-localleader-alt-key "M-,")


(doom! :input
       ;;layout            ; auie,ctsrnm is the superior home row

       company           ; the ultimate code completion backend
       ;;helm              ; the *other* search engine for love and life
       ;;ido               ; the other *other* search engine...
       ;;ivy               ; a search engine for love and life
       (vertico +icons)           ; the search engine of the future

       ;;deft              ; notational velocity for Emacs
       doom              ; what makes DOOM look the way it does
       doom-dashboard    ; a nifty splash screen for Emacs
       doom-quit         ; DOOM quit-message prompts when you quit Emacs
       ;;(emoji +unicode)  ; 🙂
       hl-todo           ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
       ;;indent-guides     ; highlighted indent columns
       ;;ligatures         ; ligatures and symbols to make your code pretty again
       ;;minimap           ; show a map of the code on the side
       modeline          ; snazzy, Atom-inspired modeline, plus API
       ;;nav-flash         ; blink cursor line after big motions
       ;;neotree           ; a project drawer, like NERDTree for vim
       ophints           ; highlight the region an operation acts on
       (popup +defaults)   ; tame sudden yet inevitable temporary windows
       tabs              ; a tab bar for Emacs
       (treemacs +lsp)          ; a project drawer, like neotree but cooler
       ;;unicode           ; extended unicode support for various languages
       vc-gutter         ; vcs diff in the fringe
       vi-tilde-fringe   ; fringe tildes to mark beyond EOB
       ;;window-select     ; visually switch windows
       workspaces        ; tab emulation, persistence & separate workspaces
       zen               ; distraction-free coding or writing

       (evil +everywhere); come to the dark side, we have cookies
       file-templates    ; auto-snippets for empty files
       fold              ; (nigh) universal code folding
       ;;(format +onsave)  ; automated prettiness
       ;;god               ; run Emacs commands without modifier keys
       ;;lispy             ; vim for lisp, for people who don't like vim
       multiple-cursors  ; editing in many places at once
       ;;objed             ; text object editing for the innocent
       ;;parinfer          ; turn lisp into python, sort of
       ;;rotate-text       ; cycle region at point between text candidates
       snippets          ; my elves. They type so I don't have to
       word-wrap         ; soft wrapping with language-aware indent

       dired             ; making dired pretty [functional]
       electric          ; smarter, keyword-based electric-indent
       ;;ibuffer         ; interactive buffer management
       undo              ; persistent, smarter undo for your inevitable mistakes
       vc                ; version-control and Emacs, sitting in a tree

       ;;eshell            ; the elisp shell that works everywhere
       ;;shell             ; simple shell REPL for Emacs
       ;;term              ; basic terminal emulator for Emacs
       vterm             ; the best terminal emulation in Emacs

       syntax              ; tasing you for every semicolon you forget
                                        ;(spell +flyspell) ; tasing you for misspelling mispelling
                                        ;grammar           ; tasing grammar mistake every you make

       ;;biblio            ; Writes a PhD for you (citation needed)
       ;;debugger          ; FIXME stepping through code, to help you add bugs
       ;;editorconfig      ; let someone else argue about tabs vs spaces
       ;;ein               ; tame Jupyter notebooks with emacs
       (eval +overlay)     ; run code, run (also, repls)
       ;;gist              ; interacting with github gists
       (lookup +dictionary)              ; navigate your code and its documentation
       (lsp +peek)               ; M-x vscode
       magit             ; a git porcelain for Emacs
       ;;make              ; run make tasks from Emacs
       ;;pass              ; password manager for nerds
       ;;pdf               ; pdf enhancements
       ;;prodigy           ; FIXME managing external services & code builders
       ;;rgb               ; creating color strings
       ;;taskrunner        ; taskrunner for all your projects
       ;;terraform         ; infrastructure as code
       ;;tmux              ; an API for interacting with tmux
       ;;upload            ; map local to remote projects via ssh/ftp

       (:if IS-MAC macos)  ; improve compatibility with macOS
       ;;tty               ; improve the terminal Emacs experience

       ;;agda              ; types of types of types of types...
       ;;beancount         ; mind the GAAP
       ;;cc                ; C > C++ == 1
       ;;clojure           ; java with a lisp
       ;;common-lisp       ; if you've seen one lisp, you've seen them all
       ;;coq               ; proofs-as-programs
       ;;crystal           ; ruby at the speed of c
       ;;csharp            ; unity, .NET, and mono shenanigans
       ;;data              ; config/data formats
       (dart +flutter +lsp)   ; paint ui and not much else
       ;;elixir            ; erlang done right
       (elm +lsp)               ; care for a cup of TEA?
       emacs-lisp        ; drown in parentheses
       ;;erlang            ; an elegant language for a more civilized age
       ;;ess               ; emacs speaks statistics
       ;;faust             ; dsp, but you get to keep your soul
       ;;fortran           ; in FORTRAN, GOD is REAL (unless declared INTEGER)
       ;;fsharp            ; ML stands for Microsoft's Language
       ;;fstar             ; (dependent) types and (monadic) effects and Z3
       ;;gdscript          ; the language you waited for
       ;;(go +lsp)         ; the hipster dialect
       (haskell +lsp)    ; a language that's lazier than I am
       ;;hy                ; readability of scheme w/ speed of python
       ;;idris             ; a language you can depend on
       json              ; At least it ain't XML
       ;;(java +meghanada) ; the poster child for carpal tunnel syndrome
       (javascript +lsp)        ; all(hope(abandon(ye(who(enter(here))))))
       ;;julia             ; a better, faster MATLAB
       ;;kotlin            ; a better, slicker Java(Script)
       ;;latex             ; writing papers in Emacs has never been so fun
       ;;lean              ; for folks with too much to prove
       ;;ledger            ; be audit you can be
       lua               ; one-based indices? one-based indices
       markdown          ; writing docs for people to ignore
       ;;nim               ; python + lisp at the speed of c
       ;;nix               ; I hereby declare "nix geht mehr!"
       ;;ocaml             ; an objective camel
       (org +pretty +hugo)               ; organize your plain life in plain text
       ;;php               ; perl's insecure younger brother
       ;;plantuml          ; diagrams for confusing people more
       ;;purescript        ; javascript, but functional
       python            ; beautiful is better than ugly
       ;;qt                ; the 'cutest' gui framework ever
       ;;racket            ; a DSL for DSLs
       ;;raku              ; the artist formerly known as perl6
       rest              ; Emacs as a REST client
       ;;rst               ; ReST in peace
       ;;(ruby +rails)     ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
       (rust +lsp)              ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
       ;;scala             ; java, but good
       ;;(scheme +guile)   ; a fully conniving family of lisps
       (sh +lsp)                ; she sells {ba,z,fi}sh shells on the C xor
       ;;solidity          ; do you need a blockchain? No.
       ;;swift             ; who asked for emoji variables?
       ;;terra             ; Earth and Moon in alignment for performance.
       (web +lsp)               ; the tubes
       (yaml +lsp)              ; JSON, but readable
       ;;zig               ; C, but simpler

       ;;(mu4e +org +gmail)
       ;;(wanderlust +gmail)

       ;;everywhere        ; *leave* Emacs!? You must be joking
       ;;irc               ; how neckbeards socialize
       ;;(rss +org)        ; emacs as an RSS reader
       ;;twitter           ; twitter client

       (default +bindings +smartparens))

The configuration

Everything from this point on is either in ~/.doom.d/config.el for configuration and in ~/.doom.d/packages.el for package installation.

Default doom setup

Some functionality uses this to identify you, e.g. GPG configuration, email clients, file templates and snippets.

(setq user-full-name "Chop Tr ("
      user-mail-address "[email protected]")

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” :size 12 :weight ‘semi-light) doom-variable-pitch-font (font-spec :family “sans” :size 13))

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-tomorrow-night)

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.


My screen is small. I Prefer 2 space indentation:

(setq standard-indent 2)

Search wrapping

(setq evil-search-wrap t)

Doom splash screen

(defun doom-dashboard-draw-ascii-emacs-banner-fn ()
  (let* ((banner
          '("      __                          __                             "
            "     /\\ \\                        /\\ \\__                          "
            "  ___\\ \\ \\___     ___   _____    \\ \\ ,_\\  _ __    __      ___    "
            " /'___\\ \\  _ `\\  / __`\\/\\ '__`\\   \\ \\ \\/ /\\`'__\\/'__`\\  /' _ `\\  "
            "/\\ \\__/\\ \\ \\ \\ \\/\\ \\L\\ \\ \\ \\L\\ \\   \\ \\ \\_\\ \\ \\//\\ \\L\\.\\_/\\ \\/\\ \\ "
            "\\ \\____\\\\ \\_\\ \\_\\ \\____/\\ \\ ,__/    \\ \\__\\\\ \\_\\\\ \\__/.\\_\\ \\_\\ \\_\\"
            " \\/____/ \\/_/\\/_/\\/___/  \\ \\ \\/      \\/__/ \\/_/ \\/__/\\/_/\\/_/\\/_/"
            "                          \\ \\_\\                                  "
            "                           \\/_/                                  "
            "                                                                 "))
         (longest-line (apply #'max (mapcar #'length banner))))
     (dolist (line banner (point))
       (insert (+doom-dashboard--center
                 line (make-string (max 0 (- longest-line (length line)))
     'face 'doom-dashboard-banner)))

(setq +doom-dashboard-ascii-banner-fn #'doom-dashboard-draw-ascii-emacs-banner-fn)


Automatic tangle on save

(add-hook 'org-mode-hook
          (lambda () (add-hook 'after-save-hook #'org-babel-tangle :append :local)))

Set the window size upon startup. (May need to edit below depends on the monitor size)

(if (string= (getenv "USER") "lw70868")
    (setq initial-frame-alist '((top . 1) (left . 1) (width . 190) (height . 65)))
  (setq initial-frame-alist '((top . 1) (left . 1) (width . 177) (height . 55))))



If you are in a buffer with lsp-mode enabled and a server that supports textDocument/formatting, it will be used instead of format-all’s formatter.

  • To disable this behavior universally use: (setq +format-with-lsp nil)
  • To disable this behavior in one mode: (setq-hook! ‘python-mode-hook +format-with-lsp nil)
(setq-hook! 'haskell-mode-hook +format-with-lsp nil)

The command format-all-ensure-formatter will ensure that a default formatter is selected in case you don’t have one set; you can customize the default formatter for each language. To ensure a formatter is set whenever you enable format-all-mode, you can use: (add-hook format-all-mode-hook ‘format-all-ensure-formatter).

;; (add-hook 'format-all-mode-hook 'format-all-ensure-formatter)

Signature auto-activate

(setq lsp-signature-auto-activate nil)



(package! ob-ts-node :recipe (:host github :repo "tmythicator/ob-ts-node"))



(package! tree-sitter)
(package! tree-sitter-langs)


(use-package! tree-sitter
  (require 'tree-sitter-langs)
  (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)
  (pushnew! tree-sitter-major-mode-language-alist
          '(scss-mode . css))
  (pushnew! tree-sitter-major-mode-language-alist
          '(haskell-mode . haskell)))


Workspaces and perspectives

Projects are beautifully managed. Can be added with <C-p><C-p>a.

Edit workspaces by treemacs-edit-workspaces

Additional keymaps

(defun treemacs-find-and-goto-treemacs ()
(map! :n "`h" #'treemacs-find-and-goto-treemacs)

(with-eval-after-load 'treemacs
  (define-key evil-treemacs-state-map "s" 'treemacs-visit-node-horizontal-split))

(with-eval-after-load 'treemacs
  (define-key evil-treemacs-state-map (kbd "<SPC>") #'avy-goto-line))

(with-eval-after-load 'treemacs
  (define-key evil-treemacs-state-map (kbd "\\\\") #'+treemacs/toggle))

(map! :n "\\\\" #'+treemacs/toggle)
(after! doom-themes
  (setq doom-themes-treemacs-theme "doom-colors") ; use "doom-colors" for less minimal icon theme

Doom theme

(after! lsp-treemacs
  (load-library "doom-themes-ext-treemacs"))



  • Use projectile-invalidate-cache to cleanup trash files in current project. I have typescript project that builded js files next to the source by accident and didn’t know how to clean it up from the find file list. Took me good 30 minutes to find this command 🤦


(map! :leader :n "g p" #'git-gutter:popup-hunk)

Avy - Jump mode

avy is a GNU Emacs package for jumping to visible text using a char-based decision tree. See also ace-jump-mode and vim-easymotion - avy uses the same idea.


(map! :n "<SPC>" #'evil-avy-goto-word-0)



The list of the default decision chars.

(setq avy-keys '(?q ?t ?e ?r ?y ?u ?o ?p
                    ?a ?s ?d ?w ?b ?n ?v
                    ?k ?l ?z ?x ?c ?j ?g
                    ?h ?f ?i ?m))


The default overlay display style.

This setting will be used for all commands, unless overridden in avy-styles-alist.

Six styles are currently available:

  1. pre: - full path before target, leaving all original text.
  2. at: - single character path on target, obscuring the target.
  3. at-full: full path on target, obscuring the target and the text behind it.
  4. post: full path after target, leaving all original text.
  5. de-bruijn: like at-full but the path is in a De Bruijn sequence.
  6. words: like at-full, but the path consists of words as defined by avy-words.

At first it seems that pre and post are advantageous over at and at-full, since you can still see the original text with them. However, they make the text shift a bit. If you don’t like that, use either at or at-full.

(setq avy-style 'de-bruijn)

Org mode


(add-hook 'org-mode-hook #'+org-pretty-mode)

Change the ellipsis

(setq org-ellipsis " [+]")

Insert clipboard image into org file

Require: vips, vipsthumbnail, pngpaste


# Location: ~/bin/clipboard-image-paste
# Should be avaiable in PATH

function help() {
  echo "$0 <size> <output_file> <format>"
  echo "Example: $0 1280 example.png \"png[Q=85]\""
  echo "Note: The last argument need to have double quote"

if [[ -z $2 ]]; then
  exit 1

if [[ -z $3 ]]; then

pngpaste "/tmp/pngpaste.png"

# Resize the image if greater than $1 with given $format
output=$(echo "out_pngpaste.$format"| sed -E 's/(out_.*\.)(png|jpg|jpeg|webp).*/\1\2/g')
vipsthumbnail -s "$1x$1>" -o "out_%s.$format" "/tmp/pngpaste.png"

rm /tmp/pngpaste.png
mv "/tmp/$output" $2
(defun org-insert-clipboard-image (&optional file)
  (interactive "F")
  (setq filename (concat file (format-time-string "_%Y%m%d_%H%M%S") ".png"))
  (shell-command (concat "clipboard-image-paste 1280 " filename " \"png[Q=85]\""))
  (insert "#+attr_html: :width 720\n")
  (insert (concat "[[" filename "]]")))

Centaur tab

Turn on the tabs by projects instead of file type

(with-eval-after-load 'centaur-tabs

Tab moving and reordering

Note: In Doom emacs s key is super key, aka on MacOS, Windows key on Windows.

(map! :n "H" #'+tabs:previous-or-goto)
(map! :n "L" #'+tabs:next-or-goto)
(map! :n "C-M-{" #'centaur-tabs-move-current-tab-to-left)
(map! :n "C-M-}" #'centaur-tabs-move-current-tab-to-right)
(map! :n "X" #'kill-current-buffer)
;; Need to unbind this for org-mode
(with-eval-after-load 'evil-org
  (define-key evil-org-mode-map (kbd "<normal-state> X") nil))

Tab numbers

(map! :desc "Goto Tab 1" :n "s-1" (cmd! (+tabs:next-or-goto 1))
      :desc "Goto Tab 2" :n "s-2" (cmd! (+tabs:next-or-goto 2))
      :desc "Goto Tab 3" :n "s-3" (cmd! (+tabs:next-or-goto 3))
      :desc "Goto Tab 4" :n "s-4" (cmd! (+tabs:next-or-goto 4))
      :desc "Goto Tab 5" :n "s-5" (cmd! (+tabs:next-or-goto 5))
      :desc "Goto Tab 6" :n "s-6" (cmd! (+tabs:next-or-goto 6)))

Personal Keymaps

Combo search replace with n.

Search current work > Jump back to it > Change it. After that you can redo the change by pressing n.

(define-key evil-motion-state-map "C-f" nil)
(map! :n "C-f w" "*Nciw")

Change or subtitute should not replace the registers

(evil-define-operator evil-change-without-register (beg end type _ yank-handler)
  (interactive "<R><y>")
  (evil-change beg end type ?_ yank-handler))

(evil-define-operator evil-delete-without-register (beg end type _ _2)
  (interactive "<R><y>")
  (evil-delete beg end type ?_))

(evil-define-command evil-visual-paste-without-register (count &optional register)
  "Paste over Visual selection."
  :suppress-operator t
  (interactive "P<x>")
  ;; evil-visual-paste is typically called from evil-paste-before or
  ;; evil-paste-after, but we have to mark that the paste was from
  ;; visual state
  (setq this-command 'evil-visual-paste)
  (let* ((text (if register
                   (evil-get-register register)
                 (current-kill 0)))
         (yank-handler (car-safe (get-text-property
                                  0 'yank-handler text)))
      (let* ((kill-ring (list (current-kill 0)))
             (kill-ring-yank-pointer kill-ring))
        (when (evil-visual-state-p)
          (evil-visual-rotate 'upper-left)
          ;; if we replace the last buffer line that does not end in a
          ;; newline, we use ~evil-paste-after~ because ~evil-delete~
          ;; will move point to the line above
          (when (and (= evil-visual-end (point-max))
                     (/= (char-before (point-max)) ?\n))
            (setq paste-eob t))
          (evil-delete-without-register evil-visual-beginning evil-visual-end
          (when (and (eq yank-handler #'evil-yank-line-handler)
                     (not (eq (evil-visual-type) 'line))
                     (not (= evil-visual-end (point-max))))
            (insert "\n"))
          (setq new-kill (current-kill 0))
          (current-kill 1))
        (if paste-eob
            (evil-paste-after count register)
          (evil-paste-before count register)))
      (kill-new new-kill)
      ;; mark the last paste as visual-paste
      (setq evil-last-paste
            (list (nth 0 evil-last-paste)
                  (nth 1 evil-last-paste)
                  (nth 2 evil-last-paste)
                  (nth 3 evil-last-paste)
                  (nth 4 evil-last-paste)

(evil-define-command evil-paste-after-without-register (count &optional register yank-handler)
  "evil paste before without yanking"
  :suppress-operator t
  (interactive "P<x>")
  (if (evil-visual-state-p)
      (evil-visual-paste-without-register count register)
    (evil-paste-after count register yank-handler)))
(define-key evil-motion-state-map "p" 'evil-paste-after-without-register)
(define-key evil-motion-state-map "s" 'evil-change-without-register)
(define-key evil-motion-state-map "c" 'evil-change-without-register)

Here I overwrite the built-in evil-change . Therefore, need to update when the official implement change (should not be too often).

(with-eval-after-load 'evil
  (evil-define-operator evil-change
    (beg end type register yank-handler delete-func)
    "Change text from BEG to END with TYPE.
Save in REGISTER or the kill-ring with YANK-HANDLER.
DELETE-FUNC is a function for deleting text, default `evil-delete'.
If TYPE is `line', insertion starts on an empty line.
If TYPE is `block', the inserted text in inserted at each line
of the block."
    (interactive "<R><x><y>")
    ;; (let ((delete-func (or delete-func #'evil-delete))
    (let ((delete-func (or delete-func #'evil-delete-without-register))
          (nlines (1+ (evil-count-lines beg end)))
          (opoint (save-excursion
                    (goto-char beg)
      (unless (eq evil-want-fine-undo t)
      (funcall delete-func beg end type register yank-handler)
       ((eq type 'line)
        (setq this-command 'evil-change-whole-line) ; for evil-maybe-remove-spaces
        (if (= opoint (point))
            (evil-open-above 1)
          (evil-open-below 1)))
       ((eq type 'block)
        (evil-insert 1 nlines))
        (evil-insert 1)))
      (setq evil-this-register nil))))

Map the s key to change

(define-key evil-motion-state-map "s" 'evil-substitute)
(define-key evil-motion-state-map "S" 'evil-change-whole-line)

Use symbol to moving instead of word

(with-eval-after-load 'evil
    (defalias #'forward-evil-word #'forward-evil-symbol)
    ;; make evil-search-word look for symbol rather than word boundaries
    (setq-default evil-symbol-word-search t))

Move parentheses

(map! :ni "C-)" #'sp-forward-slurp-sexp)
(map! :ni "C-(" #'sp-backward-slurp-sexp)

Font display

Font face

(if (string= (getenv "USER") "lw70868")
    (setq doom-font (font-spec :family "FiraCode Nerd Font Mono" :size 14)
          doom-variable-pitch-font (font-spec :family "Source Serif Pro" :size 16)
          doom-big-font (font-spec :family "FiraCode Nerd Font Mono" :size 18))
  (setq doom-font (font-spec :family "FiraCode Nerd Font Mono" :size 13)
        doom-variable-pitch-font (font-spec :family "Source Serif Pro" :size 15)
        doom-big-font (font-spec :family "FiraCode Nerd Font Mono" :size 17)))

Org pretty mode

Hide emhasis marker and toggles pretty entities.

(add-hook 'org-mode-hook #'+org-pretty-mode)

Bigger heading

  '(org-document-title :height 1.2)
  '(outline-1 :weight extra-bold :height 1.25)
  '(outline-2 :weight bold :height 1.15)
  '(outline-3 :weight bold :height 1.12)
  '(outline-4 :weight semi-bold :height 1.09)
  '(outline-5 :weight semi-bold :height 1.06)
  '(outline-6 :weight semi-bold :height 1.03)
  '(outline-8 :weight semi-bold)
  '(outline-9 :weight semi-bold))

Italic quote block

(setq org-fontify-quote-and-verse-blocks t)

Zen mode

Reduce zen mode zoom

(setq +zen-text-scale 1.396)

Disable packages

Not really a fan of this jump method

(package! evil-snipe :disable t)