Skip to content

Latest commit

 

History

History
896 lines (642 loc) · 26 KB

config.org

File metadata and controls

896 lines (642 loc) · 26 KB

My Doom Emacs Config

Table of Contents

Todos

Figure out how to make LSP ignore certain files so that it can watch a reasonable amount of files without asking or slowing things down

Packages

Add package! directives here, and packages.el will be generated from this section.

;; -*- no-byte-compile: t; -*-
;;; $DOOMDIR/packages.el

;;; NOTE: `packages.el' is now generated from `config.org'. Please edit that file
;;;       in Emacs and `packages.el' will be generated automatically!

;; To install a package with Doom you must declare them here and run 'doom sync'
;; on the command line, then restart Emacs for the changes to take effect -- or
;; use 'M-x doom/reload'.

;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
;(package! some-package)

(package! pdf-tools)
(package! nov)
(package! polymode)
(package! terraform-mode)
(package! zig-mode)

Header

Write the header and warning to edit this file instead of config.el directly.

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-

;;; NOTE: `config.el' is now generated from `config.org'. Please edit that file
;;;       in Emacs and `config.el' will be generated automatically!

Imports

You will need to fill out a secret.el file for this config to work.

If you’d like an example secret.el file to fill out, put your cursor on this code block and press C-c C-c to run it.

if [ ! -f secret.el ]; then
  cp secret.example.el secret.el
  echo "copied to secret.el, now go fill out the file"
else
  echo "secret.el already exists, make sure it has the correct information"
fi
(load! "secret.el")

Functions

Helpful stuff

(defun me/random-choice (items)
  "Returns a random item from a given list"
  (let* ((size (length items))
         (index (random size)))
    (nth index items)))

(defun me/read-lines (file)
  "Reads a file, filters out lines starting with #, and returns the lines as a list"
  (let* ((file-contents (with-temp-buffer
                          (insert-file-contents file)
                          (buffer-substring-no-properties
                           (point-min)
                           (point-max))))
         (lines (split-string file-contents "\n" t)))
    (seq-remove (lambda (line) (string-match-p "^#" line)) lines)))

(defun me/random-line-from-file (file)
  "Reads a file and returns a random line"
  (me/random-choice (me/read-lines file)))

Initial Setup

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

(setq user-full-name me/full-name
      user-mail-address me/mail-address)

Doom Banner

Sets a random banner on startup - this will select a random banner from banners/*.png

(let* ((banner-directory (substitute-in-file-name "$HOME/.config/doom/resources/banners"))
       (command (concat "\\ls -A1d " banner-directory "/*.png"))
       (output (shell-command-to-string command))
       (banners (split-string output "\n" t))
       (banner (me/random-choice banners)))
  (setq fancy-splash-image banner))

Doom Quit

(setq +doom-quit-messages
      (me/read-lines
       (substitute-in-file-name
        "$HOME/.config/doom/resources/messages.txt")))

Basic Functionality

Load custom shell env

(doom-load-envvars-file "~/.emacs.d/.local/doom_only_env")

Set some sensible defaults

(setq-default delete-by-moving-to-trash t
              window-combination-resize t
              x-stretch-cursor t)

(setq undo-limit (* 80 1024 1024)
      evil-want-fine-undo t
      auto-save-default t
      truncate-string-ellipsis "")

Don’t move cursor back when exiting insert mode

(setq evil-move-cursor-back nil)

Turn on Auto Revert Mode globally. This will automatically refresh the buffer when the file changes on disk (either through externaledits or something like a git branch change). The buffer will NOT revert if you have unsaved changes.

(global-auto-revert-mode t)

Basic UI Configuration

Setup some basic UI defaults

  • Start with a set size and position
  • No minimal window chrome
  • Relative line numbers
  • No frame title text
(add-to-list 'initial-frame-alist '(width . (text-pixels . 1180)))
(add-to-list 'initial-frame-alist '(height . (text-pixels . 780)))
(add-to-list 'initial-frame-alist '(top . 50))
(add-to-list 'initial-frame-alist '(left . 45))

(after! doom-ui
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
  (tooltip-mode -1))

(setq display-line-numbers-type 'relative)

(setq-default frame-title-format '(""))

Fonts

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.

Download fonts

  • FiraCode Nerd Font (This is my own version, but you can also get it from nerdfonts.com)
  • Overpass (It’s pretty ¯\_(ツ)_/¯)
  • Myriad Pro (A proprietary Adobe font from a shady Turkish website, what could go wrong?)
  • SF Pro (Default system font in macOS that for some reason you have to download to use yourself)
  • Bookerly (Amazon’s latest and best eBook/Kindle font)
(setq me/fixed-width-font '(:family "ComicCode Nerd Font" :style "Medium")
      me/variable-pitch-font '(:family "Overpass" :style "Regular")
      me/variable-pitch-serif-font '(:family "Bookerly" :style "Regular"))

(setq me/org-font-family (plist-get me/variable-pitch-font :family)
      me/ebook-font-family (plist-get me/variable-pitch-serif-font :family))

(setq doom-emoji-fallback-font-families nil)
(setq doom-symbol-fallback-font-families nil)

(setq doom-font
      (font-spec :family (plist-get me/fixed-width-font :family)
                 :style  (plist-get me/fixed-width-font :style)
                 :size   14)
      doom-big-font
      (font-spec :family (plist-get me/fixed-width-font :family)
                 :style  (plist-get me/fixed-width-font :style)
                 :size   20)
      doom-variable-pitch-font
      (font-spec :family (plist-get me/variable-pitch-font :family)
                 :style  (plist-get me/variable-pitch-font :style)
                 :size   16))

Theme

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.

Some good themes:

  • doom-one (default)
  • doom-nord
  • doom-palenight
(setq doom-theme 'doom-palenight)

Setup Indent

Based on this.

(defun me/setup-indent (n)
  ;; java/c/c++
  (setq-local c-basic-offset n)

  ;; shell
  (setq-local sh-set-indent n)

  ;; web development
  (setq-local coffee-tab-width n) ; coffeescript
  (setq-local javascript-indent-level n) ; javascript-mode
  (setq-local js-indent-level n) ; js-mode
  (setq-local js2-basic-offset n) ; js2-mode, in latest js2-mode, it's alias of js-indent-level
  (setq-local web-mode-markup-indent-offset n) ; web-mode, html tag in html file
  (setq-local web-mode-css-indent-offset n) ; web-mode, css in html file
  (setq-local web-mode-code-indent-offset n) ; web-mode, js code in html file
  (setq-local css-indent-offset n) ; css-mode
  )

(defun me/office-code-style ()
  (interactive)
  (message "Office code style!")
  ;; use tab instead of space
  (setq-local indent-tabs-mode t)
  ;; indent 4 spaces width
  (me/setup-indent 4))

(defun me/personal-code-style ()
  (interactive)
  (message "My personal code style!")
  ;; use space instead of tab
  (setq indent-tabs-mode nil)
  ;; indent 2 spaces width
  (me/setup-indent 2))

(defun me/setup-develop-environment ()
  (interactive)
  (me/personal-code-style))

;; How to do this dynamically based on project name:
;; (defun me/setup-develop-environment ()
;;   (interactive)
;;   (let ((proj-dir (file-name-directory (buffer-file-name))))
;;     ;; if hobby project path contains string "hobby-proj1"
;;     (if (string-match-p "hobby-proj1" proj-dir)
;;         (me/personal-code-style))
;;     ;; if commericial project path contains string "commerical-proj"
;;     (if (string-match-p "commerical-proj" proj-dir)
;;         (me/office-code-style))))

;; prog-mode-hook requires emacs24+
(add-hook 'prog-mode-hook 'me/setup-develop-environment)
;; a few major-modes does NOT inherited from prog-mode
(add-hook 'web-mode-hook 'me/setup-develop-environment)

Org Mode

Better font faces

  • Set faces for heading levels
  • Ensure that anything that should be fixed-pitch in Org files appears that way
(defun me/org-font-setup ()
  (dolist (face '((:name org-level-1 :weight bold   :height 1.3)
                  (:name org-level-2 :weight bold   :height 1.2)
                  (:name org-level-3 :weight bold   :height 1.1)
                  (:name org-level-4 :weight normal :height 1.1)
                  (:name org-level-5 :weight normal :height 1.1)
                  (:name org-level-6 :weight normal :height 1.1)
                  (:name org-level-7 :weight normal :height 1.1)
                  (:name org-level-8 :weight normal :height 1.1)))

    (set-face-attribute (plist-get face :name) nil
                        :family me/org-font-family
                        :weight (plist-get face :weight)
                        :height (plist-get face :height))))

Basic Config

  • set org directory and agenda files
  • add timestamp when finished
  • add some org templates (try <el TAB in insert mode)
  • indent text according to outline structure
  • use variable pitch fonts in org mode
  • better text wrapping
  • setup fonts
  • no line numbers
  • habit support (syncs with FlatHabit)
(require 'org-tempo)
(require 'org-habit)

(after! org
  (setq
   org-ellipsis ""
   org-directory "~/projects/org/"
   org-agenda-files '("~/projects/org/agenda.org"
                      "~/projects/org/todo.org"
                      "~/Documents/FlatHabits/MyHabits.org")
   org-log-done 'time)

  (add-to-list 'org-structure-template-alist '("el"  . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("sh"  . "src sh"))
  (add-to-list 'org-structure-template-alist '("iex" . "src elixir"))
  (variable-pitch-mode 1)
  (me/org-font-setup))

(add-hook 'org-mode-hook (lambda ()
                           (visual-fill-column-mode 1)
                           (setq-local visual-fill-column-center-text t
                                       visual-fill-column-width 100)

                           (org-indent-mode 1)
                           (visual-line-mode 1)
                           (display-line-numbers-mode 0)))

Auto-tangle Configuration Files

List the files here that you want to auto-tangle on save

(defun me/org-babel-tangle-config ()
  (when (member (buffer-file-name)
                (list (expand-file-name "~/.config/doom/config.org")
                      (expand-file-name "~/.config/doom/install.org")))
    (let ((org-confirm-babel-evaluate nil))
      (org-babel-tangle))))

(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'me/org-babel-tangle-config)))

Plugin Config

Doom Modeline

  • show mode icons
  • make the modeline slightly taller
  • show the project name in the modeline
(after! doom-modeline
  (setq
   doom-modeline-major-mode-icon t
   doom-modeline-height 35
   doom-modeline-persp-name t))

Display the current time in the modeline (without date or load average)

(setq display-time-day-and-date nil
      display-time-default-load-average nil)

(display-time-mode 1)

If there is a battery, as in, on a laptop, then display it in the modeline

(if (equal "Battery status not available"
           (battery))
    (display-battery-mode 0)
    (display-battery-mode 1))

LF UTF-8 is the default file encoding, and thus not worth noting in the modeline. So, let’s conditionally hide it and only show the encoding when it’s different

(defun me/doom-modeline-conditional-buffer-encoding ()
  "We expect the encoding to be LF UTF-8, so only show the modeline when this is not the case"
  (setq-local doom-modeline-buffer-encoding
              (unless (and (memq (plist-get (coding-system-plist buffer-file-coding-system) :category)
                                 '(coding-category-undecided coding-category-utf-8))
                           (not (memq (coding-system-eol-type buffer-file-coding-system) '(1 2))))
                t)))

(add-hook 'after-change-major-mode-hook #'me/doom-modeline-conditional-buffer-encoding)

EVIL

I don’t use evil-escape-mode, so I may as well turn it off, I’ve heard it contributes a typing delay. I’m not sure it’s much, but it is an extra pre-command-hook that I don’t benefit from, so…

(after! evil-escape (evil-escape-mode -1))

Magit

(setq magit-revision-show-gravatars '("^Author:     " . "^Commit:     "))

Org Roam (not using)

;; (use-package! org-roam
;;   :defer t
;;   :init
;;   (setq org-roam-directory "~/Documents/OrgRoam")
;;   (setq +org-roam-open-buffer-on-find-file nil))

Flycheck

Turns off proselint because it complains when I cuss and we can’t have that

(setq-default flycheck-disabled-checkers '(proselint))

Treemacs

Set Treemacs visual config and theme

(setq
 treemacs-width 30
 treemacs-follow-mode t
 treemacs-position 'left
 doom-themes-treemacs-theme "doom-colors")

Tabs

Set Centaur tabs visuals and font

Also, make most tabs group by project not by org or elisp modes

Sets up tab grouping by:

  • *star buffers and magit buffers
  • EShell buffers
  • Dired buffers
  • Everything else is grouped by project
(after! centaur-tabs
  (centaur-tabs-group-by-projectile-project)
  (setq
   centaur-tabs-style "bar"
   centaur-tabs-set-bar 'none
   centaur-tabs-bar-height 30
   centaur-tabs-height 28)

  (centaur-tabs-change-fonts (plist-get me/fixed-width-font :family) 130)

  (defun centaur-tabs-hide-tab (x)
    "Do no to show buffer X in tabs."
    (let ((name (format "%s" x)))
        (or
        ;; Current window is not dedicated window.
        (window-dedicated-p (selected-window))

        ;; Buffer name not match below blacklist.
        (string-suffix-p "ex[web]" name)
        (string-prefix-p "*epc" name)
        (string-prefix-p "*helm" name)
        (string-prefix-p "*Helm" name)
        (string-prefix-p "*Compile-Log*" name)
        (string-prefix-p "*lsp" name)
        (string-prefix-p "*company" name)
        (string-prefix-p "*Flycheck" name)
        (string-prefix-p "*tramp" name)
        (string-prefix-p " *Mini" name)
        (string-prefix-p "*help" name)
        (string-prefix-p "*straight" name)
        (string-prefix-p " *temp" name)
        (string-prefix-p "*Help" name)
        (string-prefix-p "*mybuf" name)

        ;; Is not magit buffer.
        (and (string-prefix-p "magit" name)
            (not (file-name-extension name)))
        )))
)

Projectile

Set projectile ignored projects Set Projectile project search path

Refresh projects with M-x projectile-discover-projects-in-search-path.

(after! projectile
  (add-hook 'projectile-after-switch-project-hook (lambda ()
        (if (s-suffix? "printserver/" (projectile-project-root))
            (setq-local lsp-elixir-project-dir "printserver/packages/ex_printserver/"))))
  (setq projectile-ignored-projects '("~/" "/tmp/" "~/.emacs.d/" "/opt/homebrew/"))
  (setq projectile-project-search-path '("~/projects/" "~/campaigns/")))

Evil-Snipe

Disable evil-snipe mode so that S and s work as they do in vim

(remove-hook 'doom-first-input-hook #'evil-snipe-mode)

PDFs

This takes emacs from freezing up when opening a PDF to rendering it smoothly on a HiDPI screen

(use-package! pdf-tools
  :defer t
  :config
  (pdf-loader-install)
  (setq pdf-view-use-scaling t
        pdf-view-use-imagemagick nil))

eBooks

Most of this code is for setting the modeline, but it also sets up the nov package to display epub formatted eBooks

(use-package! nov
  :mode ("\\.epub\\'" . nov-mode)
  :config
  (map! :map nov-mode-map
        :n "R" #'nov-render-document
        :n "RET" #'nov-scroll-up)

  (defun doom-modeline-segment--nov-info ()
    (concat
     " "
     (propertize
      (cdr (assoc 'creator nov-metadata))
      'face 'doom-modeline-project-parent-dir)
     " "
     (cdr (assoc 'title nov-metadata))
     " "
     (propertize
      (format "%d/%d"
              (1+ nov-documents-index)
              (length nov-documents))
      'face 'doom-modeline-info)))

  (advice-add 'nov-render-title :override #'ignore)

  (defun +nov-mode-setup ()
    (face-remap-add-relative 'variable-pitch
                             :family me/ebook-font-family
                             :height 1.1
                             :width 'semi-expanded)
    (face-remap-add-relative 'default :height 1.1)
    (setq-local line-spacing 0.2
                next-screen-context-lines 4
                shr-use-colors nil)
    ;; (require 'visual-fill-column nil t)
    (setq-local visual-fill-column-center-text t
                visual-fill-column-width 100
                nov-text-width 90)
    (visual-fill-column-mode 1)
    (hl-line-mode -1)

    (add-to-list '+lookup-definition-functions #'+lookup/dictionary-definition)

    (setq-local mode-line-format
                `((:eval
                   (doom-modeline-segment--workspace-name))
                  (:eval
                   (doom-modeline-segment--window-number))
                  (:eval
                   (doom-modeline-segment--nov-info))
                  ,(propertize
                    " %P "
                    'face 'doom-modeline-buffer-minor-mode)
                  (:eval
                   (doom-modeline-segment--misc-info))
                  (:eval
                   (doom-modeline-segment--battery))
                  ,(propertize
                    " "
                    'face (if (doom-modeline--active) 'mode-line 'mode-line-inactive)
                    'display `((space
                                :align-to
                                (- (+ right right-fringe right-margin)
                                   ,(* (let ((width (doom-modeline--font-width)))
                                         (or (and (= width 1) 1)
                                             (/ width (frame-char-width) 1.0)))
                                       (string-width
                                        (format-mode-line (cons "" '(:eval (doom-modeline-segment--major-mode))))))))))
                  (:eval
                   (doom-modeline-segment--major-mode))
                  ))

    (nov-render-document))

  (add-hook 'nov-mode-hook #'+nov-mode-setup))

LSP

Do not watch files because it’s annoying when it asks every time

(setq lsp-enable-file-watchers nil)

Company

(setq company-idle-delay 0.5)

Elixir

Create a buffer-local hook to run elixir-format on save, only when we enable elixir-mode.

;; Enable format and iex reload on save
(add-hook 'elixir-mode-hook
          (lambda ()
            (add-hook 'before-save-hook 'elixir-format nil t)
            (add-hook 'after-save-hook 'alchemist-iex-reload-module)))

Setup heex-mode using tree-sitter

;; (define-derived-mode heex-mode web-mode "HEEx"
;;                      "Major mode for editing HEEx files")
;; (add-to-list 'auto-mode-alist '("\\.heex?\\'" . heex-mode))

;; (use-package tree-sitter
;;   :hook (prog-mode . (lambda ()
;;                         (require 'tree-sitter)
;;                         (require 'tree-sitter-langs)
;;                         (require 'tree-sitter-hl)
;;                         (tree-sitter-hl-mode)
;;                      ))
;;   :config
;;   ;; Directory to store grammers custom into
;;   (add-to-list 'tree-sitter-load-path (file-name-as-directory "~/.local/share/tree-sitter/"))

;;   ;; Add HEEx for tree-sitter:
;;   ;; 0. npm install -g tree-sitter-cli
;;   ;; 1. git clone https://github.com/phoenixframework/tree-sitter-heex.git
;;   ;; 2. gcc -shared -fPIC -g -O2 -I src src/parser.c -o ./heex.so -target aarch64-apple-darwin
;;   ;; 3. cp ./heex.so ~/.local/share/tree-sitter/
;;   (tree-sitter-load 'heex)
;;   (add-to-list 'tree-sitter-major-mode-language-alist '(heex-mode . heex))
;; )

;; (use-package tree-sitter-langs)

Setup Ploymode with Elixir and Web Mode

This sets up support for webmode inside of ~H Liveview eex sigils in Elixir files as well as support for .heex template files

(use-package! polymode
  :mode ("\.ex$" . poly-elixir-web-mode)
  :config
  (define-hostmode poly-elixir-hostmode :mode 'elixir-mode)
  (define-innermode poly-liveview-expr-elixir-innermode
    :mode 'web-mode
    :head-matcher (rx line-start (* space) "~H" (= 3 (char "\"'")) line-end)
    :tail-matcher (rx line-start (* space) (= 3 (char "\"'")) line-end)
    :head-mode 'host
    :tail-mode 'host
    :allow-nested nil
    :keep-in-mode 'host
    :fallback-mode 'host)
  (define-polymode poly-elixir-web-mode
    :hostmode 'poly-elixir-hostmode
    :innermodes '(poly-liveview-expr-elixir-innermode)))

(after! web-mode
  (dolist (tuple '(("elixir" . "\\.ex\\'")
                   ("elixir" . "\\.eex\\'")
                   ("elixir" . "\\.heex\\'")))
    (add-to-list 'web-mode-engines-alist tuple)))

Tramp

To use Tramp to edit files on remote servers, just use find-file (SPC .) and type something like /ssh:user@server:file/or/directory or /ssh:server:.

Tramp needs to recognize the prompt on the remote server to work correctly.

Below I set tramp’s terminal type to tramp so that I can use that in my remote configs.

If you have customized your prompt on the remote server, make sure that you add something like the following early on in the shell startup process. (I put it at the top of my .bashrc)

# remote: ~/.bashrc

# bail out before setting custom prompt (or anything else that tramp doesn't need)
[ "$TERM" = "tramp" ] && return

# or at the very least

if [ "$TERM" = "tramp" ]; then
  export PS1='$ '
else
  # load custom prompt here
fi
(setq tramp-default-method "ssh")
(setq tramp-terminal-type "tramp")

Key Bindings

(map! :desc "Open Dired here" :n "-" #'dired-jump)

(map! :desc "Next Tab" :g "s-}" #'centaur-tabs-forward)
(map! :desc "Previous Tab" :g "s-{" #'centaur-tabs-backward)

(map! :desc "Decrease current window width" :g "s-[" #'evil-window-decrease-width)
(map! :desc "Increase current window width" :g "s-]" #'evil-window-increase-width)

Additional Information

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.