Welcome to my literate Emacs configuration. It is written in org-mode as I find that it helps keeping things tidy. I also use nix home-manager and flakes in order to maintain dependencies.
I use universal-blue as my distro of choice and prefer to install 1password as rpm-ostree
overlay instead of through nix. For this the following commands needs to be run to install it.
curl https://downloads.1password.com/linux/keys/1password.asc | sudo tee /etc/pki/rpm-gpg/RPM-GPG-KEY-1password
sudo sh -c 'echo -e "[1password]\nname=1Password Stable Channel\nbaseurl=https://downloads.1password.com/linux/rpm/stable/\$basearch\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=0\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-1password" > /etc/yum.repos.d/1password.repo'
rpm-ostree install 1password 1password-cli
(setq user-full-name "Oskar Haukebøe"
user-mail-address "[email protected]")
(use-package emacs
:hook
(before-save . delete-trailing-whitespace)
(prog-mode . display-line-numbers-mode)
:bind
("C-=" . text-scale-increase)
("C--" . text-scale-decrease)
("C-0" . text-scale-adjust)
("C-x c" . window-swap-states)
("C-c d" . delete-pair)
("C-x x k" . my/kill-buffers-not-backed-by-file)
:custom
(byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
(warning-minimum-level :error)
;; (pixel-scroll-precision-mode t)
;; gc settings ++ to improve performance
(gc-cons-threshold (* 50 1024 1024))
;; (gc-cons-percentage 0.3)
(read-process-output-max (* 3 1024 1024))
;; UI settings
(scroll-bar-mode nil)
(tool-bar-mode nil)
(menu-bar-mode nil)
(set-fringe-mode '(8 . 0))
;; Add spacing between windows
(window-divider-default-right-width 10)
(window-divider-default-places 'right-only)
;; Behavior settings
(global-auto-revert-mode t)
;; (electric-pair-mode t)
(savehist-mode t) ; Save history accross emacs sessions
(use-short-answers t) ; y/n instead of yes/no
(word-wrap t) ; Wrap lines at space between words
(truncate-lines t) ; Truncate lines instead of wrapping
(initial-scratch-message nil) ; Clean scratch buffer
(auto-revert-interval 1) ; Refresh buffers every second
(split-width-threshold 180) ; Split vertically by default
(split-height-threshold nil) ; Split vertically by default
(use-dialog-box nil) ; Disable dialog boxes
(inhibit-startup-screen t) ; Disable startup screen
(recentf-max-saved-items 100) ; Show more recent files
(scroll-margin 1) ; Add margin when scrolling
(backup-directory-alist ; Put backups in var/backups
`(("." . ,(concat user-emacs-directory "var/backups"))))
(indent-tabs-mode nil) ; Use spaces instead of tabs
(tab-width 2) ; Set tab width to 2 spaces
(isearch-allow-motion t) ; Allow movement during search
(repeat-mode t) ; Enable repeat mode
(mouse-drag-copy-region t) ; Enable mouse drag copy
(sentence-end-double-space nil) ; single space end sentence
;; (delete-selection-mode t) ; auto delete selection on insert
(delete-pair-blink-delay 0)
(delete-pair-push-mark t)
(blink-matching-delay 0)
;; Allow undo/redo window configuration with C-c <left>/<right>
(winner-mode 1)
:config
(server-start)
(add-to-list 'load-path
(concat user-emacs-directory "packages"))
(set-face-attribute 'default nil
:family "RobotoMono Nerd Font"
:height 140)
:preface
(require 'dash)
(defun my/buffer-backed-by-file-p (buffer)
(let ((backing-file (buffer-file-name buffer)))
(if (buffer-modified-p buffer)
t
(if backing-file
(file-exists-p (buffer-file-name buffer))
t))))
(defun my/kill-buffers-not-backed-by-file ()
"Kill all buffers that are not backed by a file."
(interactive)
(mapc 'kill-buffer (-remove 'my/buffer-backed-by-file-p (buffer-list)))))
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
This is a handy package which helps to show what packages are slowing down the Emacs init time.
(use-package benchmark-init
:ensure t
:config
(benchmark-init/activate)
;; (add-hook 'after-init-hook 'benchmark-init/deactivate)
:hook
(after-init-hook .
(lambda ()
(run-at-time 5 nil 'benchmark-init/deactivate))))
(use-package doom-themes
:ensure t
:custom-face
(default ((t (:background "#171717"))))
(ansi-color-bright-black ((nil (:inherit font-lock-comment-face
:foreground nil
:background nil))))
:config
(load-theme 'doom-tomorrow-night t))
This package provides a set of icons for Emacs.
(use-package nerd-icons
:ensure t
:custom
(nerd-icons-nerd-font-font-family "Symbols Nerd Font Mono")
(nerd-icons-install-font t))
Adds nerd-icons to completion menus. It works well with Marginalia
(use-package nerd-icons-completion
:ensure t
:after marginalia
:config
(nerd-icons-completion-mode)
(add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))
And to add them to corfu:
(use-package nerd-icons-corfu
:ensure t
:after corfu
:config
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
(use-package doom-modeline
:ensure t
:hook (after-init . doom-modeline-mode)
:custom
(doom-modeline-buffer-file-name-style 'auto)
(doom-modeline-modal nil)
(doom-modeline-buffer-encoding nil)
(doom-modeline-percent-position nil)
(column-number-mode t)
:config
(doom-modeline-def-modeline 'main
'(eldoc bar workspace-name window-number modals matches follow buffer-info remote-host word-count parrot selection-info)
'(compilation objed-state misc-info persp-name battery grip irc mu4e gnus github debug repl minor-modes input-method indent-info buffer-encoding process check lsp vcs time buffer-position))
(doom-modeline-def-modeline 'minimal
'(bar window-number modals matches buffer-info-simple)
'(media-info time))
(doom-modeline-def-modeline 'special
'(eldoc bar window-number modals matches buffer-info remote-host word-count parrot selection-info)
'(compilation objed-state misc-info battery irc-buffers debug minor-modes input-method indent-info buffer-encoding process time buffer-position))
(doom-modeline-def-modeline 'project
'(bar window-number modals buffer-default-directory remote-host)
'(compilation misc-info battery irc mu4e gnus github debug minor-modes input-method process time buffer-position))
(doom-modeline-def-modeline 'dashboard
'(bar window-number modals buffer-default-directory-simple remote-host)
'(compilation misc-info battery irc mu4e gnus github debug minor-modes input-method process time))
(doom-modeline-def-modeline 'vcs
'(bar window-number modals matches buffer-info remote-host parrot selection-info)
'(compilation misc-info battery irc mu4e gnus github debug minor-modes buffer-encoding process time buffer-position))
(doom-modeline-def-modeline 'package
'(bar window-number modals package)
'(compilation misc-info process time))
(doom-modeline-def-modeline 'info
'(bar window-number modals buffer-info info-nodes parrot selection-info)
'(compilation misc-info buffer-encoding time buffer-position))
(doom-modeline-def-modeline 'media
'(bar window-number modals buffer-size buffer-info)
'(compilation misc-info media-info process vcs time))
(doom-modeline-def-modeline 'message
'(eldoc bar window-number modals matches buffer-info-simple word-count parrot selection-info)
'(compilation objed-state misc-info battery debug minor-modes input-method indent-info buffer-encoding time buffer-position))
(doom-modeline-def-modeline 'pdf
'(bar window-number modals matches buffer-info pdf-pages)
'(compilation misc-info process vcs time))
(doom-modeline-def-modeline 'org-src
'(eldoc bar window-number modals matches buffer-info word-count parrot selection-info)
'(compilation objed-state misc-info debug minor-modes input-method indent-info buffer-encoding process check lsp time buffer-position))
(doom-modeline-def-modeline 'helm
'(bar helm-buffer-id helm-number helm-follow helm-prefix-argument)
'(helm-help time))
(doom-modeline-def-modeline 'timemachine
'(eldoc bar window-number modals matches git-timemachine word-count parrot selection-info)
'(misc-info minor-modes indent-info buffer-encoding time buffer-position))
(doom-modeline-def-modeline 'calculator
'(window-number modals matches calc)
'(misc-info minor-modes process buffer-position)))
A prettier startup screen
(use-package dashboard
:ensure t
;; :hook
;; ('elpaca-after-init-hook #'dashboard-insert-startupify-lists)
;; ('elpaca-after-init-hook #'dashboard-initialize)
:custom
;; (dashboard-projects-backend 'projectile)
(dashboard-set-heading-icons t)
(dashboard-set-file-icons t)
(dashboard-display-icons-p t) ; display icons on both GUI and terminal
(dashboard-icon-type 'nerd-icons) ; use `nerd-icons' package
(dashboard-week-agenda nil) ; nil for only current day
;; ; and t for the whole week
(dashboard-center-content t)
;; ;; (dashboard-startup-banner 2)
(dashboard-items '((recents . 5)
(bookmarks . 5)
(projects . 5)
(agenda . 5)
(registers . 5)))
:config
(dashboard-setup-startup-hook)
:init
;; Hopefully this will improve emacs startup if ssh hangs
(defun my/load-projects-after-startup ()
(run-with-timer 1 nil #'dashboard-refresh-buffer))
(add-hook 'emacs-startup-hook #'my/load-projects-after-startup))
which-key
is a package that displays the keybindings available after a prefix key. It is very useful to discover new keybindings.
(use-package which-key
:disabled
:ensure t
:config
(which-key-mode))
Corfu is a completion framework that provides a horizontal completion UI. It is a very simple package that does not provide any completion backends.
(use-package corfu
:ensure t
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-auto t) ;; Enable auto completion
;; (corfu-separator ?\s) ;; Orderless field separator
;; (corfu-quit-at-boundary nil) ;; Never quit at completion boundary
;; (corfu-quit-no-match nil) ;; Never quit, even if there is no match
;; (corfu-preview-current nil) ;; Disable current candidate preview
;; (corfu-preselect 'prompt) ;; Preselect the prompt
;; (corfu-on-exact-match nil) ;; Configure handling of exact matches
;; (corfu-scroll-margin 5) ;; Use scroll margin
(text-mode-ispell-word-completion nil)
(corfu-popupinfo-delay '(nil . 0.0))
:config
(global-corfu-mode 1)
(corfu-popupinfo-mode 1))
It is also possible to use Corfu in the terminal. This requires the corfu-terminal
package to be installed.
(use-package corfu-terminal
:ensure t
:after corfu
:config
(unless (display-graphic-p)
(corfu-terminal-mode +1)))
Make Corfu sort by last selected candidates.
(use-package corfu-history
:after corfu
:config
(corfu-history-mode t))
Make Corfu also show up in the minibuffer.
(with-eval-after-load 'corfu
(defun oh/corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active)
(bound-and-true-p vertico--input)
(eq (current-local-map) read-passwd-map))
(setq-local corfu-echo-delay nil ; Disable automatic echo
corfu-popupinfo-delay 0.0)
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'oh/corfu-enable-always-in-minibuffer))
Vertico is a completion framework that provides a vertical completion UI. It is a very simple package that does not provide any completion backends. It is meant to be used with orderless
.
;; Enable vertico
(use-package vertico
:ensure t
:custom
;; Enable recursive minibuffers
(enable-recursive-minibuffers t)
:config
(vertico-mode)
;; Different scroll margin
;; (setq vertico-scroll-margin 0)
;; Show more candidates
;; (setq vertico-count 20))
;; Grow and shrink the Vertico minibuffer
;; (setq vertico-resize t)
;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
(setq vertico-cycle t))
Allow using different vertico configurations for different prompts.
(use-package vertico-multiform
:after vertico)
Allow displaying the vertico completions in a grid
(use-package vertico-grid
:after vertico)
Add completion for directories
;; Configure directory extension.
(use-package vertico-directory
:after vertico
;; More convenient directory navigation commands
;; :bind (:map vertico-map
;; ("RET" . vertico-directory-enter)
;; ("DEL" . vertico-directory-delete-char)
;; ("M-DEL" . vertico-directory-delete-word))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
Orderless is a completion style that allows matching candidates in any order. It is very useful to find candidates when you don’t remember the exact order of the characters.
(use-package orderless
:ensure t
:after vertico
;; :init
;; Configure a custom style dispatcher (see the Consult wiki)
;; (setq orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch)
;; orderless-component-separator #'orderless-escapable-split-on-space)
:custom
(completion-styles '(orderless basic))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles partial-completion)))))
Marginalia is a package that displays additional information about the candidates in the minibuffer. It is very useful to find the right candidate.
(use-package marginalia
:ensure t
:after vertico
;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding
;; available in the *Completions* buffer, add it to the
;; `completion-list-mode-map'.
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:init
(marginalia-mode))
It’s also nice to have some nice looking icons for the completion candidates. This requires the nerd-fonts
package to be installed.
(use-package nerd-icons-completion
:ensure t
:after marginalia
:hook
(marginalia-mode . nerd-icons-completion-marginalia-setup)
:config
(nerd-icons-completion-mode))
Consult is a package that provides a set of commands for searching and navigating. It is very useful to find files, buffers, etc.
(use-package consult
:ensure t
:custom
(consult-buffer-sources
'(consult--source-hidden-buffer
consult--source-modified-buffer
consult--source-buffer
;; +consult-source-special
consult--source-recent-file
consult--source-file-register
consult--source-bookmark
consult--source-project-buffer-hidden
consult--source-project-recent-file-hidden))
:bind
(;; C-c bindings in `mode-specific-map'
("C-c M-x" . consult-mode-command)
("C-c h" . consult-history)
("C-c k" . consult-kmacro)
("C-c m" . consult-man)
("C-c i" . consult-info)
([remap Info-search] . consult-info)
;; C-x bindings in `ctl-x-map'
("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
;; ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
;; Other custom bindings
("M-y" . consult-yank-pop) ;; orig. yank-pop
; M-g bindings in `goto-map'
("M-g e" . consult-compile-error)
;; ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
("M-g g" . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
;; M-s bindings in `search-map'
("M-s d" . consult-find) ;; Alternative: consult-fd
("M-s c" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s l" . consult-line) ;; needed by consult-line to detect isearch
("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
;; Minibuffer history
:map minibuffer-local-map
("M-s" . consult-history) ;; orig. next-matching-history-element
("M-r" . consult-history)) ;; orig. previous-matching-history-element
:config
(recentf-mode 1))
;; (defvar +consult-special-filter "\\`\\*.*\\*\\'")
;; (defvar +consult-source-special
;; `(:name "Special"
;; :narrow ?x
;; ;; :hidden t
;; :category buffer
;; :face consult-buffer
;; :history buffer-name-history
;; ;; Specify either :action or :state
;; ;; :action ,#'consult--buffer-action ;; No preview
;; :state ,#'consult--buffer-state ;; Preview
;; :items
;; ,(lambda () (consult--buffer-query
;; :sort 'visibility
;; :as #'buffer-name
;; :exclude (remq +consult-special-filter consult-buffer-filter)
;; ;; :include '(+consult-special-filter)
;; :mode 'special-mode)))
;; "special buffer source.")
;; (add-to-list 'consult-buffer-filter +consult-special-filter))
(use-package embark
:ensure t
:bind
(("C-." . embark-act) ;; pick some comfortable binding
("C-;" . embark-dwim) ;; good alternative: M-.
("C-h b" . embark-bindings)) ;; alternative for `describe-bindings'
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
;; Show the Embark target at point via Eldoc. You may adjust the
;; Eldoc strategy, if you want to see the documentation from
;; multiple providers. Beware that using this can be a little
;; jarring since the message shown in the minibuffer can be more
;; than one line, causing the modeline to move up and down:
;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
;; Consult users will also want the embark-consult package.
(use-package embark-consult
:ensure t ; only need to install it, embark loads it after consult if found
:hook
(embark-collect-mode . consult-preview-at-point-mode))
(use-package magit
:ensure t
:defer t
:commands magit-status
:bind
(:map project-prefix-map
("m" . my/magit-project-status))
:custom
(magit-display-buffer-function
#'magit-display-buffer-same-window-except-diff-v1)
:preface
(defun my/magit-project-status ()
"Run magit-status in the current project's root directory."
(interactive)
(let ((dir (project-root (project-current t))))
(magit-status dir)))
:init
(with-eval-after-load 'project
(add-to-list 'project-switch-commands '(my/magit-project-status "Magit") t)))
Magit-todos for integrating TODO keywords with magit’s overview screen
(use-package magit-todos
:ensure t
:after magit
:custom
(magit-todos-filename-filter 'file-name-nondirectory)
:config
(magit-todos-mode 1))
(use-package hl-todo
:ensure t
:config
(global-hl-todo-mode 1))
(use-package gitignore-templates
:ensure t
:commands
'gitignore-templates-insert)
(use-package diff-hl
:ensure t
:hook
(prog-mode . diff-hl-mode)
(dired-mode . my/diff-hl-dired-mode-unless-tramp)
:custom
(diff-hl-flydiff-mode t)
(diff-hl-flydiff-delay 0)
(diff-hl-update-async t)
;; (diff-hl-dired-extra-indicators nil))
:init
(defun my/diff-hl-dired-mode-unless-tramp ()
"Enable diff-hl-dired mode when not accessing through tramp"
(unless (file-remote-p default-directory)
(diff-hl-dired-mode))))
Make magit integrate with github and other git hosting services.
(use-package forge
:ensure t
:after magit
:custom
(forge-add-default-bindings nil)
(auth-sources '("~/.authinfo"))
:config
(push '("github.uio.no" ; GITHOST
"api.github.uio.no" ; APIHOST
"github.uio.no" ; WEBHOST and INSTANCE-ID
forge-github-repository) ; CLASS
forge-alist))
(use-package project
:custom
(project-vc-ignore "^/var/home")
:config
(require 'f)
(let ((proj-dirs
(seq-filter #'f-dir? '("~/projects"
"~/knowit"))))
(mapc #'project-remember-projects-under proj-dirs)))
Eglot is a client for Language Server Protocol (LSP). It is a protocol that allows for IDE-like features such as code completion, code navigation, etc. It is supported by many programming languages.
For information about setting up a new lsp server, see Link.
(use-package eglot
:defer t
:bind
(:map eglot-mode-map
("C-c a" . eglot-code-actions)
("C-c r" . eglot-rename)
("C-c f" . eglot-format)
("C-c m" . consult-imenu)
("C-c M" . consult-imenu-multi)
("C-c d" . consult-lsp-diagnostics)))
;; (use-package eglot-x
;; :ensure (eglot-x :type git :host github :repo "nemethf/eglot-x")
;; :disabled
;; :demand
;; :after eglot
;; :config
;; (eglot-x-setup))
(use-package lsp-mode
:commands
(lsp-deferred lsp)
:bind
(:map lsp-mode-map
("C-h ." . lsp-describe-thing-at-point)
("C-c a" . lsp-execute-code-action)
("C-c f" . lsp-format-buffer)
("C-c C-f" . lsp-format-region)
("C-c r" . lsp-rename)
("C-c m" . consult-imenu)
("C-c M" . consult-imenu-multi)
("M-?" . lsp-find-references))
:custom
;; (lsp-warn-no-matched-clients nil)
(lsp-completion-provider :none) ;; I use Corfu instead!
(lsp-keymap-prefix nil)
(lsp-headerline-breadcrumb-enable nil)
(eldoc-display-functions '(eldoc-display-in-buffer))
(lsp-idle-delay 0.0)
(lsp-inlay-hint-enable t)
:init
;; Performance
(setq read-process-output-max (* 3 1024 1024)) ;; 3mb
:custom-face
(lsp-face-highlight-textual ((t (:background nil :foreground nil :weight ultra-bold :distant-foreground nil)))))
(use-package lsp-ui
:after lsp-mode
:custom
(lsp-ui-doc-enable nil)
(lsp-ui-doc-show-with-cursor nil)
(lsp-ui-doc-show-with-mouse nil)
(lsp-ui-sideline-enable nil)
:hook
(lsp-mode . lsp-ui-mode))
;; :general)
;; (oskah/leader-keys
;; "cdf" '(lsp-ui-doc-focus-frame :wk "focus frame")
;; "cdd" '(lsp-ui-doc-show :wk "show documentation")
;; "cdc" '(lsp-ui-doc-hide :wk "hide documentation")))
;; ('normal 'lsp-ui-mode-map
;; "K" 'lsp-ui-doc-show :wk "show documentation"))
(use-package consult-lsp
;; :ensure t
:after lsp-mode
:bind
(:map lsp-mode-map
("M-g M-f" . consult-lsp-diagnostics)
("M-g M-s" . consult-lsp-file-symbols)))
Eldoc is a minor mode that shows documentation in the echo area. It is enabled by default in prog-mode
.
(use-package eldoc
:defer t
:custom
(eldoc-echo-area-use-multiline-p nil)
(eldoc-idle-delay 0)
:config
(global-eldoc-mode -1))
Flymake is a minor mode that performs on-the-fly syntax checking. It is enabled by default in prog-mode
.
(use-package flymake
:after prog-mode
:disabled
:custom
(flymake-show-diagnostics-at-end-of-line nil))
(use-package flycheck
:ensure t
:custom
(flycheck-display-errors-function #'flycheck-display-error-messages)
(flycheck-display-errors-delay 0.0)
:config
(add-hook 'after-init-hook #'global-flycheck-mode))
(use-package consult-flycheck
:ensure t
:bind
("M-g f" . consult-flycheck))
Visualize the colors of color codes
(use-package rainbow-mode
:ensure t
:hook prog-mode)
(use-package editorconfig
:ensure t
:after prog-mode
:config
(editorconfig-mode 1))
Emacs 29 has built-in support for tree-sitter
, which is a parser generator tool and an incremental parsing library. It is used to create a syntax highlighting engine that is faster and more accurate than the built-in one. However, Emacs does not ship with any language support for tree-sitter
, so we’ll have to install it ourselves… or have treesit-auto
to do it for us.
According to the treesit-auto
documentation, Emacs 30 will ship with better defaults for tree-sitter
, so hopefully we won’t need treesit-auto
anymore.
(use-package treesit-auto
:ensure t
:disabled
:after prog-mode
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(delete 'c-sharp treesit-auto-langs)
(global-treesit-auto-mode))
(use-package rust-ts-mode
;; :ensure t
;; :hook (rust-ts-mode . eglot-ensure)
:hook (rust-ts-mode . lsp-deferred)
:mode "\\.rs\\'"
;; :bind
;; (:map rust-ts-mode-map
;; ("C-c C-c C-b" . rust-compile)
;; ("C-c C-c C-r" . rust-run)
;; ("C-c C-c C-c" . rust-run-clippy)
;; ("C-c C-c C-t" . rust-test)
;; ("C-c C-c C-k" . rust-check))
:custom
(rust-mode-treesitter-derive t))
;; :config
;; (with-eval-after-load 'eglot
;; (add-to-list 'eglot-server-programs
;; '((rust-ts-mode rust-mode) .
;; ("rust-analyzer"
;; :initializationOptions
;; (:check (:command "clippy")
;; :cargo (:targetDir t)))))))
(use-package c-ts-mode
:hook (c-ts-mode . lsp-deferred)
:mode
"\\.c\\'"
"\\.h\\'"
"\\.cu\\'")
(use-package java-ts-mode
:hook
(java-ts-mode . lsp-deferred)
(java-ts-mode . (lambda ()
(setq-local tab-width java-ts-mode-indent-offset)))
:mode
"\\.java\\'")
(use-package lsp-java
;; :ensure t
:config
(add-hook 'java-mode-hook 'lsp))
(use-package typescript-ts-mode
:hook (typescript-ts-mode . lsp-deferred)
:mode "\\.ts\\'"
:custom
(tab-width 4)
(typescript-ts-mode-indent-offset 4))
For editing .tsx
files, we’ll use jtsx
.
(use-package jtsx
:ensure t
:mode (("\\.jsx?\\'" . jtsx-jsx-mode)
("\\.tsx?\\'" . jtsx-tsx-mode))
:commands jtsx-install-treesit-language
:hook ((jtsx-jsx-mode . hs-minor-mode)
(jtsx-tsx-mode . hs-minor-mode)
(jtsx-jsx-mode . lsp-deferred)
(jtsx-tsx-mode . lsp-deferred)))
(use-package mhtml-mode
:mode "\\.html\\'")
When in a C# project, it is important to set the variable lsp-csharp-solution-file
to point to the project solution file (.sln). It is recommended to set this in a .dir-locals.el
file for the project.
(use-package csharp-ts-mode
;; :hook (csharp-ts-mode . lsp)
:hook (csharp-ts-mode . eglot-ensure)
:mode "\\.cs\\'"
;; (add-to-list 'treesit-language-source-alist
;; '(csharp . ("https://github.com/tree-sitter/tree-sitter-c-sharp" Latest)))
:init
(with-eval-after-load 'treesit
(add-to-list 'treesit-language-source-alist
'(c-sharp "https://github.com/tree-sitter/tree-sitter-c-sharp"
"v0.20.0")))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'(csharp-ts-mode . ("OmniSharp" "-lsp")))))
;; :general
;; (:keymaps 'csharp-ts-mode-map
;; :states 'normal
;; "K" 'lsp-describe-thing-at-point))
(use-package kotlin-ts-mode
:ensure t
:hook
(kotlin-ts-mode . lsp-deferred)
;; (kotlin-ts-mode . (lambda ()
;; (setq-local tab-width kotlin-ts-mode-indent-offset)))
:mode ("\\.kt\\'" "\\.kts\\'")
:custom
(lsp-kotlin-compiler-jvm-target "21.0")
(lsp-idle-delay 0.4)
:init
(with-eval-after-load 'treesit
(add-to-list 'treesit-language-source-alist
'(kotlin "https://github.com/fwcd/tree-sitter-kotlin"))))
(use-package sharper
;; :disabled)
:ensure t
:after '(csharp-mode csharp-ts-mode))
;; :general
;; (oh/leader-key csharp-ts-mode-map
;; "m d" 'sharper-main-transient))
(use-package json-ts-mode
:hook (json-ts-mode . eglot-ensure)
:mode "\\.json\\'")
(use-package yaml-ts-mode
:mode
"\\.yml\\'"
"\\.yaml\\'"
:hook
(yaml-ts-mode . lsp-deferred))
(use-package dockerfile-ts-mode
:mode "dockerfile")
(use-package terraform-mode
:ensure t
:hook (terraform-mode . eglot-ensure)
:mode "\\.tf")
(use-package python-ts-mode
;; :hook (python-ts-mode . eglot-ensure)
:hook (python-ts-mode . lsp)
:mode "\\.py\\'"
:init
(with-eval-after-load 'org
(add-to-list 'org-babel-load-languages
'(python . t)))
:custom
(lsp-pylsp-plugins-pydocstyle-enabled nil)
(lsp-pylsp-plugins-pycodestyle-enabled t)
(lsp-pylsp-plugins-pyflakes-enabled t)
(lsp-pylsp-plugins-flake8-enabled nil)
;; (lsp-pylsp-plugins-pylint-enabled t) ;; should look at virtualenvwrapper.el or conda.el to get this to work with pyvenv
(lsp-pylsp-plugins-autopep8-enabled t))
(use-package parinfer-rust-mode
:disabled
;; :hook
;; (emacs-lisp-mode . parinfer-rust-mode)
;; (emacs-lisp-mode . (lambda ()
;; (electric-pair-local-mode -1)
;; (parinfer-rust-mode 1)))
:custom
(parinfer-rust-auto-download t))
;; :config
;; (add-to-list 'oh/electric-pair-mode-blacklist-modes 'parinfer-rust-mode))
(use-package nix-mode
:ensure t
:hook (nix-mode . lsp-deferred)
:mode "\\.nix\\'")
(use-package git-modes
:ensure t)
(use-package csv-mode
:ensure t
:mode "\\.csv\\'"
:hook
(csv-mode . (lambda ()
(csv-align-mode t)
(csv-header-line t))))
(use-package LaTex-mode
:mode ("\\.tex\\'" . tex-mode)
:hook
(TeX-mode . eglot-ensure)
(TeX-mode . (lambda () (auto-fill-mode)))
(TeX-mode . (lambda () (truncate-lines nil)))
(TeX-mode . (lambda () (reftex-mode 1)))
:custom
(LaTeX-electric-left-right-brace t)
(TeX-view-program-selection '((output-pdf "PDF Tools")))
(TeX-source-correlate-start-server t)
(TeX-auto-save t)
(TeX-parse-self t)
(TeX-master nil)
:config
;; (load "auctex.el" nil t t)
;; Use pdf-tools to open PDF files
;; Update PDF buffers after successful LaTeX runs
(add-hook 'TeX-after-compilation-finished-functions
#'TeX-revert-document-buffer))
CDLatex makes writing math a pleasure.
(use-package cdlatex
:hook (LaTeX-mode . cdlatex-mode))
(use-package graphviz-dot-mode
:ensure t
:mode "\\.dot\\'"
:custom
(graphviz-dot-indent-width 4))
PlantUML is a markup language for generating UML diagrams
(use-package plantuml-mode
:ensure t
:mode
("\\.plantuml\\'" . plantuml-mode)
("\\.puml\\'" . plantuml-mode)
:init
(with-eval-after-load 'org
(add-to-list 'org-src-lang-modes
'("plantuml" . plantuml))
(add-to-list 'org-babel-load-languages
'(plantuml . t)))
:custom
(plantuml-default-exec-mode 'executable)
(org-plantuml-exec-mode 'plantuml)
(plantuml-indent-level 4)
(plantuml-output-type "png"))
Mermaid is a markup language for generating graphs. Pretty similar to PlantUML.
(use-package mermaid-mode
:ensure t
:mode
"\\.mermaid\\'"
"\\.mmd\\'")
(use-package ob-mermaid
:after org
:ensure t
:config
(add-to-list 'org-babel-load-languages '(mermaid . t)))
(use-package bibtex
:hook (bibtex-mode . eglot-ensure))
;; :general
;; (oh/leader-key bibtex-mode-map
;; "mri" '(citar-insert-bibtex :wk "Insert bibtex")))
To manage my bibliography entries, I use zotero which allows me to easily use their browser extension to add the bibliography entries to the database. It also automatically downloads the PDF, belonging to the entry. I also use zotfile to automatically rename the downloaded PDFs, and to place them in the library-path
which is in a cloud folder and which citar
can look through to find the files belonging to the bibliography entries. I also use better-bibtex which automatically exports my bibliography to a BibLatex file every time the bibliography is updated, which citar
then looks through. better-bibtex
also takes care of the cite-keys, which allows me to set the naming scheme in zotfile
to {%b}
which makes it use the cite-key as filename. This step is crucial, as citar
finds the matching file for an entry, by matching the filename with the cite-key.
Some other zotero plugins I use are:
- scite is also a very nice site, for finding relevant papers as well as to check how trustworthy an article is. Its
zotero
plugin makes it easy to get this information for your entire bibliography database. - PubPeer which is a cite for sharing comments about publications.
(defvar oh/bib-files
'("~/Nextcloud/.org/references.bib"
"~/Nextcloud/.org/bibliography/zotero.bib"
"~/Nextcloud/.org/bibliography/uni/IN3000.bib"
"~/Nextcloud/.org/bibliography/uni/IN2000 gang.bib"
"~/Nextcloud/.org/bibliography/uni/IN2120_gang-midterm.bib"))
(defvar oh/roam-dir
"~/Nextcloud/org_notes/roam/bibliography/")
(defvar oh/library-dir
"~/Nextcloud/.org/library/")
(use-package oc
:after org
:custom
(org-cite-csl-styles-dir "~/Zotero/styles")
(org-cite-global-bibliography oh/bib-files)
(org-cite-export-processors
'((t csl))))
;; (latex biblatex))))
(use-package citar
:ensure t
:hook
(org-mode . citar-capf-setup)
(latex-mode . citar-capf-setup)
(LaTeX-mode . citar-capf-setup)
;; :general
;; (oh/leader-key '(org-mode-map LaTeX-mode-map)
;; "mr" '(:ignore t :which-key "references")
;; "mrc" '(citar-insert-citation :which-key "insert citation")
;; "mre" '(citar-export-local-bib-file :which-key "export local bib file"))
;; (oh/leader-key
;; "nr" '(:ignore t :wk "references")
;; "nro" '(citar-open :wk "open resource"))
:bind
("C-c n o" . citar-open)
:custom
(citar-citeproc-csl-styles-dir "~/Zotero/styles/")
(citar-citeproc-csl-style "apa.csl")
(bibtex-dialect 'biblatex)
(citar-bibliography oh/bib-files)
(citar-notes-paths (list oh/roam-dir)) ; List of directories for reference nodes
(citar-open-note-function 'orb-citar-edit-note) ; Open notes in `org-roam'
;; (citar-at-point-function 'embark-act) ; Use `embark'
(org-cite-insert-processor 'citar)
(org-cite-follow-processor 'citar)
(org-cite-activate-processor 'citar)
:config
(defvar citar-indicator-files-icons
(citar-indicator-create
:symbol (nerd-icons-faicon
"nf-fa-file_o"
:face 'nerd-icons-green
:v-adjust -0.1)
:function #'citar-has-files
:padding " " ; need this because the default padding is too low for these icons
:tag "has:files"))
(defvar citar-indicator-links-icons
(citar-indicator-create
:symbol (nerd-icons-codicon
"nf-cod-link"
:face 'nerd-icons-orange
:v-adjust 0.01)
:function #'citar-has-links
:padding " "
:tag "has:links"))
(defvar citar-indicator-notes-icons
(citar-indicator-create
:symbol (nerd-icons-codicon
"nf-cod-note"
:face 'nerd-icons-blue
:v-adjust -0.3)
:function #'citar-has-notes
:padding " "
:tag "has:notes"))
(defvar citar-indicator-cited-icons
(citar-indicator-create
:symbol (nerd-icons-faicon
"nf-fa-circle_o"
:face 'nerd-icon-green)
:function #'citar-is-cited
:padding " "
:tag "is:cited"))
(setq citar-indicators
(list citar-indicator-files-icons
citar-indicator-links-icons
citar-indicator-notes-icons
citar-indicator-cited-icons)))
;; (use-package citar-embark
;; :ensure t
;; :after citar
;; :no-require
;; :config (citar-embark-mode))
(use-package citar-org
:after (oc citar)
:custom
(org-cite-insert-processor 'citar)
(org-cite-follow-processor 'citar)
(org-cite-activate-processor 'citar))
(use-package citar-org-roam
:ensure t
:after (citar org-roam)
:config (citar-org-roam-mode)
;; :general
;; (oh/leader-key
;; "nrc" '(citar-org-roam-ref-add :wk "add ref"))
:custom
(citar-org-roam-capture-template-key "n"))
;; :config
;; (add-to-list 'org-roam-capture-templates
;; '("n" "literature note" plain
;; "%?"
;; :target
;; (file+head
;; "%(expand-file-name (or citar-org-roam-subdir \"\") org-roam-directory)/${citar-citekey}.org"
;; "#+title: ${citar-citekey} (${citar-date}). ${note-title}.\n#+created: %U\n#+last_modified: %U\n\n")
;; :unnarrowed t)))
Org-ref handles crossreferences pretty well.
(use-package org-ref
:ensure t
:after org
:bind
(:map org-mode-map
("C-c r" . org-ref-insert-ref-link))
:custom
(org-ref-insert-cite-function
(lambda ()
(org-cite-insert nil))))
A special major mode is intended to view specially formatted data rather than files. These modes usually use read-only buffers.
(use-package special
:hook (special-mode . visual-line-mode))
(use-package org
:hook
;; (org-mode . variable-pitch-mode)
(org-mode . (lambda () (visual-line-mode 1)))
(org-mode . turn-on-org-cdlatex)
(org-mode . (lambda () (electric-pair-local-mode 0)))
:bind
("C-c n a" . org-agenda)
("C-c n c" . org-capture)
:custom
(org-export-with-smart-quotes t)
(org-hide-emphasis-markers t) ; Hide markup characters
(org-startup-indented t)
(org-pretty-entities t)
(org-use-sub-superscripts "{}")
(org-hide-emphasis-markers t)
(org-startup-with-inline-images t)
(org-image-actual-width '(700))
(org-image-align 'center)
(org-auto-align-tags nil)
(org-tags-column 0)
(org-fold-catch-invisible-edits 'show)
(org-startup-folded 'content)
(org-elipsis "…")
(org-default-notes-file "~/Nextcloud/org_notes/agenda/notes.org")
(org-agenda-files `(,org-default-notes-file))
(org-attach-archive-delete 'query)
(org-attach-preferred-new-method 'dir)
(org-attach-dir-relative t)
(org-startup-with-latex-preview t)
(org-format-latex-options
(plist-put org-format-latex-options :scale (/ 185 (org--get-display-dpi))))
(org-latex-prefer-user-labels t)
(org-ditaa-jar-path "~/.local/share/ditaa/ditaa.jar")
(org-capture-templates
'(("t" "Todo" entry (file "~/Nextcloud/org_notes/agenda/tasks.org")
"* TODO %?\n %i\n %a")
("n" "Note" entry
(file "~/Nextcloud/org_notes/agenda/notes.org")
"* %? :NOTES:\n:PROPERTIES:\n:CREATED: %U\n:END:\n%i\n")
("e" "Elfeed entry" entry
(file "~/Nextcloud/org_notes/agenda/notes.org")
"* %:title :FEED:\n:PROPERTIES:\n:ADDED: %U\n:FEED: %:feed-title\n:DATE: %:date-timestamp\n:LINK: %:external-link\n:END:\n%i\n#+begin_quote\n%:content\n#+end_quote\n\n%?")))
(org-agenda-custom-commands
'(("e" "Elfeed entries" tags "FEED"
((org-agenda-files (list "~/Nextcloud/org_notes/agenda/notes.org"))))
("n" "Notes" tags "NOTES"
((org-agenda-files (list "~/Nextcloud/org_notes/agenda/notes.org"))))))
:config
;; Pretty bullets
;; (font-lock-add-keywords 'org-mode
;; '(("^ *\\([-]\\) "
;; (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))
(add-to-list 'org-latex-packages-alist '("" "listings"))
(add-to-list 'org-latex-packages-alist '("" "amsmath"))
(with-eval-after-load 'ox-latex
(setq org-latex-listings 'listings)
(setq org-latex-listings-options
'(("basicstyle" "\\ttfamily\\footnotesize")
("breaklines" "true")
("showstringspaces" "false")
("postbreak" "\\mbox{$\\hookrightarrow$\\space}")
("xleftmargin" "2.8em")
("framexleftmargin" "2.8em")
;; ("numbers" "left")
("tabsize" "2"))))
(org-babel-do-load-languages 'org-babel-load-languages
'((ditaa . t)
(calc . t)
(sed . t)
(latex . t)
(makefile . t)
(org . t)
(shell . t)))
;; Load extra export backends
(require 'ox-beamer)
(require 'ox-man)
(require 'ox-texinfo)
:custom-face
(org-level-1 ((t (:inherit outline-1 :height 1.5))))
(org-level-2 ((t (:inherit outline-2 :height 1.3))))
(org-level-3 ((t (:inherit outline-3 :height 1.2))))
(org-level-4 ((t (:inherit outline-4 :height 1.1))))
(org-level-5 ((t (:inherit outline-5 :height 1.0))))
(org-level-6 ((t (:inherit outline-6 :height 1.0))))
(org-level-7 ((t (:inherit outline-7 :height 1.0))))
(org-level-8 ((t (:inherit outline-8 :height 1.0))))
;; (org-block ((t (:inherit fixed-pitch))))
;; (org-code ((t (:inherit (shadow fixed-pitch)))))
;; (org-drawer ((t (:height 0.8))))
;; (org-document-info-keyword ((t (:height 0.9))))
;; (org-meta-line ((t (:height 0.9))))
(org-document-title ((t (:height 1.5)))))
;; (org-table ((t (:inherit fixed-pitch)))))
Toggle the visibility of emphasis markers when the cursor is on the line.
(use-package org-appear
:ensure t
:hook (org-mode . org-appear-mode))
Automatically toggle org-preview-latex-fragment
when the cursor is on the line.
(use-package org-fragtog
:ensure t
:hook (org-mode . org-fragtog-mode))
Provides a clean look for org-mode.
(use-package org-modern
:ensure t
:hook (org-mode . org-modern-mode)
:custom
(org-modern-table nil)
(org-modern-list
'((?- . "•")
;;(?* . "•")
(?+ . "‣"))))
(use-package org-block-capf
:vc (:url "https://github.com/xenodium/org-block-capf")
:disabled
:custom
(org-block-capf-explicit-lang-defaults nil)
:hook (org-mode . org-block-capf-add-to-completion-at-point-functions))
Show pdf previews as inline images.
(use-package org-inline-pdf
:ensure t
:hook (org-mode . org-inline-pdf-mode))
(use-package org-download
:ensure t
:after org
:custom
(org-download-method 'attach))
;; :general
;; (oh/leader-key org-mode-map
;; "map" 'org-download-clipboard
;; "maf" 'org-download-screenshot
;; "mar" 'org-download-rename-at-point))
It is nice sometimes to use org for presentations.
(use-package org-present
:ensure t
:after org
;; :general
;; (oh/leader-key 'org-mode-map
;; "tp" '(org-present :wk "present"))
:custom
(org-present-text-scale 2)
(org-present-startup-folded t)
:config
(add-hook 'org-present-mode-hook
(lambda ()
;; (focus-mode t)
(org-present-big)
(org-appear-mode -1)
(org-present-read-only)))
;; (setq header-line-format " ")))
(add-hook 'org-present-mode-quit-hook
(lambda ()
;; (focus-mode -1)
(org-present-small)
(org-appear-mode t)
(org-present-show-cursor t)
(org-present-read-write))))
;; (setq header-line-format nil))))
;; (nano-modeline-org-mode))))
Export dispatcher using pandoc
(use-package ox-pandoc
:ensure t
:after ox)
(use-package org-roam
:ensure t
:defer
:custom
(org-roam-completion-everywhere t)
(org-roam-node-display-template "${title:*} ${tags:10}")
(org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
(org-roam-directory (file-truename "~/Nextcloud/org_notes/roam"))
(org-roam-dailies-directory (file-truename "~/Nextcloud/org_notes/daily"))
:bind
("C-c n j" . org-roam-dailies-capture-today)
;; :general
;; (oh/leader-key
;; "nf" '(org-roam-node-find :wk "find")
;; "nc" '(org-roam-capture :wk "capture")
;; "ni" '(org-roam-node-insert :wk "insert")
;; "nb" '(org-roam-buffer-toggle :wk "buffer")
;; "nt" '(org-roam-tag-add :wk "add tag")
;; "nl" '(consult-org-roam-backlinks :wk "backlinks")
;; "nrr" '(org-roam-ref-find :wk "find ref")
;; "nR" '(org-roam-refile :wk "refile")
;; "na" '(org-roam-alias-add :wk "add alias"))
:config
(org-roam-db-autosync-mode 1))
(use-package org-roam-ui
:ensure t
:disabled
;; :after org-roam
;; normally we'd recommend hooking orui after org-roam, but since
;; org-roam does not have a hookable mode anymore, you're advised to
;; pick something yourself if you don't care about startup time, use
;; :hook (after-init . org-roam-ui-mode)
;; :general
;; (oh/leader-key
;; "ng" '(org-roam-ui-mode :which-key "org-roam-ui"))
:custom
(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))
(use-package consult-org-roam
:ensure t
:custom
(consult-org-roam-mode 1)
;; Use `ripgrep' for searching with `consult-org-roam-search'
(consult-org-roam-grep-func #'consult-ripgrep)
;; Configure a custom narrow key for `consult-buffer'
(consult-org-roam-buffer-narrow-key ?r)
;; Display org-roam buffers right after non-org-roam buffers
;; in consult-buffer (and not down at the bottom)
(consult-org-roam-buffer-after-buffers t)
:config
;; Eventually suppress previewing for certain functions
(consult-customize
consult-org-roam-forward-links
:preview-key "M-.")
:bind
;; Define some convenient keybindings as an addition
("C-c n e" . consult-org-roam-file-find)
("C-c n b" . consult-org-roam-backlinks)
("C-c n l" . consult-org-roam-forward-links)
("C-c n r" . consult-org-roam-search))
(use-package org-noter
:ensure t
:defer
;; :general
;; (oh/leader-key
;; "ne" '(org-noter :which-key "org-noter"))
;; ('(normal visual insert emacs)
;; 'org-noter-doc-mode-map
;; "i" '(org-noter-insert-note :which-key "insert note"))
:custom
(org-noter-auto-save-last-location t)
(org-noter-notes-search-path
'("~/Nextcloud/org_notes" "~/Nextcloud/org_notes/roam/bibliography")))
(use-package org-side-tree
:ensure t
:bind ("C-c t w" . org-side-tree))
(use-package markdown-mode
:mode "\\.md\\'"
:hook (markdown-mode . olivetti-mode)
:custom
(markdown-hide-markup t))
(use-package dired
:commands (dired dired-jump)
:custom
(dired-listing-switches "-agohv --group-directories-first")
(dired-kill-when-opening-new-dired-buffer t)
(dired-async-mode t)
:config
;; Kill the buffer belonging to the deleted file or directory
(advice-add 'dired-delete-file :before
(lambda (file &rest rest)
(when-let ((buf (get-file-buffer file)))
(kill-buffer buf)))))
;; :general
;; (oh/leader-key
;; "fd" '(dired-jump :which-key "dired jump")
;; "fD" '(dired-jump-other-window :which-key "dired"))
;; ('normal 'dired-mode-map
;; "h" 'dired-up-directory
;; "l" 'dired-find-file))
(use-package dired-preview
:ensure t
:after dired
:custom
(dired-preview-delay 0.0)
(dired-preview-ignored-extensions-regexp "\\.\\(mkv\\|webm\\|mp4\\|mp3\\|ogg\\|m4a\\|flac\\|wav\\|gz\\|zst\\|tar\\|xz\\|rar\\|zip\\|iso\\|epub\\)"))
The emacs web browser
(use-package eww
:commands (oh/switch-to-eww-buffer)
;; :custom
;; (shr-use-fonts nil)
;; :general
;; (oh/leader-key
;; "ow" '(oh/switch-to-eww-buffer :wk "eww"))
:custom
(browse-url-browser-function 'eww-browse-url)
(browse-url-generic-program "zen")
:config
(defun oh/switch-to-eww-buffer ()
"Switches to an existing EWW buffer, if one exists."
(interactive)
(let ((eww-buf (catch 'found
(dolist (buf (buffer-list))
(when (with-current-buffer buf
(eq major-mode 'eww-mode))
(throw 'found buf))))))
(if eww-buf
(switch-to-buffer eww-buf)
(call-interactively 'eww)))))
A terminal emulator
(use-package eat
:ensure t
:disabled
:bind
("C-c e" . eat)
("C-x p t" . eat-project)
:hook
(eat-mode . (lambda () (display-line-numbers-mode -1))))
(use-package vterm
:ensure t
:hook
;; Iv'e had some severe performance issues, so I am disabling a
;; bunch of minor modes and hoping for it to imrove
(vterm-mode . (lambda ()
(corfu-mode)
(flycheck-mode)
(undo-fu-session-mode)
(wakatime-mode)
(yas-minor-mode)))
:bind
("C-c v" . 'vterm)
(:map project-prefix-map
("t" . project-vterm))
:custom
;; (vterm-max-scrollback 10000)
(vterm-tramp-shells '(("ssh" "/bin/bash")
("podman" "/bin/bash")
("docker" "/bin/bash")))
:preface
(defun project-vterm ()
(interactive)
(defvar vterm-buffer-name)
(let* ((default-directory (project-root (project-current t)))
(vterm-buffer-name (project-prefixed-buffer-name "vterm")))
(call-interactively 'vterm vterm-buffer-name)))
:init
(add-to-list 'project-switch-commands '(project-vterm "Vterm") t)
(add-to-list 'project-kill-buffer-conditions '(major-mode . vterm-mode)))
Integrate direnv in emacs.
;; (use-package direnv
;; :init
;; (direnv-mode))
(use-package direnv
:ensure t
:after (prog-mode)
:config
(direnv-mode))
(use-package pdf-tools
:ensure t
:mode ("\\.pdf\\'" . pdf-view-mode)
;; :requires pdf-outline
:commands (pdf-view-mode)
;:hook
;(pdf-view-mode-hook . evil-normal-state)
:config
(require 'pdf-outline))
;; (pdf-tools-install))
Wakatime is a service that tracks your coding activity. It is very useful to see how much time you spend on a project.
I’ve encountered issues with the wakatime-cli
program not functioning properly. As a result, I’ve discovered that the most dependable method to install Wakatime is by using the Wakatime VS Code extension and simply directing it to the binary installed by VS Code.
(use-package wakatime-mode
:ensure t
:custom
(wakatime-disable-on-error t)
;; (wakatime-cli-path "~/.wakatime/wakatime-cli")
:config
(global-wakatime-mode))
(use-package yasnippet
:ensure t
:init
(yas-global-mode 1))
(use-package smartparens
:ensure t
:hook
(prog-mode text-mode markdown-mode)
:bind
(:map smartparens-mode-map
("C-M-f" . sp-forward-sexp)
("C-M-b" . sp-backward-sexp)
("C-M-d" . sp-down-sexp)
("C-M-u" . sp-backward-up-sexp)
("C-M-n" . sp-next-sexp)
("C-M-p" . sp-previous-sexp)
("C-S-d" . sp-beginning-of-sexp)
("C-S-a" . sp-end-of-sexp)
("C-M-k" . sp-kill-sexp)
("C-M-w" . sp-copy-sexp)
("C-<backspace>" . sp-backward-unwrap-sexp)
;; ;; ("C-M-t" . sp-transpose-sexp)
("M-D" . sp-splice-sexp)
("C-<right>" . sp-forward-slurp-sexp)
("C-<left>" . sp-forward-barf-sexp)
("C-M-<left>" . sp-backward-slurp-sexp)
("C-M-<right>" . sp-backward-barf-sexp)
("C-]" . sp-select-previous-thing-exchange)
("C-M-[" . sp-select-next-thing))
:config
(show-smartparens-global-mode t)
;; load default config
(require 'smartparens-config))
(use-package makefile-executor
:ensure t
:hook
('makefile-mode-hook 'makefile-executor-mode))
;; :general
;; (oh/leader-key
;; "cb" '(makefile-executor-execute-project-target :wk "Run make command")))
(use-package copilot
;; :hook (prog-mode . copilot-mode)
;; :vc (:url "https://github.com/copilot-emacs/copilot.el")
;; :general
;; (oh/leader-key
;; "ta" '(oh/toggle-copilot-mode :wk "copilot"))
:bind
("C-c t c" . copilot-mode)
(:map copilot-completion-map
("<tab>" . 'copilot-accept-completion)
("TAB" . 'copilot-accept-completion)
("C-TAB" . 'copilot-accept-completion-by-word)
("C-<tab>" . 'copilot-accept-completion-by-word)))
;; (defvar oh/electric-pair-mode-blacklist-modes '()
;; "Modes where electric-pair-mode should not be enabled")
;; (defun oh/toggle-copilot-mode ()
;; "Toggle copilot mode."
;; (interactive)
;; (if (bound-and-true-p copilot-mode)
;; (progn (copilot-mode -1)
;; (if (not (cl-some (lambda (mode)
;; (derived-mode-p mode))
;; oh/electric-pair-mode-blacklist-modes))
;; (electric-pair-mode 1)))
;; (progn (copilot-mode 1)
;; (electric-pair-mode -1))))
Some utilities for using nix-shell together with direnv for projects
(use-package nix-init)
Use any LLM in Emacs. I have also added GitHub Models which allows to use any of the models available at the Marketplace using just one api key.
(use-package gptel
:ensure t
:bind
("C-c g g" . gptel)
("C-c g s" . gptel-send)
("C-c g r" . gptel-rewrite)
("C-c g a" . gptel-add)
:custom
;; (gptel-api-key
;; (lambda () (auth-source-pass-get 'secret "openai-key")))
(gptel-api-key
(auth-source-pick-first-password :host "api.openai.com"))
(gptel-default-mode 'org-mode)
:config
(setq gptel-backend
(gptel-make-anthropic "Claude" ;Any name you want
:stream t ;Streaming responses
:key (auth-source-pick-first-password :host "api.anthropic.com")))
;; OPTIONAL configuration
(gptel-make-openai "Github Models" ;Any name you want
:host "models.inference.ai.azure.com"
:endpoint "/chat/completions?api-version=2024-05-01-preview"
:stream t
:key (auth-source-pick-first-password :host "ai.azure.com")
:models '(DeepSeek-R1 gpt-4o o3-mini))
(gptel-make-tool
:name "read_buffer" ; javascript-style snake_case name
:function (lambda (buffer) ; the function that will run
(unless (buffer-live-p (get-buffer buffer))
(error "error: buffer %s is not live." buffer))
(with-current-buffer buffer
(buffer-substring-no-properties (point-min) (point-max))))
:description "return the contents of an emacs buffer"
:args (list '(:name "buffer"
:type string ; :type value must be a symbol
:description "the name of the buffer whose contents are to be retrieved"))
:category "buffers")
(gptel-make-tool
:name "list_buffers"
:function (lambda ()
(mapcar #'buffer-name (buffer-list)))
:description "return a list of all open buffers"
:args nil
:category "buffers")
(gptel-make-tool
:name "list_project_buffers"
:function (lambda ()
(when-let ((proj (project-current)))
(mapcar #'buffer-name (project-buffers proj))))
:description "return a list of buffers in the current project"
:args nil
:category "buffers")
(gptel-make-tool
:name "list_emacs_keybinds"
:function (lambda ()
(let ((keybinds '()))
(mapatoms
(lambda (sym)
(when (and (fboundp sym)
(not (string-prefix-p "widget-" (symbol-name sym)))
(not (string-prefix-p "cl-" (symbol-name sym))))
(let ((keys (where-is-internal sym)))
(when keys
(push (cons (symbol-name sym)
(mapcar #'key-description keys))
keybinds))))))
keybinds))
:description "return a list of all emacs keybinds"
:args nil
:category "emacs"))
gptel-quick is a nifty package that uses gptel to describe thing at point
(use-package gptel-quick
:after embark
:vc (:url "https://github.com/karthink/gptel-quick.git"
:rev "d7a3aed")
:config
(setq gptel-quick-timeout nil)
:bind
(:map embark-general-map
("?" . #'gptel-quick)))
Of course I need to have the wizard book as info pages :)
(use-package sicp
:ensure t
:after info)
I have been wanting for a while to try using Emacs for mail, but haven’t really gotten it to work before now. For connecting with exchange mail, I use davmail, and for my proton mail I use protonmail bridge. The nix config starts both of these programs as systemd services.
Both the mbsyncrc
and the msmtprc
files are in the ./dotfiles directory. They require a password for the Proton account which they read from ~/.mbsync-password
. The password that goes on here can be found by running protonmail-bridge --cli
then info
.
In order to connect to the protonmail bridge, put the certificate generated by openssl s_client -starttls imap -connect 127.0.0.1:1143 -showcerts
in ~/.mail/.cert/protonmail.crt
, i.e. the lines between (and including) -----BEGIN CERTIFICATE-----
and -----END CERTIFICATE-----
To initialize the maildir run:
mkdir ~/.mail ~/.mail/ifi ~/.mail/proton ~/.mail/knowit
mu init --maildir=~/.mail [email protected] [email protected] [email protected]
mu index
mbsync -a
(use-package mu4e
:ensure nil
:defer t
:if (and (file-exists-p "~/.mail")
(executable-find "mbsync")
(executable-find "msmtp")
(executable-find "mu"))
:bind
("C-c o m" . mu4e)
:custom
(mu4e-split-view nil)
(mail-user-agent 'mu4e-user-agent)
(shr-use-colors nil)
(mu4e-context-policy 'pick-first)
(mu4e-update-interval (* 5 60)) ;; Update every 5 minutes
(mu4e-index-update-error-warning nil) ;; Hide 'CLOSE' error from proton
(mu4e-hide-index-messages t)
(mu4e-bookmarks
'((:name "Unread messages" :query "flag:unread AND NOT flag:trashed AND maildir:/INBOX/" :key 117)
(:name "Today's messages" :query "date:today..now" :key 116)
(:name "Last 7 days" :query "date:7d..now" :hide-unread t :key 119)
(:name "Messages with images" :query "mime:image/*" :key 112)))
:config
(setq sendmail-program (executable-find "msmtp")
send-mail-function 'smtpmail-send-it
mu4e-root-maildir "~/.mail"
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
message-send-mail-function 'message-send-mail-with-sendmail
message-kill-buffer-on-exit t
mu4e-get-mail-command (concat (executable-find "mbsync") " -a")
mu4e-change-filenames-when-moving t
mu4e-use-fancy-chars t)
(setq mu4e-maildir-shortcuts
(list
'(:maildir "/ifi/Inbox"
:key ?i)
'(:maildir "/proton/Inbox"
:key ?p)
'(:maildir "/knowit/Inbox"
:key ?k)))
(setq mu4e-contexts
(list
(make-mu4e-context
:name "ifi"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/ifi" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Oskar Haukebøe")
(mu4e-sent-folder . "/ifi/Sent")
(mu4e-trash-folder . "/ifi/Trash")
(mu4e-drafts-folder . "/ifi/Drafts")
(mu4e-refile-folder . "/ifi/Archive")
(smtpmail-smtp-user . "[email protected]")))
(make-mu4e-context
:name "proton"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/proton" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Oskar Haukebøe")
(mu4e-sent-folder . "/proton/Sent")
(mu4e-trash-folder . "/proton/Trash")
(mu4e-drafts-folder . "/proton/Drafts")
(mu4e-refile-folder . "/proton/Archive")
(mu4e-compose-signature . nil)
(smtpmail-smtp-user . "[email protected]")))
(make-mu4e-context
:name "knowit"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/knowit" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Oskar Haukebøe")
(mu4e-sent-folder . "/knowit/Sent")
(mu4e-trash-folder . "/knowit/Trash")
(mu4e-drafts-folder . "/knowit/Drafts")
(mu4e-refile-folder . "/knowit/Archive")
(mu4e-compose-signature . nil)
(smtpmail-smtp-user . "[email protected]"))))))
Org-msg allows for composing the mail using orgmode, and then send it as beautiful html.
(use-package org-msg
:ensure t
:after mu4e
;; :disabled
:config
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
org-msg-startup "hidestars indent inlineimages"
org-msg-greeting-fmt "\nHi%s,\n\n"
org-msg-recipient-names '(("[email protected]" . "Oskar"))
org-msg-greeting-name-limit 3
org-msg-default-alternatives '((new . (text html))
(reply-to-html . (text html))
(reply-to-text . (text)))
org-msg-convert-citation t
org-msg-signature "
Cheers,
#+begin_signature
Oskar
#+end_signature")
(org-msg-mode))
(use-package mu4e-marker-icons
:ensure t
:after mu4e
:init (mu4e-marker-icons-mode 1))
And obviously I need consult integration as well
(use-package consult-mu
:after (consult mu4e)
:vc (:url "https://github.com/armindarvish/consult-mu.git"
:rev "39ed566")
:bind
(:map mu4e-search-minor-mode-map
("s" . consult-mu)))
An RSS reader
(use-package elfeed
:ensure t
:custom
(elfeed-feeds '(("https://blog.nicco.love/rss" nicco blog)
("http://www.masteringemacs.org/feed" masterringemacs blog emacs)
("https://blog.dornea.nu/feed.xml" dornea blog)
("https://blog.system76.com/rss.xml" cosmic)
("https://universal-blue.discourse.group/tag/announcements.rss" universalblueannounce ublue)))
:bind
("C-c e" . 'elfeed))
(use-package rmapi)
Not undo-tree
(use-package vundo
:ensure t
:defer
:custom
(vundo-glyph-alist vundo-unicode-symbols)
(vundo-window-max-height 10))
;; :general
;; (oh/leader-key
;; "u" '(vundo :wk "not undo tree")))
Save & recover undo steps between Emacs sessions.
(use-package undo-fu
:ensure t
:custom
(undo-limit (* 64 1024 1024)) ; 64mB.
(undo-strong-limit (* 96 1024 1024)) ; 96mB.
(undo-outer-limit (* 10 undo-strong-limit))) ; 960mB.
(use-package undo-fu-session
:ensure t
:config
(undo-fu-session-global-mode))
This sets up spell-checking using both English and Norwegian dictionaries together. It is also necessary to install hunspell-en_us
and hunspell-nb
. Jinx is a much faster alternative to flyspell, and it also supports combining dictionaries.
(use-package jinx
:hook
(emacs-startup . global-jinx-mode)
:custom
(jinx-languages "en_US nb_NO")
:bind
("M-$" . jinx-correct)
("C-M-$" . jinx-languages)
;; :general
;; (oh/leader-key
;; "sc" '(jinx-correct :wk "correct previous")
;; "ts" '(jinx-mode :wk "toggle spellcheck"))
:config
(with-eval-after-load 'vertico
(add-to-list 'vertico-multiform-categories
'(jinx grid
;; (:not indexed)
(vertico-grid-annotate . 20)))
(vertico-multiform-mode 1)))
(use-package lsp-ltex
;; :ensure t
;; :hook (text-mode . (lambda ()
;; (require 'lsp-ltex)
;; (lsp))) ; or lsp-deferred
:bind
(:map text-mode-map
("C-c t l" . lsp-deferred))
:init
(setq lsp-ltex-version "15.2.0")) ; make sure you have set this, see below
(use-package powerthesaurus
:ensure t
:bind
("C-c p" . #'powerthesaurus-transient))
;; :general
;; (oh/leader-key
;; "st" '(powerthesaurus-transient :wk "thesaurus")))
A better help buffer
(use-package helpful
:ensure t
;; :custom
;; (counsel-describe-function-function #'helpful-callable)
;; (counsel-describe-variable-function #'helpful-variable)
;; :general
;; ('normal "K" 'helpful-at-point)
;; (oh/leader-key
;; "hp" 'describe-package
;; "ht" 'describe-theme
;; "hv" 'describe-variable
;; "hf" 'describe-function
;; "hk" 'describe-key)
:bind
([remap describe-function] . helpful-function)
([remap describe-variable] . helpful-variable)
([remap describe-key] . helpful-key)
([remap describe-command] . helpful-command))
(use-package devilry-mode
:vc (:url "https://github.com/ohaukeboe/devilry-mode")
:disabled
:defer
:custom
(dm-java-compilation nil))
;; :general
;; (oh/leader-key
;; "tD" '(devilry-mode :wk "devilry"))
;; (oh/leader-key '(devilry-mode-map)
;; "md" '(dm-do-oblig :wk "do oblig")
;; "mc" '(desktop-hard-clear :wk "clear desktop")))
Make text more readable by narrowing the text at the center of the screen. This is useful for writing prose with visual-line-mode enabled.
(use-package olivetti
:ensure t
;; :commands olivetti-mode
:hook (org-mode . olivetti-mode)
:custom (olivetti-body-width 90)
:bind
("C-c t o" . olivetti-mode))
(use-package tramp
:custom
;; Make tramp use controllerMaster options from ssh config
(tramp-use-connection-share nil)
:config
;; helps in making remote lsp server available
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
An amazing litte package that makes scrolling actually feel smooth. This is especially nice when having images in the buffer.
(use-package ultra-scroll
:vc (:url "https://github.com/jdtsmith/ultra-scroll.git"
:rev "2c517bf")
:init
(setq scroll-conservatively 101 ; important!
scroll-margin 0)
:config
(ultra-scroll-mode 1))