UP | HOME

Emacs configuration

Table of Contents

Emacs configuration

Source for my Emacs configuration. The init.el file can be generated from this org file using org-babel-tangle (reference).

Boostrap straight.el

Use straight.el with the use-package form to install and manage emacs packages.

;; Disable emacs default package.el (in favor of straight.el)
(setq package-enable-at-startup nil)

;; Bootstrap straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package) ;; Use the use-package form to install define packages

(use-package straight
  :custom (straight-use-package-by-default t)) ;; Make :straight t the default to ensure packages are installed

Custom file location

Set the custom file location.

;; Set custom file location
(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)

Update defaults

Update a few defaults like the menu-bar, scroll-bar etc.

;; Remove menu, scroll bars and other visual things
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(setq column-number-mode nil)
(setq line-number-mode nil)
(setq size-indication-mode nil)
(setq inhibit-startup-screen t)
(setq mouse-yank-at-point t)
(setq-default indent-tabs-mode nil)

(global-display-line-numbers-mode t)

;; Disable line numbers for some modes
(dolist (mode '(org-mode-hook
                term-mode-hook
                shell-mode-hook
                treemacs-mode-hook
                eshell-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

(add-hook 'before-save-hook 'delete-trailing-whitespace)

Org-mode

I use org-mode to organize my tasks, edit this website and write meeting notes and keep some long-term documentation.

I have a few different files for tasks and I like to clean them up by removing old DONE tasks. My exact workflow is that I have a recurrent tasks every two weeks that reminds me to refile DONE tasks older than two weeks to an archive.org file.

To automate this, a few custom functions are needed to find DONE tasks older than two weeks. (source: https://stackoverflow.com/a/8186450)

(defun zin/since-state (since todo-state &optional done all)
  "List Agenda items that are older than SINCE.

TODO-STATE is a regexp for matching to TODO states.  It is provided to
`zin/find-state' to match inactive timestamps.
SINCE is compared to the result of `zin/org-date-diff'.  If
`zin/org-date-diff' is greater than SINCE, the entry is shown in the
Agenda.
Optional argument DONE allows for done and not-done headlines to be
evaluated.  If DONE is non-nil, match completed tasks.
Optional argument ALL is passed to `zin/find-state' to specify whether
to search for any possible match of STATE, or only in the most recent
log entry."
  (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
    ;; If DONE is non-nil, look for done keywords, if nil look for not-done
    (if (member (org-get-todo-state)
                (if done
                    org-done-keywords
                  org-not-done-keywords))
        (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
               (subtree-valid (save-excursion
                               (forward-line 1)
                               (if (and (< (point) subtree-end)
                                        ;; Find the timestamp to test
                                        (zin/find-state todo-state subtree-end all))
                                   (let ((startpoint (point)))
                                     (forward-word 3)
                                     ;; Convert timestamp into days difference from today
                                     (zin/org-date-diff startpoint (point)))))))
          (if (or (not subtree-valid)
                  (<= subtree-valid since))
              next-headline
            nil))
      (or next-headline (point-max)))))

(defun zin/find-state (state &optional end all)
  "Used to search through the logbook of subtrees.

Tests to see if the first line of the logbook is a change of todo
status to status STATE
- Status \"STATE\" from ...
The search brings the point to the start of YYYY-MM-DD in inactive timestamps.

Optional argument END defines the point at which to stop searching.
Optional argument ALL when non-nil specifies to look for any occurence
of STATE in the subtree, not just in the most recent entry."
  (let ((drawer (if all "" ":.*:\\W" "CLOSED:")))
    (or (re-search-forward (concat drawer ".*State \\\"" state "\\\"\\W+from.*\\[") end t)
        (re-search-forward (concat drawer ".*\\[") end t))))

(defun zin/org-date-diff (start end &optional compare)
  "Calculate difference between  selected timestamp to current date.

The difference between the dates is calculated in days.
START and END define the region within which the timestamp is found.
Optional argument COMPARE allows for comparison to a specific date rather than to current date."
  (let* ((start-date (if compare compare (calendar-current-date))))
    (- (calendar-absolute-from-gregorian start-date) (org-time-string-to-absolute (buffer-substring-no-properties start end)))
    ))

In the org-mode configuration you can see references to files that are not part of this repository. The org-agenda-files and the org-capture-templates templates.

I use capture templates to create new notes / recipes on this website. Each page is a different org file and for recipes there's a common template.

There's also the org-agenda-custom-commands that uses the previously defined functions to refiles old DONE tasks.

(use-package org
  :bind (("C-c a" . org-agenda-list)
         ("C-c l" . org-store-link)
         ("C-c c" . org-capture))
  :hook
  (org-mode . org-indent-mode)
  (org-mode . visual-line-mode)
  :config
  (setq org-html-doctype "html5" ;; HTML export

        ;; Visual tweaks
        org-hide-emphasis-markers t
        org-ellipsis " ▼ "

        ;; Startup
        org-startup-with-inline-images t
        org-startup-folded "showeverything"
        org-startup-indented t

        ;; Babel
        org-babel-min-lines-for-block-output 1
        org-confirm-babel-evaluate nil
        org-src-preserve-indentation t

        ;; Fontify
        org-fontify-done-headline t
        org-src-fontify-natively t

        ;; Agenda
        org-agenda-start-with-log-mode t
        org-log-done 'time
        org-agenda-skip-scheduled-if-done t
        org-log-into-drawer t
        org-agenda-files '("~/org/work.org" "~/org/keep.org" "~/org/house.org" "~/org/perso.org")

        org-agenda-custom-commands '(("R" "Tasks that were completed more than 14 days ago." tags "-REFILE/"
               ((org-agenda-files '("~/org/work.org" "~/org/perso.org" "~/org/house.org"))
                (org-agenda-overriding-header "Archivable tasks")
                (org-agenda-skip-function '(zin/since-state 14 "\\\(DONE\\\|CANCELED\\\)" t)))))



        ;; Refiling
        org-refile-targets '(("~/org/archives.org" :maxlevel . 1)))

  (advice-add 'org-refile :after 'org-save-all-org-buffers)

  (setq org-todo-keywords
        '((sequence "TODO(t!)" "|" "DONE(d!)")))

  (setq org-capture-templates
        '(("s" "Site Entries")
          ("sr" "Recipe" plain
           (file (lambda () (expand-file-name (read-string "Filename: ") "~/dev/volnt.github.io/recipes/")))
           (file "~/org/templates/recipe.org"))
          ("sn" "Note" plain
           (file (lambda () (expand-file-name (read-string "Filename: ") "~/dev/volnt.github.io/notes/"))))
          ("t" "Todo Entries")
          ("tt" "Todo" entry (file "~/org/perso.org")
           "* TODO %?\n%U\n%i" :empty-lines 1)
          ("th" "House entry." entry (file "~/org/house.org")
           "* TODO %? :house:\n%U\n%i" :empty-lines 1)
          ("tw" "Work entry." entry (file "~/org/work.org")
           "* TODO %? :work:\n%U\n%i" :empty-lines 1)
          ("m" "Meeting Entries")
          ("mm" "Meeting" entry (file "~/org/perso.org")
           "* %? :meeting:\n%U\n%i" :clock-in :clock-resume :empty-lines 1)
          ("mw" "Work Meeting" entry (file "~/org/work.org")
           "* %? :work:meeting:\n%U\n%i" :clock-in :clock-resume :empty-lines 1)
          ("mo" "One-to-One" entry (file+headline "~/org/work.org" "One-to-one")
           "* TODO %? :work:meeting:\n%U\n%i" :clock-in :clock-resume :empty-lines 1)
          ("mt" "Tech Screening" entry (file "~/org/work.org")
           (file "~/org/templates/tech-screening.org") :clock-in :clock-resume :empty-lines 1)
          ("ml" "ML Interview" entry (file "~/org/work.org")
           (file "~/org/templates/ml-engineer-screening.org") :clock-in :clock-resume :empty-lines 1)))

  (org-babel-do-load-languages
   'org-babel-load-languages '((python . t) (shell . t) (C . t) (gnuplot . t)))
  (use-package org-superstar
    :hook (org-mode . org-superstar-mode)
    :custom
    (org-superstar-remove-leading-stars t)
    (org-superstar-headline-bullets-list '("◉" "○" "●" "○" "●" "○" "●")))
  (use-package epresent)
  (use-package ox-jira)
  (straight-use-package '(org-contrib :includes org-checklist))
  (load "org-checklist"))

Install major modes

A few major modes I use.

;; Major modes

(use-package ledger-mode)
(use-package gnuplot
  :config
  (use-package gnuplot-mode))
(use-package lua-mode)
(use-package typescript-mode)
(use-package yaml-mode)
(use-package terraform-mode)
(use-package markdown-mode)

Theme

Setup the theme (color-theme + modeline).

;; Theme

(use-package solarized-theme
  :config (load-theme 'solarized-selenized-dark))

(use-package doom-modeline
  :init (doom-modeline-mode 1)
  :config
  (use-package all-the-icons)) ;; eval-expression (all-the-icons-install-fonts) on first run

UI

All UI related packages.

Use vertico for minibuffer completion with marginalia for added details.

For code-completion use company.

;; UI

(use-package unicode-fonts
  :config
  (unicode-fonts-setup)
  (use-package font-utils)
  (use-package ucs-utils))

(use-package flycheck
  :config (global-flycheck-mode))

(use-package vertico
  :init
  (vertico-mode)

  ;; Different scroll margin
  ;; (setq vertico-scroll-margin 0)

  ;; Show more candidates
  ;; (setq vertico-count 20)

  ;; Grow and shrink the Vertico minibuffer
  ;; (setq vertico-resize t)

  ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
  ;; (setq vertico-cycle t)
  )

;; Optionally use the `orderless' completion style. See
;; `+orderless-dispatch' in the Consult wiki for an advanced Orderless style
;; dispatcher. Additionally enable `partial-completion' for file path
;; expansion. `partial-completion' is important for wildcard support.
;; Multiple files can be opened at once with `find-file' if you enter a
;; wildcard. You may also give the `initials' completion style a try.
(use-package orderless
  :init
  ;; Configure a custom style dispatcher (see the Consult wiki)
  ;; (setq orderless-style-dispatchers '(+orderless-dispatch)
  ;;       orderless-component-separator #'orderless-escapable-split-on-space)
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

(use-package marginalia
  :config
  (marginalia-mode))

(use-package company
  :config
  (setq company-global-modes '(not shell-mode))
  (global-company-mode t)
  :bind (:map company-active-map ("<tab>" . company-complete-selection))
  :custom
  (company-minimum-prefix-length 1)
  (company-idle-delay 0.0))

(use-package highlight-indentation
  :straight (highlight-identation :type git :host github :repo "antonj/Highlight-Indentation-for-Emacs")
  :hook (prog-mode . highlight-indentation-mode)
  :config (setq highlight-indentation-blank-lines t))

(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

(use-package hl-line
  :config (global-hl-line-mode))

(use-package git-gutter
  :config (global-git-gutter-mode t))

(use-package helpful)

Utilities

Here are all utilities with no effect on UI.

;; Utilities

(use-package magit
  :bind (("C-c s" . magit-status)
         ("C-c b" . magit-blame)
         ("C-c g" . vc-git-grep)))

(use-package undo-tree
  :config (global-undo-tree-mode))

(use-package multiple-cursors
  :bind (("C-c m" . mc/mark-all-in-region)
         ("C-c n" . mc/mark-next-like-this)))

(use-package projectile
  :config (projectile-mode)
  :bind-keymap ("C-c p" . projectile-command-map))

(use-package autorevert)

(use-package which-key
  :config
  (which-key-mode))

(use-package eldoc)

(use-package realgud
  :config (load-library "realgud"))

(use-package yasnippet
  :config
  (yas-global-mode t)
  (use-package yasnippet-snippets))

(use-package gazr
  :straight (gazr :type git :host github :repo "volnt/gazr.el")
  :bind (("C-c C-g" . gazr)))

Vterm

Emacs-libvterm (vterm) is fully-fledged terminal emulator inside GNU Emacs based on libvterm, a C library. As a result of using compiled code (instead of elisp), emacs-libvterm is fully capable, fast, and it can seamlessly handle large outputs.

(use-package vterm
  :config
  (setq vterm-max-scrollback 100000))

Python setup

Because Python is the language I use the most, I use more packages than just the major-mode.

blacken is used for code formatting, and py-isort for imports ordering.

lsp-pyright is used for code completion, flycheck warnings and find-definitions / find-references.

;; Python

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :config
  (lsp-enable-which-key-integration t)
  (use-package lsp-ui)
  (use-package lsp-treemacs))

(use-package py-isort
  :custom (py-isort-options '("-w 120"))
  :hook (before-save . py-isort-before-save))

(use-package blacken
  :hook (python-mode . blacken-mode)
  :custom (blacken-line-length 120))

(use-package lsp-pyright
  :hook (python-mode . (lambda ()
                         (require 'lsp-pyright)
                         (lsp)))  ; or lsp-deferred
  :init (setq lsp-pyright-python-executable-cmd "python3.8")
  :config (setq lsp-pyright-disable-organize-imports t)
  :bind-keymap ("C-c C-o" . lsp-command-map)
  :bind (("C-c ;" . xref-find-definitions)
         ("C-c ," . xref-pop-marker-stack)
         ("C-c :" . lsp-find-references)))

Date: 2022-03-28 Mon 13:38

Emacs 28.1 (Org mode 9.5.2)