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

1957 lines
57 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 "RecMonoLinear Nerd Font")
(defvar my/variable-width-font "Iosevka Aile")
(defvar my/comment-font "RecMonoCasual Nerd Font")
(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
** 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
** 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 title bar, toolbar and scroll bar
#+begin_src emacs-lisp
; This needs to go before scroll-bar-mode
(setq-default default-frame-alist '((undecorated . t)))
(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 like the [[https://draculatheme.com][dracula theme]]
#+begin_src emacs-lisp :tangle no
(use-package dracula-theme
:init
(load-theme 'dracula :no-confirm))
#+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-spectrum :no-confirm))
#+end_src
So is [[https://github.com/catppuccin/emacs][Catppuccin]]
#+begin_src emacs-lisp
(use-package catppuccin-theme
:init
(setq catppuccin-flavor 'mocha)
(load-theme 'catppuccin :no-confirm))
#+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
(setq iosevka-ligatures '("<---" "<--" "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
"<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
"<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
(setq monaspace-ligatures '(
; ss01
"==" "===" "=/=" "!=" "!==" "/=" "/==" "~~" "=~" "!~"
; ss02
">=" "<="
; ss03
"->" "<-" "=>" "<!--" "-->" "<~" "<~~" "~>" "~~>" "<~>"
; ss04
"</" "/>" "</>" "/\\" "\\/"
; ss05
"|>" "<|"
; ss06
"##" "###"
; ss07
"***" "/*" "*/" "/*/" "(*" "*)" "(*)"
; ss08
".=" ".-" "..<"
; dlig & calt
"<!" "**" "::" "=:" "=!" "=/" "--" ".." "//" "&&" "||" ":=" ":>" ":<" "!!" ">:" "<:" "#=" "?:" "?." "??" ";;" "///" ":::" "..." "=!=" "=:=" "..=" "..-"))
(use-package ligature
:config
;; Enable all Iosevka ligatures in programming modes
(ligature-set-ligatures 'prog-mode iosevka-ligatures)
;; Ligatures for Monaspace
;; 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)
(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
** TRAMP
Set some connection properties
#+begin_src emacs-lisp
(with-eval-after-load "tramp" (add-to-list 'tramp-connection-properties
(list (regexp-quote "/sshx:hass:")
"remote-shell" "/bin/bash")))
#+end_src
** Ollama
Let's evaluate this puppy.
#+begin_src emacs-lisp
(use-package gptel
:general
(:prefix my/leader
"m" 'gptel-menu
"C-m" 'gptel-send)
:config
(setq gptel-model "mistral-nemo"
gptel-backend (gptel-make-ollama "Ollama"
:host "100.119.162.110:11434"
:stream t
:models '("mistral" "mistral-nemo")))
(add-to-list 'gptel-directives '(commit . "You are an expert programmer summarizing a git diff.
Reminders about the git diff format:
For every file, there are a few metadata lines, like (for example):
```
diff --git a/lib/index.js b/lib/index.js
index aadf691..bfef603 100644
--- a/lib/index.js
+++ b/lib/index.js
```
This means that `lib/index.js` was modified in this commit. Note that this is only an example.
Then there is a specifier of the lines that were modified.
A line starting with `+` means it was added.
A line that starting with `-` means that line was deleted.
A line that starts with neither `+` nor `-` is code given for context and better understanding.
It is not part of the diff.
After the git diff of the first file, there will be an empty line, and then the git diff of the next file.
Do not include the file name as another part of the comment.
Do not use the characters `[` or `]` in the summary.
Write every summary comment in a new line.
Comments should be in a bullet point list, each line starting with a `-`.
The summary should not include comments copied from the code.
The output should be easily readable. When in doubt, write fewer comments and not more. Do not output comments that
simply repeat the contents of the file.
Readability is top priority. Write only the most important comments about the diff.
EXAMPLE SUMMARY COMMENTS:
```
- Raise the amount of returned recordings from `10` to `100`
- Fix a typo in the github action name
- Move the `octokit` initialization to a separate file
- Add an OpenAI API for completions
- Lower numeric tolerance for test files
- Add 2 tests for the inclusive string split function
```
Most commits will have less comments than this examples list.
The last comment does not include the file names,
because there were more than two relevant files in the hypothetical commit.
Do not include parts of the example in your summary.
It is given only as an example of appropriate comments.
")))
#+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
Add eglot support for flycheck-mode
#+begin_src emacs-lisp
(use-package flycheck-eglot
:after (flycheck eglot)
:config
(global-flycheck-eglot-mode 1))
#+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
*** 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
'((toml-mode toml-ts-mode conf-toml-mode) . ("taplo" "lsp" "stdio")))
;; (add-to-list 'eglot-server-programs
;; `((elixir-mode elixir-ts-mode heex-ts-mode) .
;; ,(eglot-alternatives '(("nextls" "--stdio=true"
;; :initializationOptions (:experimental (:completions (:enable t))))
;; "elixir-ls"))))
;;
(add-to-list 'eglot-server-programs
`((elixir-ts-mode heex-ts-mode) .
,(eglot-alternatives '("lexical" "elixir-ls"))))
(add-to-list 'eglot-server-programs
'(dhall-mode . ("dhall-lsp-server")))
(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
:mode "\\.svelte\\'"
: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\\'")
("svelte" . "\\.svelte\\'")))
: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 :tangle no
(use-package elixir-mode
:after eglot
:hook ((elixir-mode . eglot-ensure))
:config
(add-to-list 'auto-mode-alist '("\\.[hl]eex\\'" . elixir-mode)))
#+end_src
#+begin_src emacs-lisp
(use-package elixir-ts-mode
:after eglot
:hook ((elixir-ts-mode . eglot-ensure)))
#+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)
:preferPrelude t
:prefix "plain")
:checkOnSave (:enable t
:command "clippy"
:allTargets t)
:inlayHints (:bindingModeHints t
:chainingHints t
:lifetimeElisionHints (:enable "always" :useParameterNames t)
:maxLength nil
: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 cmake-ts-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 c-ts-mode) . eglot-ensure)
((c++-mode c++-ts-mode) . eglot-ensure))
#+end_src
Add some flags to clangd
#+begin_src emacs-lisp
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((c-mode c-ts-mode c++-mode c++-ts-mode)
. ("clangd"
"--malloc-trim"
"--log=error"
"--clang-tidy"
"--completion-style=detailed"))))
#+end_src
Add QML mode
#+begin_src emacs-lisp
(use-package qml-mode
:mode "\\.qml\\'")
#+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 =nixfmt= 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 ["nixfmt"]))))))
#+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
:mode
(("\\.cl\\'" . lisp-mode))
:config
(setq sly-lisp-implementations
'((sbcl ("sbcl") :coding-system utf-8-unix))
sly-default-lisp 'sbcl))
#+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
*** 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
Register =nushell= LSP with eglot
#+begin_src emacs-lisp
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(nushell-ts-mode . ("nu" "--lsp"))))
#+end_src
*** Lua
[[https://github.com/immerr/lua-mode][lua-mode]] for Lua support.
#+begin_src emacs-lisp
(use-package lua-mode)
#+end_src
*** Device Tree
Highlighting for device tree configuration.
#+begin_src emacs-lisp
(use-package dts-mode)
#+end_src
*** OCaml
[[https://github.com/ocaml/tuareg][Tuareg]] is the recommended OCaml mode
#+begin_src emacs-lisp
(use-package tuareg)
#+end_src
[[https://github.com/ocaml/merlin][Merlin]] adds context-sensitive completion
#+begin_src emacs-lisp
(use-package merlin
:init (setq merlin-command "ocamlmerlin")
:hook (tuareg-mode . merlin-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))
(add-to-list 'org-export-backends 'md)
#+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 . (lambda () (display-line-numbers-mode -1)))
(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