nixos-config/home-manager/modules/emacs/config.org

1947 lines
58 KiB
Org Mode

#+TITLE: Emacs config
#+AUTHOR: Erwin Boskma
#+OPTIONS: toc:nil h:4
#+STARTUP: overview
#+PROPERTY: header-args:emacs-lisp :tangle init.el
#+PROPERTY: header-args:emacs-lisp+ :noweb tangle
This is my emacs configuration. It is structured as an org-mode file, to easily provide context and documentation.
Last export: {{{modification-time(%Y-%m-%d %H:%M)}}}
#+TOC: headlines 4
* Setup
** Early init
This ends up in =early-init.el=.
#+begin_src emacs-lisp :tangle early-init.el
;; -*- lexical-binding: t; -*-
;; Prevent package.el from loading packages until we're ready for it
(setq package-enable-at-startup nil)
;; Don't resize the frame to preserve the number of columns or lines
;; being displayed when setting font, menu bar, tool bar, tab bar,
;; internal borders, fringes, or scroll bars. Since I use a tiling
;; window manager, this option is 1. useless anyways and 2. _terribly_ expensive.
(setq frame-inhibit-implied-resize t)
;; Suppress warnings and errors during asynchronous native compilation
(setq native-comp-async-report-warnings-errors nil)
;; Prevent unwanted runtime builds; packages are compiled ahead-of-time when
;; they are installed and site files are compiled when gccemacs is installed.
;; (setq comp-deferred-compilation nil)
(setq native-comp-deferred-compilation nil)
;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are
;; compiled ahead-of-time when they are installed and site files are compiled
;; when gccemacs is installed.
(setq comp-deferred-compilation nil)
;; This should fix input lag by ~80% when using PGTK
(setq-default pgtk-wait-for-event-timeout 0)
#+end_src
** Enable lexical binding
Setting =lexical-binding= to =t= can improve startup time. This has to be first!
#+begin_src emacs-lisp
;; -*- lexical-binding: t; -*-
#+end_src
** Personal variables
This sets some variables with my personal preferences for easy customization
#+begin_src emacs-lisp
(defvar my/default-font "Monaspace Neon")
(defvar my/variable-width-font "Iosevka Aile")
(defvar my/comment-font "Monaspace Radon")
(defvar my/default-font-height 120)
(defvar my/default-font-weight 'light)
(defvar my/default-font-width 'normal)
(defvar my/variable-width-font-height 1.1)
(defvar my/config-file-path (expand-file-name "config.org" user-emacs-directory))
(defvar my/snippets-dir (expand-file-name "snippets" user-emacs-directory))
(defvar my/local-init-file (expand-file-name "local-init.el" user-emacs-directory))
(defvar my/leader "C-c")
#+end_src
And my personal info.
#+begin_src emacs-lisp
(setq user-full-name "Erwin Boskma"
user-mail-address "erwin@datarift.nl")
#+end_src
** Garbage collector
Increasing the garbage collector threshold should also improve startup time. This increases it from 800 kB to 128MB
#+begin_src emacs-lisp
(setq gc-cons-threshold (* 128 1024 1024))
#+end_src
This resets the threshold back to it's default. This should not be done if =lsp-mode= is enabled, it needs the higher threshold.
#+begin_src emacs-lisp :tangle no
(add-hook 'after-init-hook
(lambda ()
(setq gc-cons-threshold 800000)
(message "gc-cons-threshold restored to %s"
gc-cons-threshold)))
#+end_src
** Increase process output buffer
LSP responses can be rather large, in the 800KiB - 3MiB range. 2MiB is a decent value
#+begin_src emacs-lisp
(setq read-process-output-max (* 2 1024 1024))
#+end_src
** Package sources
Add repositories where packages are installed from.
#+begin_src emacs-lisp
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")
("onpa" . "https://olanilsson.bitbucket.io/packages/")))
#+end_src
** Bootstrap use-package
If =use-package= is not installed, install it.
*NOTE*: Disabled because it is builtin since emacs 29
#+begin_src emacs-lisp :tangle no
(require 'package)
(package-initialize)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package)
(cl-eval-when 'compile (require 'use-package)))
#+end_src
By default packages should always be installed from the package manager. This is equivalent to setting =:ensure t= on each call to =use-package=. To override this for a package (e.g. because it is builtin, or a subpackage), use =:ensure nil=. This is done automatically for packages that use =:load-path=.
#+begin_src emacs-lisp
(setq use-package-always-ensure t)
#+end_src
** Bootstrap straight.el and use-package [DISABLED]
[[https://github.com/radian-software/straight.el][straight.el]] is a pure functional package manager and installs packages from git instead of downloading tars
#+begin_src emacs-lisp :tangle no
(defvar bootstrap-version)
;; Workaround for flycheck. See https://github.com/radian-software/straight.el/issues/508 for more info
(setq straight-fix-flycheck t)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 6))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
#+end_src
Install =use-package= and make it use =straight.el= by default.
#+begin_src emacs-lisp :tangle no
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
#+end_src
** general.el
[[https://github.com/noctuid/general.el][general.el]] provides a more convenient way for binding keys in emacs. It also integrates with =use-package= with the =:general= keyword.
#+begin_src emacs-lisp
(use-package general
:demand t
:config
(progn
(general-define-key
:prefix my/leader
"a" 'org-agenda
"k" 'general-describe-keybindings)
(general-define-key
"C-M-z" 'zap-to-char
"M-z" 'zap-up-to-char)))
#+end_src
** Setup asdf [DISABLED]
[[https://asdf-vm.com][asdf]] is a tool to install and use multiple versions of development tools and programming languages.
#+begin_src emacs-lisp :tangle no
(when (executable-find "asdf")
(use-package asdf-vm
:straight (:host github :repo "delonnewman/asdf-vm.el")
;; :load-path "~/.config/emacs/elisp/asdf-vm.el/"
:config
(asdf-vm-init)))
#+end_src
** Set custom settings to load in temp file [DISABLED]
Setting =custom-file= stops emacs from adding customised settings to =init.el=. I prefer to specify everything in this file, so this creates a temporary file where the customisations are stored. This effectively localises customisations to a session
#+begin_src emacs-lisp :tangle no
(setq custom-file (make-temp-file "emacs-custom"))
#+end_src
** Disable the customize interface
The =customize= functionality is annoying and messes up regularly. Stuff it has done so far:
- Clobber the path to =mix= in Elixir projects
This has been borrowed from [[https://github.com/doomemacs/doomemacs/blob/35865ef5e89442e3809b8095199977053dd4210f/core/core-ui.el#L628-L639][Doom Emacs]]
#+begin_src emacs-lisp
(dolist (sym '(customize-option customize-browse customize-group customize-face
customize-rogue customize-saved customize-apropos
customize-changed customize-unsaved customize-variable
customize-set-value customize-customized customize-set-variable
customize-apropos-faces customize-save-variable
customize-apropos-groups customize-apropos-options
customize-changed-options customize-save-customized))
(put sym 'disabled (concat "This config doesn't support `customize', configure Emacs from " user-emacs-directory "/config.org instead")))
(put 'customize-themes 'disabled (concat "Use `load-theme' in " user-emacs-directory "/config.org instead"))
#+end_src
** Custom packages location
Emacs only includes files directly under =user-emacs-directory=. I like to keep custom packages in a separate =elisp/= subdirectory.
#+begin_src emacs-lisp
(add-to-list 'load-path (expand-file-name "elisp/" user-emacs-directory))
#+end_src
** Record key frequency
Records what keys are used the most, so I can see if I can optimise shortcuts
#+begin_src emacs-lisp
(use-package keyfreq
:config
(keyfreq-mode 1)
(keyfreq-autosave-mode 1))
#+end_src
** Save minibuffer history
#+begin_src emacs-lisp
(use-package savehist
:ensure nil
:init
(savehist-mode))
#+end_src
* Preferences
Don't display the help screen at startup
#+begin_src emacs-lisp
(setq inhibit-startup-screen t)
#+end_src
Enable line wrapping
#+begin_src emacs-lisp
(global-visual-line-mode 1)
#+end_src
Disable toolbar and scroll bar
#+begin_src emacs-lisp
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
#+end_src
Don't show the warnings buffer for anything lesser than an error.
#+begin_src emacs-lisp
(setq warning-minimum-level :error)
#+end_src
Keep buffers up-to-date automatically
#+begin_src emacs-lisp
(global-auto-revert-mode t)
#+end_src
Automatically scroll the compile buffer
#+begin_src emacs-lisp
(setq compilation-scroll-output 'first-error)
#+end_src
Always display line numbers, and the relative line number
#+begin_src emacs-lisp
(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode)
#+end_src
Show matching parenthesese
#+begin_src emacs-lisp
(show-paren-mode 1)
(setq show-paren-style 'expression)
#+end_src
Surround marked text with (), [] or {}
#+begin_src emacs-lisp
(electric-pair-mode 1)
#+end_src
Centralise backup files (the =filename~= files), so they don't pollute the project.
#+begin_src emacs-lisp
(setq backup-directory-alist '((".*" . "~/.local/share/emacs/backup"))
backup-by-copying t ; Don't delink hardlinks
version-control t ; Use version numbers on backup files
delete-old-versions t ; Automatically delete obsolete backups
kept-new-versions 20 ; How many of the newest versions to keep
kept-old-versions 5 ; And how many of the oldest
)
#+end_src
Same for auto save files (the ones with the =#=)
#+begin_src emacs-lisp
(setq auto-save-file-name-transforms '((".*" "~/.local/share/emacs/autosave/" t)))
#+end_src
Default indenting to spaces. If tabs are necessary, this can be set buffer-local. A single tab character can be added using =C-q TAB=
#+begin_src emacs-lisp
(setq-default indent-tabs-mode nil)
#+end_src
Show trailing whitespace
#+begin_src emacs-lisp
(setq show-trailing-whitespace t)
#+end_src
Delete trailing whitespace on save.
#+begin_src emacs-lisp
(add-hook 'before-save-hook 'delete-trailing-whitespace)
#+end_src
Sentences end in one space, not two. We're not using typewriters.
#+begin_src emacs-lisp
(setq sentence-end-double-space nil)
#+end_src
Don't move files to trash when deleting.
#+begin_src emacs-lisp
(setq delete-by-moving-to-trash nil)
#+end_src
Restore cursor position when re-opening a file
#+begin_src emacs-lisp
(save-place-mode t)
#+end_src
Prefer to open frames in a horizontal split and make sure they're of a decent width
#+begin_src emacs-lisp
(setq split-height-threshold nil
window-min-width 100)
#+end_src
Set fill column to 80
#+begin_src emacs-lisp
(setq-default fill-column 80)
#+end_src
Kill whole lines instead of clearing them
#+begin_src emacs-lisp
(setq kill-whole-line t)
#+end_src
* Interface
** Easy edit config file
Disabled because the configuration is handled by Nix using [[https://github.com/nix-community/emacs-overlay][emacs-overlay]]
#+begin_src emacs-lisp :tangle no
(defun find-config ()
"Edit config.org"
(interactive)
(find-file my/config-file-path))
(general-define-key
:prefix my/leader
"C" 'find-config)
#+end_src
** Appearance
Enable pixel scrolling.
#+begin_src emacs-lisp
(setq pixel-scroll-precision-mode t)
#+end_src
I prefer the [[https://draculatheme.com][dracula theme]]
#+begin_src emacs-lisp
(use-package dracula-theme
:init
(load-theme 'dracula t))
#+end_src
[[https://github.com/belak/emacs-monokai-pro-theme][Monokai Pro]] is also pretty.
#+begin_src emacs-lisp :tangle no
(use-package monokai-pro-theme
:init
(load-theme 'monokai-pro t))
#+end_src
Set fonts.
#+begin_src emacs-lisp
(defun my/set-font-size (&optional frame)
(let* ((frame (or frame (selected-frame)))
(geometry (frame-monitor-attribute 'geometry frame))
(mm-size (frame-monitor-attribute 'mm-size frame))
(width-px (caddr geometry))
(width-mm (car mm-size))
(width-in (/ width-mm 25.4))
(display-dpi (/ width-px width-in))
(font-height (cond
((< display-dpi 110) 120)
((< display-dpi 130) 140)
((< display-dpi 160) 160)
(t 160))))
(set-face-attribute 'default frame :height font-height)))
(add-hook 'server-after-make-frame-hook 'my/set-font-size)
(add-hook 'after-make-frame-functions 'my/set-font-size)
(defun my/setup-fonts ()
(set-face-attribute 'default nil :family my/default-font :weight 'light)
(set-face-attribute 'font-lock-comment-face nil :font my/comment-font)
(set-face-attribute 'variable-pitch nil
:font my/variable-width-font
:height my/variable-width-font-height))
(add-hook 'after-init-hook 'my/setup-fonts)
#+end_src
Emoji support
#+begin_src emacs-lisp
(defun set-emoji-font ()
(when (member "Twitter Color Emoji" (font-family-list))
(set-fontset-font t 'emoji (font-spec :family "Twitter Color Emoji") nil 'prepend)))
(use-package emojify
:defer t
:init
(setq emojify-display-style 'unicode)
:config
(set-emoji-font)
:hook (server-after-make-frame . set-emoji-font))
#+end_src
Enable ligatures
#+begin_src emacs-lisp
(use-package ligature
:config
;; Enable all Iosevka ligatures in programming modes
;; (ligature-set-ligatures 'prog-mode '("<---" "<--" "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
;; "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
;; "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
;; ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
;; Ligatures for Monaspace
(ligature-set-ligatures 'prog-mode '(
; ss01
"==" "===" "=/=" "!=" "!==" "/=" "/==" "~~" "=~" "!~"
; ss02
">=" "<="
; ss03
"->" "<-" "=>" "<!--" "-->" "<~" "<~~" "~>" "~~>" "<~>"
; ss04
"</" "/>" "</>" "/\\" "\\/"
; ss05
"|>" "<|"
; ss06
"##" "###"
; ss07
"***" "/*" "*/" "/*/" "(*" "*)" "(*)"
; ss08
".=" ".-" "..<"
; dlig & calt
"<!" "**" "::" "=:" "=!" "=/" "--" ".." "//" "&&" "||" ":=" ":>" ":<" "!!" ">:" "<:" "#=" "?:" "?." "??" ";;" "///" ":::" "..." "=!=" "=:=" "..=" "..-"))
;; Enables ligature checks globally in all buffers. You can also do it
;; per mode with `ligature-mode'.
(global-ligature-mode t))
#+end_src
Add a dashboard on startup
#+begin_src emacs-lisp
(use-package dashboard
:after all-the-icons
:hook (dashboard-mode . (lambda ()
(setq show-trailing-whitespace nil)))
:config
(setq dashboard-set-navigator t
dashboard-center-content t
dashboard-set-file-icons t
dashboard-set-heading-icons t
dashboard-set-init-info t
dashboard-image-banner-max-height 250
dashboard-banner-logo-title "Bûter, brea en griene tsiis, wa dat net sizze kin is gjin oprjochte Fries."
dashboard-startup-banner (concat user-emacs-directory "images/ue-colorful.png")
dashboard-footer-icon (all-the-icons-octicon "dashboard"
:height 1.1
:v-adjust -0.05
:face 'font-lock-keyword-face))
(setq dashboard-navigator-buttons
`(((,(all-the-icons-octicon "search" :height 0.9 :v-adjust -0.1)
" Find file" nil
(lambda (&rest _) (find-file)) nil "" " C-x C-f"))
((,(all-the-icons-octicon "file-directory" :height 1.0 :v-adjust -0.1)
" Open project" nil
(lambda (&rest _) (projectile-switch-project)) nil "" " C-c p p"))
((,(all-the-icons-octicon "three-bars" :height 1.1 :v-adjust -0.1)
" File explorer" nil
(lambda (&rest _) (projectile-dired)) nil "" " C-c p D"))
((,(all-the-icons-octicon "settings" :height 0.9 :v-adjust -0.1)
" Open settings" nil
(lambda (&rest _) (find-config)) nil "" " C-c C "))))
(setq
dashboard-projects-backend 'projectile
dashboard-items '((recents . 5)
(projects . 5)
(registers . 5)))
(dashboard-setup-startup-hook)
;; Also show dashboard on new emacsclient window
(setq initial-buffer-choice (lambda ()
(get-buffer-create "*dashboard*")))
:custom-face
(dashboard-heading ((t (:weight bold)))))
#+end_src
** Modeline
[[https://github.com/myrjola/diminish.el][diminish]] hides modes from the modeline
#+begin_src emacs-lisp
(use-package diminish)
#+end_src
[[https://github.com/domtronn/all-the-icons.el][all-the-icons]] provides ALL the icons. Run `all-the-icons-install-fonts` after installing to download the actual font.
#+begin_src emacs-lisp
(use-package all-the-icons
:if (display-graphic-p))
#+end_src
[[https://github.com/tarsius/minions][minions]] adds a menu for minor modes to the modeline
#+begin_src emacs-lisp
(use-package minions
:config
(minions-mode 1))
#+end_src
Use [[https://github.com/seagle0128/doom-modeline][doom-modeline]] for a nice and fancy modeline
*2023-05-12* Disabled because it causes emacs to hang
#+begin_src emacs-lisp :tangle no
(use-package doom-modeline
:init
(doom-modeline-mode 1)
:config
(setq doom-modeline-height 25
doom-modeline-bar-width 6
doom-modeline-lsp t
doom-modeline-github nil
doom-modeline-mu4e nil
doom-modeline-irc nil
doom-modeline-minor-modes t
doom-modeline-persp-name nil
doom-modeline-buffer-file-name-style 'truncate-except-project
doom-modeline-icon t)
:custom-face
(mode-line ((t (:height 0.85))))
(mode-line-inactive ((t (:height 0.85)))))
#+end_src
[[https://github.com/tarsius/moody][moody]] cleans up the modeline a bit so it is nicer to look at
#+begin_src emacs-lisp
(use-package moody
:config
(setq x-underline-at-descent-line t)
(moody-replace-mode-line-buffer-identification)
(moody-replace-vc-mode)
(moody-replace-eldoc-minibuffer-message-function))
#+end_src
** Command completion
*** Ivy / Counsel
*Disabled in favor of [[https://github.com/minad/vertico][vertico]]*
Ivy is a generic completion framework which uses the minibuffer.
[[https://github.com/abo-abo/swiper][ivy GitHub page]]
#+begin_src emacs-lisp :tangle no
(use-package ivy
:diminish t
:bind
<<ivy-binds>>
:config
<<ivy-config>>
)
#+end_src
Ivy config
#+name: ivy-config
#+begin_src emacs-lisp :tangle no
(ivy-mode t)
(setq ivy-initial-inputs-alist nil
ivy-use-virtual-buffers nil)
(define-key read-expression-map (kbd "C-r") 'counsel-expression-history)
#+end_src
Ivy keybindings
#+name: ivy-binds
#+begin_src emacs-lisp :tangle no
("C-x s" . swiper)
("C-x C-r" . ivy-resume)
#+end_src
Counsel enhances emacs commands with ivy.
[[https://github.com/abo-abo/swiper][counsel GitHub page]]
#+begin_src emacs-lisp :tangle no
(use-package counsel
:bind
("M-x" . counsel-M-x)
("C-x C-m" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-x c k" . counsel-yank-pop))
#+end_src
Company provides completion in the main buffer.
[[https://company-mode.github.io/][company website]]
#+begin_src emacs-lisp :tangle no
(use-package company
:diminish t
:hook
(after-init . global-company-mode))
#+end_src
Hydra allows you to group commands behind a custom prefix.
[[https://github.com/abo-abo/hydra][hydra GitHub page]]
#+begin_src emacs-lisp :tangle no
(use-package ivy-hydra)
#+end_src
=major-mode-hydra= binds a single key to open a context sensitive hydra based on the current major mode. Hydras can be defined in =use-package= definitions via the =:mode-hydra= integration.
#+begin_src emacs-lisp :tangle no
(use-package major-mode-hydra
:bind
("C-M-SPC" . major-mode-hydra)
:config
(major-mode-hydra-define org-mode
()
("Tools"
(("l" org-lint "lint")))))
#+end_src
*** Vertico / Corfu
[[https://github.com/minad/vertico][vertico]] is a minimalistic completion UI based on the default completion system integrated in emacs.
#+begin_src emacs-lisp
(use-package vertico
:custom
(vertico-cycle t)
:init
(vertico-mode))
#+end_src
[[https://github.com/minad/marginalia][marginalia]] adds extra information to the minibuffer completions.
#+begin_src emacs-lisp
(use-package marginalia
:after vertico
:custom
(marginalia-annotaters '(marginalia-annotaters-heavy marginalia-annotaters-light nil))
:init
(marginalia-mode))
#+end_src
[[https://github.com/minad/consult][consult]] is an alternative to counsel for ivy, but for vertico.
#+begin_src emacs-lisp
(use-package consult
:general
("C-s" 'consult-line)
("C-x c k" 'consult-yank-pop)
:config
(setq consult-project-function (lambda ()
(when (fboundp 'projectile-project-root)
(projectile-project-root)))))
#+end_src
[[https://github.com/minad/corfu][corfu]] enhances the completion at point function popups
#+begin_src emacs-lisp
(use-package corfu
;; :bind
;; ("TAB" . corfu-insert)
:custom
(corfu-cycle t)
(corfu-auto t)
:init
(global-corfu-mode))
#+end_src
[[https://github.com/oantolin/orderless][orderless]] is a flexible completion style that allows flexible matching using literal strings, regex and more.
#+begin_src emacs-lisp
(use-package orderless
:init
(setq completion-styles '(orderless partial-completion basic)
;; completion-category-defaults nil
completion-category-overrides '((file (styles basic partial-completion)))))
#+end_src
Use =corfu= with =dabbrev= (included with emacs)
#+begin_src emacs-lisp
(use-package dabbrev
:ensure nil
:general
("M-/" 'dabbrev-completion)
("C-M-/" 'dabbrev-expand)
:custom
(dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))
#+end_src
*** Misc
which-key shows suggestions when you type an incomplete command and wait for a bit (1 second by default).
[[https://github.com/justbur/emacs-which-key][which-key GitHub page]]
#+begin_src emacs-lisp
(use-package which-key
:defer 0
:diminish which-key-mode
:config
(which-key-mode)
(setq which-key-idle-delay 1))
#+end_src
*** Possible candidates
The following packages look interesting, and are worth investigating further:
- [[https://github.com/abo-abo/avy][avy]]: quick text navigation
- [[https://github.com/abo-abo/ace-window][ace-window]]: window navigation
- [[https://github.com/magnars/expang-region.el][expand-region]]: context aware text selection
- [[https://github.com/minad/cape][cape]]: A set of completion at point extensions
** File tree
[[https://github.com/Alexander-Miller/treemacs][treemacs]] is Emacs' equivalent of neovim's NeoTree or vim's NERDTree.
#+begin_src emacs-lisp
(use-package treemacs
:defer t
:init
(with-eval-after-load 'winum
(define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
:general
("M-0" 'treemacs-select-window)
(:prefix "C-c"
"t 1" 'treemacs-delete-other-windows
"t t" 'treemacs
"t d" 'treemacs-select-directory
"t B" 'treemacs-bookmark
"t C-t" 'treemacs-find-file
"t M-t" 'treemacs-find-tag)
:config
(progn
(treemacs-project-follow-mode)))
#+end_src
Enable integration with =projectile=
#+begin_src emacs-lisp
(use-package treemacs-projectile
:after (treemacs projectile))
#+end_src
Add icons
#+begin_src emacs-lisp
(use-package treemacs-icons-dired
:hook (dired-mode . treemacs-icons-dired-enable-once))
#+end_src
Enable =magit= integration
#+begin_src emacs-lisp
(use-package treemacs-magit
:after (treemacs magit))
#+end_src
* Programming
** General settings
Auto balance parenthesese. *Note:* Disabled in favour of =electric-pair-mode=
#+begin_src emacs-lisp :tangle no
(use-package smartparens
:ghook 'prog-mode-hook)
#+end_src
Rainbow delimiters FTW!
#+begin_src emacs-lisp
(use-package rainbow-delimiters
:ghook 'prog-mode-hook)
#+END_src
Paredit is a minor mode for editing parentheses
#+begin_src emacs-lisp
(use-package paredit
:hook (emacs-lisp-mode . paredit-mode)
(racket-mode . paredit-mode)
(racket-repl-mode . paredit-mode)
:general
(:keymaps 'paredit-mode-map
"{" 'paredit-open-curly
"}" 'paredit-close-curly
"M-[" 'paredit-wrap-square
"M-{" 'paredit-wrap-curly))
#+end_src
** Project management
Projectile works great to manage projects. It includes fuzzy search and supports jumping between files (e.g. .h <-> .cpp).
[[https://github.com/bbatsov/projectile][projectile GitHub page]]
#+begin_src emacs-lisp
(use-package projectile
:diminish projectile-mode
:config
;; (setq projectile-completion-system 'ivy)
(projectile-mode)
:general
(:prefix "C-c"
"p" '(:keymap projectile-command-map))
:init
(setq projectile-switch-project-action #'projectile-dired
projectile-project-search-path '("~/workspace" "~/workspace/horus" "~/workspace/horus/web" "~/workspace/horus/horus-vr")))
#+end_src
There is also an integration with counsel.
#+begin_src emacs-lisp :tangle no
(use-package counsel-projectile
:after projectile
:general
("C-SPC" 'counsel-projectile-switch-project)
:config
(counsel-projectile-mode))
#+end_src
Enable [[https://github.com/BurntSushi/ripgrep][ripgrep]] support
#+begin_src emacs-lisp
(use-package rg
:after projectile
:config
(rg-enable-default-bindings))
#+end_src
** Git
Magit. Obviously.
[[https://magit.vc][magit website]]
#+begin_src emacs-lisp
(use-package magit
:config
;; (setq magit-completing-read-function 'ivy-completing-read)
:general
(:prefix my/leader
"g s" 'magit-status
"g x" 'magit-checkout
"g c" 'magit-commit
"g p" 'magit-push
"g u" 'magit-pull
"g e" 'magit-ediff-resolve
"g r" 'magit-rebase-interactive
"g i" 'magit-init))
#+end_src
transient can be used to create command dispatchers. Magit uses it to easily add options to git commands, and it displays a nice popup with the possible flags and commands.
[[https://magit.vc/manual/transient/][transient manual]]
#+begin_src emacs-lisp
(use-package transient)
#+end_src
Visualise git changes in the gutter, next to the line numbers
[[https://github.com/emacsorphanage/git-gutter][git-gutter GitHub page]]
#+begin_src emacs-lisp
(use-package git-gutter
:diminish t
:ghook
'prog-mode-hook
'org-mode-hook
:config
(setq git-gutter:update-interval 0.02))
(use-package git-gutter-fringe
:config
(define-fringe-bitmap 'git-gutter-fr:added [224] nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:modified [224] nil nil '(center repeated))
(define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240] nil nil 'bottom))
#+end_src
Show inline git-blame with [[https://github.com/Artawower/blamer.el][blamer.el]]. Inspired by VS Code's Git Lens
#+begin_src emacs-lisp
(use-package blamer
:after bind-key
:bind (("C-c C-i" . blamer-show-commit-info)
("C-c i" . blamer-show-posframe-commit-info))
:config
(global-blamer-mode 1))
#+end_src
** Syntax checking and highlighting
*** Flycheck
=Flycheck= is a general purpose syntax highlighting framework that provides hooks for other packages and an improvement of the builtin =flymake=.
[[https://www.flycheck.org][website]]
#+begin_src emacs-lisp
(use-package flycheck
:diminish t
:init
(global-flycheck-mode))
#+end_src
*** Tree-sitter
[[https://tree-sitter.github.io/][tree-sitter]] is a new development in parsing and syntax highlighting. It has been merged into Emacs 29, but until that's released we're using the [[https://emacs-tree-sitter.github.io/][emacs-tree-sitter]] package while on Emacs 28.
#+begin_src emacs-lisp
(when (< emacs-major-version 29)
(use-package tree-sitter
:config
(global-tree-sitter-mode)
:ghook
('tree-sitter-after-on-hook #'tree-sitter-hl-mode)))
#+end_src
[[https://github.com/emacs-tree-sitter/tree-sitter-langs][tree-sitter-langs]] provides =tree-sitter= support for a bunch of languages.
#+begin_src emacs-lisp
(use-package tree-sitter-langs
:after tree-sitter)
#+end_src
Automatically use the =<lang>-ts-mode= when it is available
#+begin_src emacs-lisp
(use-package treesit-auto
:config
(setq treesit-auto-install 'prompt)
(global-treesit-auto-mode))
#+end_src
*** lsp-mode
[[https://emacs-lsp.github.io/lsp-mode][lsp-mode]] adds Language Server Protocol support to emacs.
#+begin_src emacs-lisp :tangle no
(use-package lsp-mode
:init
(setq lsp-keymap-prefix "C-c l"
lsp-use-plists nil)
(defun eb/lsp-mode-setup-completion ()
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(orderless)))
:hook ((conf-toml-mode
python-mode
sh-mode) . lsp-deferred)
(lsp-mode . lsp-enable-which-key-integration)
(lsp-completion-mode . eb/lsp-mode-setup-completion)
:commands
(lsp lsp-deferred)
:custom
(lsp-completion-provider :none) ;; I'm using corfu
:config
(setq lsp-typescript-surveys-enabled nil
lsp-completion-enable t
lsp-enable-suggest-server-download nil))
#+end_src
[[https://emacs-lsp.github.io/lsp-ui/][lsp-ui]] provides higher level UI elements for =lsp-mode=, like code lenses and flycheck support.
#+begin_src emacs-lisp :tangle no
(use-package lsp-ui
:ghook 'lsp-mode-hook
:config
(setq lsp-ui-doc-enable t
lsp-ui-peek-enable t
lsp-ui-sideline-enable t
lsp-ui-imenu-enable t)
:custom
(lsp-ui-doc-position 'bottom))
#+end_src
[[https://github.com/emacs-lsp/lsp-ivy][lsp-ivy]] integrates =ivy= into =lsp-mode=
#+begin_src emacs-lisp :tangle no
(use-package lsp-ivy
:commands lsp-ivy-workspace-symbol)
#+end_src
[[https://github.com/gagbo/consult-lsp][consult-lsp]] integrates =consult= into =lsp-mode=
#+begin_src emacs-lisp :tangle no
(use-package consult-lsp)
#+end_src
[[https://github.com/emacs-lsp/lsp-treemacs][lsp-treemacs]] provides an integration between =lsp-mode= and [[https://github.com/Alexander-Miller/treemacs][treemacs]].
#+begin_src emacs-lisp :tangle no
(use-package lsp-treemacs
:commands lsp-treemacs-errors-list)
#+end_src
*** dap-mode
[[http://emacs-lsp.github.io/dap-mode][dap-mode]] provides debugging facilities using the [[https://microsoft.github.io/debug-adapter-protocol/][Debug Adapter Protocol]]
#+begin_src emacs-lisp :tangle no
(use-package dap-mode
:defer t
:custom
(dap-auto-configure-mode t)
(dap-auto-configure-features '(sessions locals breakpoints expressions tooltip))
:config
(require 'dap-lldb)
(require 'dap-cpptools)
(setq dap-lldb-debugged-program-function (lambda () (read-file-name "Select program executable to debug")))
(dap-register-debug-template "C++ LLDB"
(list :type "lldb-vscode"
:dap-server-path (executable-find "lldb-vscode")
:cwd nil
:args nil
:request "launch"
:program nil))
(defun dap-debug-create-or-edit-json-template ()
"Edit C++ debugging configuration or create and edit if none exists"
(interactive)
(let ((filename (concat (lsp-workspace-root) "/launch.json"))
(default (concat user-emacs-directory "/default-launch.json")))
(unless (file-exists-p filename)
(copy-file default filename))
(find-file-existing filename))))
#+end_src
*** eglot
[[https://joaotavora.github.io/eglot/][eglot]] is an alternative to =lsp-mode= that is builtin with emacs >= 29
#+begin_src emacs-lisp
(use-package eglot
:config
(fset #'json--log-event #'ignore) ;; Performance boost by not logging every event
(add-to-list 'eglot-server-programs
'(conf-toml-mode . ("taplo" "lsp" "stdio")))
(add-to-list 'eglot-server-programs
'((elixir-mode elixir-ts-mode heex-ts-mode) . ("elixir-ls")))
(add-to-list 'eglot-stay-out-of 'flymake)
(setq eglot-autoshutdown t
eldoc-echo-area-use-multiline-p 0.1)
:hook
(eglot-managed-mode . (lambda ()
(eglot-inlay-hints-mode 1)
(define-key eglot-mode-map (kbd "C-c l a") 'eglot-code-actions)
(define-key eglot-mode-map (kbd "C-c l f") 'eglot-format)
(define-key eglot-mode-map (kbd "C-c l h") 'eldoc)
(define-key eglot-mode-map (kbd "C-c l i") 'eglot-find-implementation)
(define-key eglot-mode-map (kbd "C-c l r") 'eglot-rename)
(define-key eglot-mode-map (kbd "C-c l t") 'eglot-find-typeDefinition)
(define-key eglot-mode-map (kbd "C-c l w d") 'eglot-list-connections)
(define-key eglot-mode-map (kbd "C-c l w r") 'eglot-reconnect)
(define-key eglot-mode-map (kbd "C-c l w q") 'eglot-shutdown)
(define-key eglot-mode-map (kbd "C-c l y") 'eglot-inlay-hints-mode))))
#+end_src
[[https://github.com/nemethf/eglot-x][eglot-x]] adds support for some LSP extensions to =eglot=
#+begin_src emacs-lisp :tangle no
(use-package eglot-x
:vc (:fetcher github :repo nemethf/eglot-x)
:after eglot
:config
(eglot-x-setup))
#+end_src
[[https://github.com/mohkale/consult-eglot][consult-eglot]] adds an integration between =consult= and =eglot=
#+begin_src emacs-lisp
(use-package consult-eglot)
#+end_src
** Snippets
Snippets are predefined pieces of code that can be inserted and filled in. [[https://github.com/joaotavora/yasnippet][YASnippet]] uses syntax inspired by TextMate is the most popular, for good reason.
#+begin_src emacs-lisp
(use-package yasnippet
:init
(load "yasnippet.el")
:diminish t
:general
(:keymaps 'yas-minor-mode-map
"<tab>" nil
"TAB" nil
"<C-tab>" 'yas-expand)
:config
(add-to-list 'yas-snippet-dirs my/snippets-dir)
(yas-global-mode))
(use-package yasnippet-snippets)
#+end_src
** Languages
*** JavaScript / TypeScript
Indent 2 spaces
#+begin_src emacs-lisp
(setq-default js-indent-level 2
typescript-indent-level 2)
#+end_src
[[https://github.com/mooz/js2-mode/][js2-mode]] improves a lot on the builtin =js-mode=
#+begin_src emacs-lisp
(use-package js2-mode
:after eglot
:mode
("\\.mjs\\'" . js2-mode)
("\\.jsx?\\'" . js2-jsx-mode)
:hook
(js2-mode . eglot-ensure)
(js2-jsx-mode . eglot-ensure))
#+end_src
Prettier has my preference for formatting JavaScript and TypeScript
#+begin_src emacs-lisp
(use-package prettier
:config
(setq prettier-enabled-parsers '
(css html json markdown scss svelte toml typescript vue)))
#+end_src
TypeScript stuff
#+begin_src emacs-lisp
(use-package typescript-mode
:after eglot
:mode
("\\.tsx?\\'" . typescript-mode)
:hook (typescript-mode . eglot-ensure))
#+end_src
Prefer local packages from =node_modules= to global ones
#+begin_src emacs-lisp
(use-package add-node-modules-path)
#+end_src
*** Web mode
[[https://web-mode.org/][web-mode]] handles HTML/CSS and JavaScript
#+begin_src emacs-lisp
(use-package web-mode
:after eglot
:config
(setq web-mode-markup-indent-offset 2
web-mode-css-indent-offset 2
web-mode-code-indent-offset 2
web-mode-enable-auto-pairing t
web-mode-enable-css-colorization t
web-mode-enable-current-element-highlight t
web-mode-enable-current-column-highlight t)
(add-to-list 'web-mode-engines-alist '(("elixir" . "\\.html.heex\\'")
("jinja2" . "\\.jinja2\\'")
("python" . "\\.pt\\'")))
:hook
((html-mode css-mode web-mode) . eglot-ensure))
#+end_src
*** Markdown
[[https://jblevins.org/projects/markdown-mode/][markdown-mode]] adds support for Markdown editing. =gfm-mode= supports GitHub Flavoured Markdown.
#+begin_src emacs-lisp
(use-package markdown-mode
:after eglot
:mode
(("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init
(setq markdown-command "multimarkdown")
:hook
(markdown-mode . display-fill-column-indicator-mode)
(markdown-mode . eglot-ensure))
#+end_src
[[https://github.com/skeeto/impatient-mode][impatient-mode]] live renders HTML, but it can be made to work with Markdown with a custom filter
#+begin_src emacs-lisp
(use-package impatient-mode
:after markdown-mode
:config
(imp-set-user-filter 'markdown-filter))
(defun markdown-filter (buffer)
(princ
(with-temp-buffer
(let ((tmpname (buffer-name)))
(set-buffer buffer)
(set-buffer (markdown tmpname)) ; the function markdown is in `markdown-mode.el'
(buffer-string)))
(current-buffer)))
#+end_src
*** Elixir
Add support for Elixir with [[https://github.com/elixir-editors/emacs-elixir][elixir-mode]]. The =elixir-format= hook sets up the correct formatter configuration when in a =projectile= project.
#+begin_src emacs-lisp
(use-package elixir-mode
:after eglot
:hook ((elixir-format . (lambda ()
(if (projectile-project-p)
(setq elixir-format-arguments
(list "--dot-formatter"
(concat (locate-dominating-file buffer-file-name ".formatter.exs") ".formatter.exs")))
(setq elixir-format-arguments nil))))
(elixir-mode . (lambda () (add-hook 'before-save-hook 'elixir-format nil t)))
(elixir-mode . eglot-ensure))
:config
;; (setq lsp-elixir-server-command '("elixir-ls"))
(add-to-list 'auto-mode-alist '("\\.[hl]eex\\'" . elixir-mode)))
#+end_src
Add a [[https://github.com/ayrat555/mix.el][mix]] minor mode to call =mix= tasks from emacs.
#+begin_src emacs-lisp
(use-package mix
:hook
(elixir-mode . mix-minor-mode))
#+end_src
*** Erlang
#+begin_src emacs-lisp
(use-package erlang
:mode
("\\.P\\'" . erlang-mode)
("\\.E\\'" . erlang-mode)
("\\.S\\'" . erlang-mode)
:config
(require 'erlang-start))
#+end_src
*** Rust
Rust support with [[https://github.com/rust-lang/rust-mode][rust-mode]].
#+begin_src emacs-lisp
(use-package rust-mode
:after eglot
:hook
(rust-mode . eglot-ensure)
(rust-ts-mode . eglot-ensure)
(before-save . eglot-format-buffer)
;; :init
;; (setq lsp-rust-analyzer-cargo-watch-command "clippy"
;; lsp-rust-analyzer-server-display-inlay-hints t
;; lsp-rust-analyzer-binding-mode-hints t
;; lsp-rust-analyzer-display-lifetime-elision-hints-enable "always")
)
#+end_src
Configure [[https://rust-analyzer.github.io][rust-analyzer]]
#+begin_src emacs-lisp
(defun eb/ra-eglot-config (server)
"initializationOptions for rust-analyzer"
`(:diagnostics (:enable t)
:imports (:granularity (:enforce :json-false :group "crate")
:group t :merge
(:glob t)
:prefix "plain")
:lruCapacity nil
:checkOnSave (:enable t
:command "clippy"
:allTargets t)
:inlayHints (:bindingModeHints t
:chainingHints t
:lifetimeElisionHints (:enable "always" :useParameterNames :json-false)
:maxLength nil
:parameterHints :json-false
:renderColons t
:typeHints (:enable t
:hideClosureInitialization :json-false
:hideNamedConstructor :json-false))
:procMacro (:enable t)))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(rust-mode . ("rust-analyzer" :initializationOptions eb/ra-eglot-config))))
#+end_src
Add [[https://github.com/kwrooijen/cargo.el][cargo]] support
#+begin_src emacs-lisp
(use-package cargo
:hook
(rust-mode . cargo-minor-mode))
#+end_src
Flycheck support for Rust with [[https://github.com/flycheck/flycheck-rust][flycheck-rust]]
#+begin_src emacs-lisp
(use-package flycheck-rust
:hook
(flycheck-mode . flycheck-rust-setup))
#+end_src
*** TOML
Support for TOML files with [[https://github.com/dryman/toml-mode.el][toml-mode]]
#+begin_src emacs-lisp :tangle no
(use-package toml-mode
:mode ("\\.toml\\'" . conf-toml-mode))
#+end_src
*** Docker
Add docker support with [[https://github.com/spotify/dockerfile-mode][dockerfile-mode]]
#+begin_src emacs-lisp
(use-package dockerfile-mode
:after eglot
:hook (dockerfile-mode . eglot-ensure))
#+end_src
*** Bitbake / Yocto
#+begin_src emacs-lisp :tangle no
(use-package bitbake-modes
:straight (:type git :repo "https://bitbucket.org/olanilsson/bitbake-modes.git"))
#+end_src
*** INI
=ini-mode= provides highlighting for ini files
#+begin_src emacs-lisp
(use-package ini-mode)
#+end_src
*** JSON
[[https://github.com/joshwnj/json-mode][json-mode]] extends the builtin =js-mode= with better syntax highlighting for JSON and adds some editing keybindings
#+begin_src emacs-lisp
(use-package json-mode
:after eglot
:hook (json-mode . eglot-ensure))
#+end_src
*** CMake
Add [[https://melpa.org/#/cmake-mode][cmake-mode]]
#+begin_src emacs-lisp
(use-package cmake-mode
:after eglot
:hook (cmake-mode . eglot-ensure))
#+end_src
*** YAML
Use [[https://github.com/yoshiki/yaml-mode][yaml-mode]] to handle YAML files
#+begin_src emacs-lisp
(use-package yaml-mode
:after eglot
:hook (yaml-mode . eglot-ensure))
#+end_src
*** C/C++
Enable clangd LSP for C and C++
#+begin_src emacs-lisp
(use-package cc-mode
:ensure nil
:after eglot
:hook
(c-mode . eglot-ensure)
(c++-mode . eglot-ensure))
#+end_src
Enable and configure =auto-insert-mode= for Horus projects
#+begin_src emacs-lisp
(defconst my/generate-cpp-file-executable
"~/workspace/horus/development/code-generation/generate-cpp-file.py"
"Python program to generate a C++ boilerplate")
(when (file-executable-p my/generate-cpp-file-executable)
(define-auto-insert
"\\.[ch]pp\\'"
(lambda nil (call-process my/generate-cpp-file-executable nil t nil buffer-file-name))))
(auto-insert-mode)
#+end_src
*** Meson
[[https://mesonbuild.com][meson]] is a build system designed to be as fast and as user-friendly as possible.
#+begin_src emacs-lisp
(use-package meson-mode)
#+end_src
*** nix
Add [[https://github.com/NixOS/nix-mode][nix-mode]]
#+begin_src emacs-lisp
(use-package nix-mode
:after eglot
:mode "\\.nix\\'"
:hook (nix-mode . eglot-ensure))
#+end_src
Tell =nil= to use =nixpkgs-fmt= for formatting nix files.
#+begin_src emacs-lisp
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(nix-mode . ("nil" :initializationOptions
(:formatting (:command ["nixpkgs-fmt"]))))))
#+end_src
*** Common Lisp
Common Lisp does not use =lsp-mode=, but has it's own environment: [[https://github.com/slime/slime][SLIME]] or Superior Lisp Interaction Mode for Emacs.
#+begin_src emacs-lisp :tangle no
(use-package slime
:init
(setq slime-lisp-implementations
'((sbcl ("sbcl" "--load ~/.quicklisp/setup.lisp") :coding-system utf-8-unix))
slime-default-lisp 'sbcl)
:config
(slime-setup '(slime-fancy slime-quicklisp slime-asdf))
:mode
(("\\.cl\\'" . lisp-mode))
:hook
(lisp-mode . (lambda () (slime-mode t)))
(inferior-lisp-mode . (lambda () (inferior-slime-mode t))))
#+end_src
[[https://github.com/joaotavora/sly][SLY]] is a fork of SLIME, by the same author as =eglot=, with improved UX
#+begin_src emacs-lisp
(use-package sly)
#+end_src
*** Clojure
Similar to =lisp=, there is [[https://github.com/clojure-emacs/cider][CIDER]] (Clojure Interactive Development Environment that Rocks) for Clojure(Script)
#+begin_src emacs-lisp
(use-package cider)
#+end_src
*** Terraform
[[https://github.com/emacsorphanage/terraform-mode][terraform-mode]] is a major mode for Terraform files
#+begin_src emacs-lisp
(use-package terraform-mode
:after eglot
:hook (terraform-mode . eglot-ensure))
#+end_src
Register =terraform-ls= with eglot
#+begin_src emacs-lisp
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(terraform-mode . ("terraform-ls" "serve"))))
#+end_src
*** Fish
[[https://github.com/wwwjfy/emacs-fish][fish-mode]] provides a major mode for fish shell scripts
#+begin_src emacs-lisp
(use-package fish-mode
:init
(setq fish-enable-auto-indent t))
#+end_src
*** Zig
[[https://github.com/ziglang/zig-mode][zig-mode]] provides a major mode for the [[https://ziglang.org][zig programming language]]
#+begin_src emacs-lisp
(use-package zig-mode
:after eglot
:hook (zig-mode . eglot-ensure))
#+end_src
*** Yuck
#+begin_src emacs-lisp
(use-package yuck-mode)
#+end_src
*** Racket
#+begin_src emacs-lisp
(use-package racket-mode
:after eglot
:hook (racket-mode . eglot-ensure))
#+end_src
*** Ruby
Let =ruby-mode= handle Ruby files
#+begin_src emacs-lisp
(use-package ruby-mode
:ensure nil
:after eglot
:hook (ruby-mode . eglot-ensure))
#+end_src
*** MapServer
Add highlighting for MapServer .map files
#+begin_src emacs-lisp :tangle no
(use-package mapserver-mode
:straight (:type git :host github :repo "AxxL/mapserver-emacs-mode")
:mode ("\\.map\\'" . mapserver-mode))
#+end_src
*** Go
It's better than nothing.
#+begin_src emacs-lisp
(use-package go-mode
:after eglot
:hook (go-mode . eglot-ensure))
#+end_src
*** Cucumber
[[https://github.com/michaelklishin/cucumber.el][feature-mode]] provides support for user stories written in Cucumber/Gherkin format.
#+begin_src emacs-lisp
(use-package feature-mode
:config
(setq feature-use-docker-compose nil)
:mode "\\.feature\\'")
#+end_src
*** Protobuf
[[https://github.com/protocolbuffers/protobuf/blob/main/editors/protobuf-mode.el][protobuf-mode]]
#+begin_src emacs-lisp
(use-package protobuf-mode)
#+end_src
*** Just
[[https://github.com/leon-barrett/just-mode.el][just-mode]] provides syntax highlighting for Justfiles, for the command runner [[https://github.com/casey/just][just]]
#+begin_src emacs-lisp
(use-package just-mode)
#+end_src
*** Python
Python
#+begin_src emacs-lisp :tangle no
(use-package python-mode
:hook
((python-mode python-ts-mode) . eglot-ensure))
#+end_src
#+begin_src emacs-lisp
(use-package python
:ensure nil
:hook (python-base-mode . eglot-ensure)
:init (setq python-indent-guess-indent-offset nil))
#+end_src
jinja2
#+begin_src emacs-lisp
(use-package jinja2-mode)
#+end_src
*** Haskell
[[https://github.com/haskell/haskell-mode][haskell-mode]] for Haskell files
#+begin_src emacs-lisp
(use-package haskell-mode
:hook
(((haskell-mode haskell-ts-mode) . eglot-ensure)
turn-on-haskell-unicode-input-method))
#+end_src
*** Dhall
Dhall is a programmable configuration language that you can think of as: JSON + functions + types + imports
#+begin_src emacs-lisp
(use-package dhall-mode
:mode "\\.dhall\\'")
#+end_src
*** nushell
[[http://www.nushell.sh/][nushell]] is a new type of shell that operates on typed data
#+begin_src emacs-lisp
(use-package nushell-ts-mode)
#+end_src
* Org
** Main org setup
[[https://orgmode.org][org-mode]] configuration
#+begin_src emacs-lisp
(use-package org
:general
<<org-binds>>
:custom
<<org-customisations>>
:hook
<<org-hooks>>
:config
<<org-config>>)
#+end_src
Some keybindings for often used functions
#+name: org-binds
#+begin_src emacs-lisp :tangle no
(:prefix my/leader
"r" 'org-capture
"a" 'org-agenda
"s" 'org-store-link
"L" 'org-store-link-global
"O" 'org-open-at-point-global)
#+end_src
Customisations
#+name: org-customisations
#+begin_src emacs-lisp :tangle no
(org-directory "~/org")
(org-log-done t)
(org-indent-indentation-per-level 2)
(org-startup-indented t)
(org-log-into-drawer t)
(org-default-notes-file (expand-file-name "notes.org" org-directory))
(org-return-follows-link t)
(org-pretty-entities t)
(org-hide-emphasis-markers t)
(org-startup-with-inline-images t)
(org-startup-with-latex-previews t)
(org-image-actual-width '(300))
#+end_src
Hooks
#+name: org-hooks
#+begin_src emacs-lisp :tangle no
((org-mode . org-indent-mode)
(org-mode . turn-on-visual-line-mode)
(org-mode . variable-pitch-mode)
(org-mode-indent . (lambda () (diminish 'org-indent-mode)))
(org-agenda-mode . (lambda () (hl-line-mode 1))))
#+end_src
Configuration
#+name: org-config
#+begin_src emacs-lisp :tangle no
(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(dolist (face '((org-level-1 . 1.2)
(org-level-2 . 1.1)
(org-level-3 . 1.05)
(org-level-4 . 1.0)
(org-level-5 . 1.0)
(org-level-6 . 1.0)
(org-level-7 . 1.0)
(org-level-8 . 1.0)))
(set-face-attribute (car face) nil :family my/variable-width-font :weight 'medium :height (cdr face)))
(set-face-attribute 'org-document-title nil :family my/variable-width-font :weight 'bold :height 1.3)
(set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
(set-face-attribute 'org-table nil :inherit 'fixed-pitch)
(set-face-attribute 'org-formula nil :inherit 'fixed-pitch)
(set-face-attribute 'org-code nil :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
(set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)
#+end_src
=org-capture= allows creating and saving quick notes, links and TODOs
#+begin_src emacs-lisp
(use-package org-capture
;; :straight org
:ensure org
:after org
:config
(setq org-capture-templates
'(("t" "Todo" entry (file+headline "gtd.org")
"* TODO %?\n %i\n %a")
("n" "Note" entry (file+olp+datetree "notes.org")
"* %?\nEntered on %U\n %i\n %a")
("N" "Note (work)" entry (file+olp+datetree "work/notes.org")
"* %?\nEntered on %U\n %i\n %a")
("c" "org-protocol-capture" entry (file+headline "inbox.org" "Links")
"* %^{Title}\n\n[[%:link][%:description]]\n\n %i"
:immediate-finish t))))
#+end_src
=org-datetree= allows you to organise captures by date. It is basically a set of headings representing the date, with the first level for the year, the second level for the month and the third for the day.
#+begin_src emacs-lisp
(use-package org-datetree
;; :straight org
:ensure org
:after org)
#+end_src
=org-protocol= can be used to send data to emacs using =org-protocol://= URLs.
#+begin_src emacs-lisp
(use-package org-protocol
;; :straight org
:ensure org
:after org
:config
(setq org-protocol-default-template-key "c"))
#+end_src
** org-roam
[[https://www.orgroam.com][org-roam]] helps with non-hierarchical note-taking. It uses the [[https://en.wikipedia.org/wiki/Zettelkasten][zettelkasten method]] to capture and organise notes and ideas.
#+begin_src emacs-lisp
(use-package org-roam
;; :after org
:custom
(org-roam-directory "~/org-roam")
(org-roam-completion-everywhere t)
<<org-roam-templates>>
:config
(require 'org-roam-dailies)
(org-roam-db-autosync-mode)
:general
(:prefix my/leader
:keymaps 'global
"n f" 'org-roam-node-find
"n r" 'org-roam-node-random
"n i" 'org-roam-node-insert
"n l" 'org-roam-buffer-toggle
:keymaps 'org-mode-map
"n o" 'org-id-get-create
"n t" 'org-roam-tag-add
"n a" 'org-roam-alias-add
"n l" 'org-roam-buffer-toggle)
("C-M-i" 'completion-at-point
:keymaps 'org-roam-dailies-map
"Y" 'org-roam-dailies-capture-yesterday
"T" 'org-roam-dailies-capture-tomorrow)
(:prefix my/leader "n d" '(:keymap org-roam-dailies-map)))
#+end_src
Org roam capture templates
#+name: org-roam-templates
#+begin_src emacs-lisp :tangle no
(org-roam-capture-templates '(("d" "default" plain
"%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+date: %U\n")
:unnarrowed t)
("w" "work note" plain
"%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :work:\n")
:unnarrowed t)
("p" "project" plain
"* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :project:\n")
:unnarrowed t)))
#+end_src
[[https://github.com/org-roam/org-roam-ui][org-roam-ui]] provides a frontend to explore and interact with =org-roam= notes. It can be started with =M-x org-roam-ui-mode RET=, and is then available on [[http://127.0.0.1:35901/]]. Updates are sent real-time through a WebSocket.
The settings provided here are also the defaults, set any to =nil= to disable.
#+begin_src emacs-lisp
(use-package org-roam-ui
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
#+end_src
** org-chef
[[https://github.com/Chobbes/org-chef][org-chef]] is used to manage recipes and import them from supported websites
#+begin_src emacs-lisp
(use-package org-chef
:after org-capture
:config
(add-to-list 'org-capture-templates '("r" "Recipes"))
(add-to-list 'org-capture-templates '("rr" "Recipe" entry (file "~/org/cookbook.org")
"%(org-chef-get-recipe-from-url)"
:empty-lines 1))
(add-to-list 'org-capture-templates '("rm" "Manual recipe" entry (file "~/org/cookbook.org")
"* %^{Recipe title: }\n :PROPERTIES:\n :source-url:\n :servings:\n :prep-time:\n :cook-time:\n :ready-in:\n :END:\n** Ingredients\n %?\n** Directions\n\n")))
#+end_src
* Misc
** direnv
[[https://github.com/purcell/envrc][envrc]] adds support for [[https://direnv.net][direnv]] to emacs. =envrc= works on a per-buffer basis, so when you open multiple projects the environments don't pollute eachother. NOTE: this should be loaded last.
#+name: envrc-init
#+begin_src emacs-lisp :tangle no
(use-package envrc
:init
(envrc-global-mode)
:general
(:prefix my/leader "e" '(:keymap envrc-command-map)))
#+end_src
** Writing
Enable flyspell
#+begin_src emacs-lisp
(use-package flyspell
:ensure nil
:init
(setq ispell-list-command "--list")
:hook
(text-mode . flyspell-mode))
(use-package flyspell-correct
:after flyspell
:general
(:keymaps 'flyspell-mode-map
"C-;" 'flyspell-correct-wrapper))
#+end_src
Use ivy for flyspell suggestions. Use =M-o= to switch to the actions (e.g. saving a word to your dictionary).
#+begin_src emacs-lisp :tangle no
(use-package flyspell-correct-ivy
:after flyspell-correct)
#+end_src
** Load local init file
If =local-init.el= exists, load it.
#+begin_src emacs-lisp
(when (file-readable-p my/local-init-file)
(load my/local-init-file))
#+end_src
#+begin_comment
DO NOT ADD ANYTHING BELOW THIS
#+end_comment
#+begin_src emacs-lisp :exports none
<<envrc-init>>
#+end_src