;;; -*- lexical-binding: t -*- #+TITLE:Emacs Configuration #+AUTHOR: Janis Böhm #+email: janis@nirgendwo.xyz #+STARTUP: indent #+BABEL: :session *R* :cache yes :results silent :exports code :tangle yes #+PROPERTY: header-args :tangle "init.el" * Preamble ** Inspiration I started my own emacs config after becoming fed up with how slow Spacemacs, which is my go-to config of emacs, was for me, and how rigid the experience was to me. I never felt like I could really change Spacemacs to fit my own needs beyond what the config offered itself. After looking online for guides on how to do a "literate configuration" and stumbled upon Sophie Bosio's [[https://sophiebos.io/posts][emacs blogs]] and [[https://github.com/SophieBosio/.emacs.d][git repo]]. ** Cool Packages to remember These are some packages which I might want to use in the future, or maybe not, but I definitely want to remember that they exist. - https://github.com/rnkn/olivetti This might be really nice for creative writing. Usually, I would probably have two buffers open to emulate this same behaviour and also have the benefit of having another section of the same file open for referencing/reading back. - https://github.com/minad/vertico Completion engine alternative to helm, super lightweight - https://github.com/rougier/svg-tag-mode create svg images to replace tokens based on regexes - https://github.com/minad/org-modern maybe for em dashes? does loads more too. - https://github.com/emacsorphanage/key-chord look at org-bullets to make a package which turns =--= and =---= into $\textemdash$ and $\textemdash$. -> https://github.com/jorgenschaefer/typoel perchance? ** Links https://emacs.stackexchange.com/questions/12383/how-to-unbind-a-key https://github.com/noctuid/evil-guide?tab=readme-ov-file#keymap-precedence * Start ** Lexical Scoping #+begin_src emacs-lisp ;;; -*- lexical-binding: t -*- #+end_src ** Tangling init.org From Sophie's emacs.d: #+begin_src emacs-lisp (defun tangle-init () "If the current buffer is init.org the code-blocks are tangled, and the tangled file is compiled." (when (equal (buffer-file-name) (expand-file-name (concat user-emacs-directory "init.org"))) ;; Avoid running hooks when tangling. (let ((prog-mode-hook nil)) (org-babel-tangle) ;; (byte-compile-file (concat user-emacs-directory "init.el")) ))) (add-hook 'after-save-hook 'tangle-init) #+end_src ** Startup performance Usually, I like to run emacs as a daemon and only ever open new emacs clients, but that often doesn't work properly and definitely doesn't work when actively working on an emacs config, so this snippet might be useful. #+begin_src emacs-lisp (setq gc-cons-percentage 0.6) (setq read-process-output-max (* 1024 1024)) ;; 1mb #+end_src Use GC magic hack. #+begin_src emacs-lisp :tangle no (use-package gcmh :config (setq gcmh-idle-delay 5 gcmh-high-cons-threshold (* 100 1024 1024)) ; 100mb (gcmh-mode 1)) #+end_src I have no idea what this does, but apparently it's an optimisation. #+begin_src emacs-lisp (setq idle-update-delay 1.0) #+end_src ** Cleaning up elements Remove visual elements: #+begin_src emacs-lisp (dolist (mode '(tool-bar-mode scroll-bar-mode menu-bar-mode blink-cursor-mode)) (funcall mode 0)) #+end_src #+begin_src emacs-lisp (setq ring-bell-function 'ignore initial-scratch-message nil inhibit-startup-message t use-dialog-box nil) #+end_src Warnings crop up every so often and they are usually unimportant. I disable this buffer when actively working on my config. #+begin_src emacs-lisp :tangle no (setq native-comp-async-report-warnings-errors 'silent) ;; native-comp warning (setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) (setq warning-minimum-level :emergency) (setq inhibit-message t) #+end_src Backups / Auto-saves #+begin_src emacs-lisp (defvar emacs-autosave-directory (concat user-emacs-directory "autosaves/") "This variable dictates where to put auto saves. It is set to a directory called autosaves located wherever your .emacs.d/ is located.") ;; Sets all files to be backed up and auto saved in a single directory. (setq backup-directory-alist `((".*" . ,emacs-autosave-directory)) auto-save-file-name-transforms `((".*" ,emacs-autosave-directory t))) #+end_src Auto-save files: #+begin_src emacs-lisp :tangle no (use-package auto-save-buffers-enhanced :straight t :config (auto-save-buffers-enhanced t) (setq auto-save-buffers-enhanced-exclude-regexps '("init.org"))) #+end_src ** Package repositories Use =straight=, and it's bootstrapping code: #+begin_src emacs-lisp (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" (or (bound-and-true-p straight-base-dir) user-emacs-directory))) (bootstrap-version 7)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) #+end_src Disable =package.el= in the early init file: #+begin_src emacs-lisp :tangle early-init.el (setq package-enable-at-startup nil) #+end_src Then, install =use-package= and tell it to always assume =:straight=: #+begin_src emacs-lisp (straight-use-package 'use-package) (setq straight-use-package-by-default t) #+end_src load org early to work around version mismatches: #+begin_src emacs-lisp (use-package org :straight (:type built-in)) #+end_src Add package repositories and rank them by priority #+begin_src emacs-lisp :tangle no (setq package-archives '(("GNU ELPA" . "https://elpa.gnu.org/packages/") ("MELPA" . "https://melpa.org/packages/") ("ORG" . "https://orgmode.org/elpa/") ("MELPA Stable" . "https://stable.melpa.org/packages/") ("nongnu" . "https://elpa.nongnu.org/nongnu/")) package-archive-priorities '(("GNU ELPA" . 20) ("MELPA" . 15) ("ORG" . 10) ("MELPA Stable" . 5) ("nongnu" . 0))) (package-initialize) ; (package-refresh-contents) #+end_src #+begin_src emacs-lisp (defvar local-lisp (concat user-emacs-directory "local-lisp/")) (add-to-list 'load-path local-lisp) (let ((default-directory local-lisp)) (normal-top-level-add-subdirs-to-load-path)) #+end_src ** Global Variables #+begin_src emacs-lisp (set-language-environment "UTF-8") (column-number-mode t) ;; Show current column number in mode line (delete-selection-mode t) ;; Replace selected text when yanking (dirtrack-mode t) ;; Directory tracking in shell (global-so-long-mode t) ;; Mitigate performance for long lines (global-visual-line-mode t) ;; Break lines instead of truncating them (global-auto-revert-mode t) ;; Revert buffers automatically when they change (recentf-mode t) ;; Remember recently opened files (savehist-mode t) ;; Remember minibuffer prompt history (save-place-mode t) ;; Remember last cursor location in file (show-paren-mode t) ;; Highlight matching parentheses #+end_src #+begin_src emacs-lisp (setq-default tab-width 4 fill-column 80 indent-tabs-mode nil) (setq use-short-answers t) (setq initial-major-mode 'org-mode) #+end_src #+begin_src emacs-lisp (use-package exec-path-from-shell :straight t :config (when (daemonp) (exec-path-from-shell-initialize))) #+end_src #+begin_src emacs-lisp (use-package keychain-environment :straight t :defer t :config (keychain-refresh-environment)) #+end_src Keymap: #+begin_src emacs-lisp (defvar custom-bindings-map (make-keymap) "A keymap for custom keybindings.") (define-minor-mode custom-bindings-mode "A mode that activates custom keybindings." :init-value t :keymap custom-bindings-map) #+end_src * Visuals ** Borders, Frames & Windows #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(internal-border-width . 8 )) (set-fringe-mode 4) (setq-default fringes-outside-margins 0) (setq-default indicate-buffer-boundaries nil) (setq-default indicate-empty-lines nil) (set-face-attribute 'header-line t :inherit 'default) #+end_src ** Themes Light theme for writing #+begin_src emacs-lisp (use-package gruvbox-theme :straight t) #+end_src [[https://github.com/doomemacs/themes][Doom Themes]] as default theme #+begin_src emacs-lisp ;; doom needs this somehow (use-package all-the-icons :straight t) (use-package doom-themes :straight t :config (setq doom-themes-enable-bold t doom-themes-enable-italic t) (doom-themes-org-config)) #+end_src Manage themes, use dark-theme by default: #+begin_src emacs-lisp (defvar nemo/dark-theme 'gruvbox-dark-medium) (defvar nemo/light-theme 'gruvbox-light-medium) (add-hook 'after-init-hook (lambda () (load-theme nemo/dark-theme t))) #+end_src Disable themes before loading a new one, from [[https://github.com/larstvei/dot-emacs?tab=readme-ov-file#advice][here]]. #+begin_src emacs-lisp (defadvice load-theme (before disable-before-load (theme &optional no-confirm no-enable) activate) (mapc 'disable-theme custom-enabled-themes)) #+end_src Helper function for switching themes. #+begin_src emacs-lisp (defun nemo/set-light-theme () "load the light theme." (interactive) (load-theme nemo/light-theme t)) (defun nemo/set-dark-theme () "load the dark theme." (interactive) (load-theme nemo/dark-theme t)) #+end_src ** emacs-related #+begin_src emacs-lisp (setq help-window-select t help-window-keep-selected t) #+end_src ** Fonts Font size [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Face-Attributes.html][(height)]] in emacs works in $\frac{1}{10}$ths of points, so ~110~ is the same as ~11~ points. #+begin_src emacs-lisp (defvar nemo/font-height-mono 110) (defvar nemo/font-height-sans 120) #+end_src #+begin_src emacs-lisp (set-face-attribute 'default nil :font "monospace" :height nemo/font-height-mono) (set-face-attribute 'fixed-pitch nil :font "monospace" :height nemo/font-height-mono) (set-face-attribute 'variable-pitch nil :font "sans-serif" :height nemo/font-height-sans) (when (member "SF Mono" (font-family-list)) (set-face-attribute 'default nil :font "SF Mono" :height nemo/font-height-mono) (set-face-attribute 'fixed-pitch nil :family "SF Mono" :height nemo/font-height-mono)) (when (member "SF Pro Text" (font-family-list)) (set-face-attribute 'variable-pitch nil :family "SF Pro Text" :height nemo/font-height-sans)) #+end_src Use nerd-icons and apple emojis #+begin_src emacs-lisp (use-package nerd-icons :straight t) (use-package emojify :straight t :config (when (member "Apple Color Emoji" (font-family-list)) (set-fontset-font t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend))) #+end_src Use both fixed and variable pitched fonts and faces. #+begin_src emacs-lisp (use-package mixed-pitch :straight t :hook ((org-mode . mixed-pitch-mode) (LaTeX-mode . mixed-pitch-mode))) #+end_src ** Mode Line Honestly not very happy with this at the moment, but it's kind of hacked together based on Sophie's and Amit's modelines. #+begin_src emacs-lisp ;; Mode line setup (setq-default mode-line-format '(" " (:propertize "λ" face font-lock-comment-face) " " mode-line-buffer-identification " " ; read-only or modified status (:eval (cond (buffer-read-only (propertize " RO " 'face 'mode-line-read-only-face)) ((buffer-modified-p) (propertize " ** " 'face 'mode-line-modified-face)) (t " "))) ; directory and buffer/file name (:propertize (:eval (shorten-directory default-directory 30)) face mode-line-folder-face) (:propertize "%b" face mode-line-filename-face) ;; Version control info (:eval (when-let (vc vc-mode) ;; Use a pretty branch symbol in front of the branch name (list (propertize "  " 'face 'font-lock-comment-face) (propertize (substring vc 5) 'face 'font-lock-comment-face)))) " " (:propertize mode-name) ; (global-mode-string global-mode-string) (:eval (propertize " " 'display `((space :align-to (- (+ right right-fringe right-margin) ,(+ 2 (string-width "%4l:%c"))))))) ;; Line and column numbers (:propertize "%4l:" face mode-line-position-face) (:eval (propertize "%c" 'face (if (>= (current-column) 80) 'mode-line-80col-face 'mode-line-position-face))) )) ;; Helper function (defun shorten-directory (dir max-length) "Show up to `max-length' characters of a directory name `dir'." (let ((path (reverse (split-string (abbreviate-file-name dir) "/"))) (output "")) (when (and path (equal "" (car path))) (setq path (cdr path))) (while (and path (< (length output) (- max-length 4))) (setq output (concat (car path) "/" output)) (setq path (cdr path))) (when path (setq output (concat ".../" output))) output)) ;; Extra mode line faces (make-face 'mode-line-read-only-face) (make-face 'mode-line-modified-face) (make-face 'mode-line-folder-face) (make-face 'mode-line-filename-face) (make-face 'mode-line-position-face) (make-face 'mode-line-mode-face) (make-face 'mode-line-80col-face) (set-face-attribute 'mode-line-read-only-face nil :inherit 'mode-line-face :foreground "#4271ae" :box '(:line-width 2 :color "#4271ae")) (set-face-attribute 'mode-line-modified-face nil :inherit 'mode-line-face :foreground "#c82829" :background "#ffffff" :box '(:line-width 2 :color "#c82829")) (set-face-attribute 'mode-line-folder-face nil :inherit 'mode-line-face :foreground "gray60") (set-face-attribute 'mode-line-filename-face nil :inherit 'mode-line-face :foreground "#eab700" :weight 'bold) (set-face-attribute 'mode-line-position-face nil :inherit 'mode-line-face) (set-face-attribute 'mode-line-mode-face nil :inherit 'mode-line-face :foreground "gray80") (set-face-attribute 'mode-line-80col-face nil :inherit 'mode-line-position-face :foreground "black" :background "#eab700") #+end_src * Packages ** =general.el= #+begin_src emacs-lisp (defconst nemo/leader-evil "SPC") (defconst nemo/leader-global "C-c") (defconst nemo/major-key "m") (defconst nemo/leader-major-evil (concat nemo/leader-evil " " nemo/major-key)) (defconst nemo/leader-major-global (concat nemo/leader-global " " nemo/major-key)) (use-package general :straight t :config (general-define-key :states '(emacs normal) :prefix-map 'nemo/leader-prefix-map :prefix-command 'nemo/leader-prefix-command :global-prefix nemo/leader-global :prefix nemo/leader-evil) (general-create-definer leader-def :prefix-command 'nemo/leader-prefix-command :prefix-map 'nemo/leader-prefix-map) (general-create-definer leader-other-def :states '(emacs normal) :prefix-map 'nemo/leader-prefix-map :prefix-command 'nemo/leader-prefix-command :global-prefix nemo/leader-global :prefix nemo/leader-evil ) (general-create-definer leader-major-def :states '(emacs normal) :global-prefix nemo/leader-global :prefix nemo/leader-evil ) ;; magit uses with-editor-mode to spawn the git process ;; in with the emacs client as the $EDITOR (general-def '(emacs normal) 'with-editor-mode-map ",," 'with-editor-finish) (leader-def "" #'nemo/switch-to-last-buffer "d" #'duplicate-line "gb" #'xref-go-back "gf" #'xref-go-forward "bk" #'switch-to-prev-buffer "bp" #'switch-to-prev-buffer "bj" #'switch-to-next-buffer "bn" #'switch-to-next-buffer "bb" #'consult-buffer "bd" #'kill-current-buffer "bs" #'scratch-buffer "wd" #'delete-window "wo" #'delete-other-windows "ff" #'find-file "fi" #'nemo/edit-init-org "fs" #'save-buffer ) ) #+end_src ** Web Search #+begin_src emacs-lisp (use-package engine-mode :straight t :defer t :config (defengine duckduckgo "https://duckduckgo.com/?q=%s" :keybinding "g") (defengine twitter "https://twitter.com/search?q=%s" :keybinding "x") (defengine wikipedia "https://www.wikipedia.org/search-redirect.php?language=en&go=Go&search=%s" :keybinding "w" :docstring "Searchin' the wikis.") (defengine wiktionary "https://www.wikipedia.org/search-redirect.php?family=wiktionary&language=en&go=Go&search=%s" :keybinding "d") (defengine wolfram-alpha "https://www.wolframalpha.com/input/?i=%s") (defengine youtube "https://www.youtube.com/results?aq=f&oq=&search_query=%s" :keybinding "y") (engine-mode t) ) #+end_src ** =magit= try out =diff-hl= for highlighting diffs in magit: #+begin_src emacs-lisp (use-package diff-hl :straight t :config (global-diff-hl-mode)) #+end_src =magit= is awesome and the number one reason why I use Emacs! #+begin_src emacs-lisp (use-package magit :straight t :defer t :general (leader-def "gSh" 'magit-status-here "gs" #'magit-status "gSb" 'magit-blame) :config (setq magit-mode-quit-window 'magit-restore-window-configuration ;;magit-auto-revert-mode t )) #+end_src =magit-todos= sounds cool. #+begin_src emacs-lisp :tangle no (use-package magit-todos :after magit :straight t :config (magit-todos-mode t)) #+end_src And forge sounds cool as well. #+begin_src emacs-lisp (use-package forge :straight t :after magit) #+end_src ** which-key This is one of those features of Spacemacs that is super useful. #+begin_src emacs-lisp (use-package which-key :straight t :config (which-key-mode)) #+end_src ** rainbow-delimiters raimbow delimiters colours matching delimiters with different colours. Used by Spacemacs as well. #+begin_src emacs-lisp (use-package rainbow-delimiters :straight t :hook (prog-mode . rainbow-delimiters-mode)) #+end_src ** evil #+begin_src emacs-lisp (use-package evil :straight t :init (setq evil-want-C-u-scroll t) (setq evil-want-keybinding nil) (setq evil-want-integration t) (setq evil-undo-system 'undo-fu) :config (evil-mode 1) #+end_src Evil key-binds, documentation for how this works is [[https://evil.readthedocs.io/en/latest/keymaps.html][here]]: #+begin_src emacs-lisp (leader-def "wj" 'evil-window-down "wk" 'evil-window-up "wh" 'evil-window-left "wl" 'evil-window-right ) #+end_src Advice =evil-search-forward= (bound to =/=) to push the current marker onto the xref stack to allow for returning to where I started searching from: #+begin_src emacs-lisp (defun nemo/evil-search-forward--push-xref (&optional _regex-p _no-recursive-edit) "push current marker to xref stack when forward searching." (xref-push-marker-stack)) (advice-add #'evil-search-forward :before #'nemo/evil-search-forward--push-xref '((name . "evil-search-push-xref"))) #+end_src Close =:config=. #+begin_src emacs-lisp ) #+end_src #+begin_src emacs-lisp (use-package evil-surround :straight t :defer t :config (global-evil-surround-mode 1)) #+end_src #+begin_src emacs-lisp (use-package evil-collection :after evil :straight t :init (setq evil-collection-key-blacklist '("C-c " "")) :config (evil-collection-init)) #+end_src ** =undo-fu= Also use =undo-fu=, which evil can use. #+begin_src emacs-lisp (use-package undo-fu :straight t :defer t) #+end_src ** Projectile #+begin_src emacs-lisp (use-package projectile :straight t :defer t :general (leader-def "p" '(:keymap project-prefix-map :wk "Projectile") ) :config (setq projectile-project-search-path '("~/code/")) (projectile-mode)) #+end_src ** =ripgrep= & Co. #+begin_src emacs-lisp (use-package ripgrep :straight t :defer t) (use-package rg :straight t :defer t) (use-package wgrep :straight t :defer t) #+end_src ** VTerm #+begin_src emacs-lisp (use-package vterm :straight t) #+end_src * Stuff ** Mini-buffer escape #+begin_src emacs-lisp :tangle no (setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) #+end_src #+begin_src emacs-lisp (defun nemo/abort-minibuffer-if-active () "Abort the minibuffer if it is active." (interactive) (when (active-minibuffer-window) (abort-recursive-edit))) (global-set-key (kbd "") 'nemo/abort-minibuffer-if-active) #+end_src * Writing ** Olivetti #+begin_src emacs-lisp (use-package olivetti :straight t :defer t ;; :bind (:map custom-bindings-map ("C-c o" . olivetti-mode)) :hook (org-mode . olivetti-mode) :hook (olivetti-mode-on . (lambda () (olivetti-set-width 88))) :config (setq olivetti-style t)) #+end_src ** Spelling #+begin_src emacs-lisp (use-package jinx :straight t :hook (emacs-startup . global-jinx-mode) :bind (("M-$" . jinx-correct) ("C-M-$" . jinx-languages)) :config (leader-def "Ss" 'jinx-correct) (setq jinx-languages "en_GB dk_DK de_DE")) #+end_src ** LaTeX #+begin_src emacs-lisp (use-package auctex :straight t :hook (LaTeX-mode . turn-on-prettify-symbols-mode) (LaTeX-mode . reftex-mode) ;; (LaTeX-mode . (lambda () (corfu-mode -1))) ;; (LaTeX-mode . outline-minor-mode) (LaTeX-mode . olivetti-mode) :config (setq TeX-view-program-selection '((output-pdf "PDF Tools")) TeX-view-program-list '(("PDF Tools" TeX-pdf-tools-sync-view)) TeX-source-correlate-start-server t)) #+end_src PDF tools for latex previewing: #+begin_src emacs-lisp (use-package pdf-tools :straight t :defer t :mode ("\\.pdf\\'" . pdf-view-mode) :config (pdf-loader-install) (setq-default pdf-view-display-size 'fit-height) (setq pdf-view-continuous t) (setq +latex-viewers '(pdf-tools)) (leader-major-def :keymaps 'pdf-view-mode-map "g b" #'pdf-history-backward "g f" #'pdf-history-forward) (general-def :keymaps 'pdf-view-mode-map "j" (lambda() (interactive) (pdf-view-scroll-up-or-next-page 20)) "k" (lambda() (interactive) (pdf-view-scroll-down-or-previous-page 20))) ) #+end_src ** Org Taken from [[https://sophiebos.io/posts/prettifying-emacs-org-mode/][ here]]. #+begin_src emacs-lisp (use-package org :straight (:type built-in) :defer t :hook (org-mode . variable-pitch-mode) ;; I basically always want to be running =visual-line-mode= anyway, but certainly in org-mode. :hook (org-mode . visual-line-mode) :hook (org-mode . nemo/prettify-symbols-setup) :general-config (general-define-key :prefix-map 'nemo/org-map "i" '(:ignore t :which-key "Insert..") "ih" '("Heading" . org-insert-heading) "is" '("Subheading" . org-insert-subheading) "ii" '("Item" . org-insert-item) "ib" '("Block..". org-insert-structure-template) "il" '("Link" . org-insert-link) ) (leader-major-def :keymaps 'org-mode-map "m" '(:keymap nemo/org-map :wk "Org") ) (general-def 'normal 'org-src-mode-map ",," 'org-edit-src-exit) (general-def 'normal :keymaps 'org-mode-map "TAB" 'org-cycle "RET" 'org-open-at-point) :config #+end_src Change heading font sizes: #+begin_src emacs-lisp (custom-set-faces '(org-document-title ((t (:height 1.8)))) '(outline-1 ((t (:height 1.35)))) '(outline-2 ((t (:height 1.3)))) '(outline-3 ((t (:height 1.2)))) '(outline-4 ((t (:height 1.1)))) '(outline-5 ((t (:height 1.1)))) '(outline-6 ((t (:height 1.1)))) '(outline-8 ((t (:height 1.1)))) '(outline-9 ((t (:height 1.1))))) #+end_src Open Org files with the content folded away: #+begin_src emacs-lisp (setq org-startup-folded 'content) #+end_src Enable LaTeX previews. #+begin_src emacs-lisp (setq org-startup-with-latex-preview t) #+end_src Inline images as well. #+begin_src emacs-lisp (setq org-startup-with-inline-images t) #+end_src In case LaTeX previews are too small, use this to increase them. #+begin_src emacs-lisp (plist-put org-format-latex-options :scale 1.35) #+end_src =pretty-entities= allows for latex symbols to be embedded into org-mode. =org-hide-leading-stars= hides all but one star on org headings. #+begin_src emacs-lisp (setq org-adapt-indentation t org-hide-leading-stars t org-pretty-entities-include-sub-superscripts t org-pretty-entities t) #+end_src Let Emacs act according to the language's rules inside of a source block. #+begin_src emacs-lisp (setq org-src-fontify-natively t org-src-tab-acts-natively t org-edit-src-content-indentation 0) #+end_src End =:config= #+begin_src emacs-lisp ) #+end_src Install various export packages: #+begin_src emacs-lisp (use-package ox-epub :straight t) #+end_src Use org-bullets for fancy headline markers #+begin_src emacs-lisp :tangle no (use-package org-bullets :straight t :hook (org-mode . (lambda () (org-bullets-mode 1))) :config) #+end_src Use typo-mode in org-mode for en and em dashes: #+begin_src emacs-lisp :tangle no (require 'typo) (typo-global-mode 1) (add-hook 'org-mode-hook 'typo-mode) #+end_src Use =org-fragtog= to show embedded LaTeX fragments when in insert mode. #+begin_src emacs-lisp (use-package org-fragtog :straight t :hook (org-mode . org-fragtog-mode)) #+end_src Using =org-appear= we can hide emphasis markers for italic, bold, etc. and show when editing the surrounded word. #+begin_src emacs-lisp (use-package org-appear :straight t :commands (org-appear-mode) :hook (org-mode . org-appear-mode) :config (setq org-hide-emphasis-markers t) (setq org-appear-autoemphasis t ;; show /../ *..* =..= ~..~ tokens org-appear-autolinks t ;; show link hyperlinks when editing org-appear-autosubmarkers t)) ;; show _.._ and superscript markers #+end_src #+begin_src emacs-lisp (use-package org-modern :straight t :defer t :after org :hook (org-mode . org-modern-mode)) #+end_src Change org face fonts and font sizes for headers. #+begin_src emacs-lisp :tangle no (require 'org-faces) ;; size org levels differently (dolist (face '((org-document-title . 1.8) (org-level-1 . 1.35) (org-level-2 . 1.3) (org-level-3 . 1.2) (org-level-4 . 1.1) (org-level-5 . 1.1) (org-level-6 . 1.1) (org-level-7 . 1.1) (org-level-8 . 1.1))) ;; set the font for each of them to sans-serif (set-face-attribute (car face) nil :font "SF Pro Text" :weight 'bold :height (cdr face))) #+end_src Fix org-indent to be fixed-pitch. #+begin_src emacs-lisp :tangle no (require 'org-indent) (set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch)) #+end_src Make sure that faces like code blocks or verbatim text are still using a monospaced font: #+begin_src emacs-lisp :tangle no (set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch :height 0.85) (set-face-attribute 'org-code nil :foreground nil :inherit '(shadow fixed-pitch) :height 0.85) (set-face-attribute 'org-indent nil :foreground nil :inherit '(org-hide fixed-pitch) :height 0.85) (set-face-attribute 'org-verbatim nil :foreground nil :inherit '(shadow fixed-pitch) :height 0.85) (set-face-attribute 'org-special-keyword nil :foreground nil :inherit '(font-lock-comment-face fixed-pitch)) (set-face-attribute 'org-meta-line nil :foreground nil :inherit '(font-lock-comment-face fixed-pitch)) (set-face-attribute 'org-checkbox nil :foreground nil :inherit 'fixed-pitch) #+end_src prettify symbols for quotes and source blocks in org-mode. #+begin_src emacs-lisp (defun nemo/prettify-symbols-setup () ;; org-babel (push '("#+BEGIN_SRC" . ?≫) prettify-symbols-alist) (push '("#+END_SRC" . ?≫) prettify-symbols-alist) (push '("#+begin_src" . ?≫) prettify-symbols-alist) (push '("#+end_src" . ?≫) prettify-symbols-alist) (push '("#+BEGIN_QUOTE" . ?❝) prettify-symbols-alist) (push '("#+END_QUOTE" . ?❞) prettify-symbols-alist) (prettify-symbols-mode)) #+end_src =svg-tags-mode= for fancy SVG images #+begin_src emacs-lisp (use-package svg-tag-mode :straight t :config (setq svg-tag-tags '((":TODO:" . ((lambda (tag) (svg-tag-make "TODO")))))) ) #+end_src ** org-agenda #+begin_src emacs-lisp (require 'org) (setq org-agenda-start-on-weekday nil org-agenda-block-separator nil org-agenda-remove-tags t) (use-package org-super-agenda :straight t :after org :config (org-super-agenda-mode)) (setq org-agenda-files (list "~/Shared/agenda.org" "~/notes.org" "~/projects.org")) (add-hook 'emacs-startup-hook (lambda () (progn (org-agenda nil "a") (delete-other-windows) (olivetti-mode)))) #+end_src * Navigation / mini-buffer Realistically, I will probably never use 90% of Helm's functionality, so =vertico= should be sufficient. ** Move Text Use the =move-text= package to move the current line or selection up or down with =M-j= and =M-k=. #+begin_src emacs-lisp (use-package move-text :straight t :defer t :bind (("M-j" . move-text-down) ("M-k" . move-text-up))) #+end_src ** Treemacs #+begin_src emacs-lisp (use-package treemacs :straight t ;; hijack projectile prefix because they fit together :general (general-define-key :prefix-map 'project-prefix-map "t" '("Treemacs" . treemacs-select-window) ) :config (setq treemacs-width 25) ) (use-package treemacs-evil :after (treemacs evil) :straight t) (use-package treemacs-projectile :after (treemacs projectile) :straight t) (use-package treemacs-icons-dired :hook (dired-mode . treemacs-icons-dired-enable-once) :straight t) (use-package treemacs-magit :after (treemacs magit) :straight t) #+end_src ** Vertico #+begin_src emacs-lisp (use-package vertico :straight t :bind (:map minibuffer-local-map ("C-h" . backward-kill-sexp)) :config (vertico-mode 1) (setq vertico-count 25 completion-ignore-case t read-buffer-completion-ignore-case t read-file-name-completion-ignore-case t) (vertico-multiform-mode) ) #+end_src Use =vertico-posframe= to make the =vertico= buffer floating in the centre of the frame. #+begin_src emacs-lisp (use-package vertico-posframe :straight t ;; Ensure posframe is always restored when exiting a minibuffer :hook (minibuffer-exit . (lambda () (vertico-posframe-mode 1))) :config (vertico-posframe-mode 1) (setq vertico-posframe-height vertico-count vertico-multiform-commands '((consult-line (:not posframe)) (consult-ripgrep (:not posframe)) (embark-act (:not posframe)) (t posframe)) )) #+end_src #+begin_src emacs-lisp (use-package savehist :straight t :init (savehist-mode)) #+end_src Use =consult-xref= for =lsp-xref= and =xref-find-references=. #+begin_src emacs-lisp (use-package consult :straight t :bind (:map custom-bindings-map ("C-x b" . consult-buffer) ("M-g g" . consult-goto-line)) :init (setq xref-show-xrefs-function 'consult-xref xref-show-definitions-function 'consult-xref) :config ()) #+end_src #+begin_src emacs-lisp (defun my-consult-line-wrapper () (interactive) (vertico-posframe-mode -1) (consult-line) (vertico-posframe-mode 1)) (defun my-consult-ripgrep-wrapper () (interactive) (vertico-posframe-mode -1) (consult-ripgrep) (vertico-posframe-mode 1)) (bind-key "C-s" 'consult-line custom-bindings-map) (bind-key "C-M-s" 'consult-ripgrep custom-bindings-map) #+end_src [[https://github.com/minad/marginalia][=marginalia=]] adds marginalia into the mini-buffer, for example key-binds in =M-x= #+begin_src emacs-lisp (use-package marginalia :straight t :init (marginalia-mode 1)) #+end_src #+begin_src emacs-lisp :tangle no (use-package vertico-prescient :straight t :after vertico :init (vertico-prescient-mode 1) ) #+end_src ** Company - Auto-Completion #+begin_src emacs-lisp (use-package company :straight t :defer t :hook (after-init . company-mode) :hook (prog-mode . company-mode) :config (setq company-minimum-prefix-length 1 company-idle-delay 0.0)) #+end_src Use =ispell= with company mode for completion in text-modes #+begin_src emacs-lisp (use-package ispell :straight t :defer t :config (setq ispell-dictionary "en_GB")) #+end_src ** COMMENT Corfu - Auto-Completion Auto-completion using =corfu=: #+begin_src emacs-lisp (use-package corfu :straight t :custom ;; Enable auto completion (corfu-auto t) ;; Enable cycling for `corfu-next/previous' (corfu-cycle t) ;; No delay (corfu-auto-delay 0) ;; Start when this many characters have been typed (corfu-auto-prefix 1) ;; Short delay (corfu-popupinfo-delay 0.5) ;; Preselect the first suggestion (corfu-preselect 'first) (corfu-on-exact-match nil) :config (global-corfu-mode)) (use-package corfu-terminal :straight t :defer t :hook (before-make-frame . (lambda () (corfu-terminal-mode (if (display-graphic-p) -1 +1)))) ) (use-package emacs ;; :custom ;; TODO :init ;; TAB cycle if there are only few candidates ;; (setq completion-cycle-threshold 3) (setq enable-recursive-minibuffers t) ;; Hide commands in M-x which do not apply to the current mode. Corfu ;; commands are hidden, since they are not supposed to be used via M-x. (setq read-extended-command-predicate #'command-completion-default-include-p) (setq text-mode-ispell-word-completion nil) ;; Enable indentation+completion using the TAB key. ;; `completion-at-point' is often bound to M-TAB. (setq tab-always-indent 'complete)) #+end_src Use =kind-icon= to decorate corfu completion candidates: #+begin_src emacs-lisp (use-package kind-icon :straight t :after corfu :config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)) #+end_src This package seems to slow down search quite a bit in common buffers like find-file and exectue-extended-comand: #+begin_src emacs-lisp :tangle no (use-package cape :straight t ;; Bind dedicated completion commands ;; Alternative prefix keys: C-c p, M-p, M-+, ... :bind (("C-c p p" . completion-at-point) ;; capf ("C-c p t" . complete-tag) ;; etags ("C-c p d" . cape-dabbrev) ;; or dabbrev-completion ("C-c p h" . cape-history) ("C-c p f" . cape-file) ("C-c p k" . cape-keyword) ("C-c p s" . cape-symbol) ("C-c p a" . cape-abbrev) ("C-c p l" . cape-line) ("C-c p w" . cape-dict) ("C-c p \\" . cape-tex) ("C-c p _" . cape-tex) ("C-c p ^" . cape-tex) ("C-c p &" . cape-sgml) ("C-c p r" . cape-rfc1345)) :init ;; Add `completion-at-point-functions', used by `completion-at-point'. ;; NOTE: The order matters! (add-to-list 'completion-at-point-functions #'cape-dabbrev) (add-to-list 'completion-at-point-functions #'cape-file) (add-to-list 'completion-at-point-functions #'cape-elisp-block) (add-to-list 'completion-at-point-functions #'cape-history) (add-to-list 'completion-at-point-functions #'cape-keyword) (add-to-list 'completion-at-point-functions #'cape-tex) (add-to-list 'completion-at-point-functions #'cape-dict) ;;(add-to-list 'completion-at-point-functions #'cape-sgml) ;;(add-to-list 'completion-at-point-functions #'cape-rfc1345) ;;(add-to-list 'completion-at-point-functions #'cape-abbrev) ;;(add-to-list 'completion-at-point-functions #'cape-symbol) ;;(add-to-list 'completion-at-point-functions #'cape-line) ) #+end_src Use =embark= for in-mini-buffer actions: #+begin_src emacs-lisp :tangle no (use-package embark :straight t :defer t :bind (("C-." . embark-act)) :config ()) (use-package embark-consult :straight t :defer t :hook (embark-collect-mode . consult-preview-at-point-mode)) #+end_src Use =corfu-prescient= to sort completion candidates: #+begin_src emacs-lisp (use-package corfu-prescient :straight t :after prescient :after corfu ;; :hook (corfu-mode . corfu-prescient-mode) ;; :hook (text-mode . corfu-prescient-mode) :config (setq corfu-prescient-completion-styles '(prescient orderless basic partial-completion)) (corfu-prescient-mode 1) ) (defun nemo/reset-corfu-prescient () (interactive) (corfu-prescient-mode -1) (corfu-prescient-mode 1) ) #+end_src ** Orderless & Prescient Install =prescient= package: #+begin_src emacs-lisp (use-package prescient :straight t :config (add-to-list 'completion-styles 'prescient) (add-to-list 'prescient-filter-method 'fuzzy)) #+end_src Fuzzy and out-of-order completion matching using =orderless=: #+begin_src emacs-lisp (use-package orderless :straight t :config (add-to-list 'completion-styles 'orderless) (setq orderless-matching-styles '(orderless-flex) completion-category-overrides '((file (styles basic partial-completion))) orderless-component-separator "[ |]")) #+end_src * Programming Enable =hs-minor-mode= in =prog-mode= to allow for folding away comments and modules/namespaces: #+begin_src emacs-lisp (add-hook 'prog-mode-hook 'hs-minor-mode) #+end_src ** hl-todo #+begin_src emacs-lisp (use-package hl-todo :straight t :hook ((prog-mode) . hl-todo-mode) :config (setq hl-todo-keyword-faces '(("TODO" . "#e78a4e"))) ) #+end_src ** smart-parens #+begin_src emacs-lisp (use-package smartparens :straight t :hook ((prog-mode text-mode markdown-mode) . smartparens-mode) :config (require 'smartparens-config)) #+end_src ** Flycheck #+begin_src emacs-lisp (use-package flycheck :straight t :defer t :hook (after-init. global-flycheck-mode) :config) #+end_src ** Yasnippet Auto-completion requires yasnippet for some competions, such as function arguments and parens. #+begin_src emacs-lisp (use-package yasnippet :straight t :defer t :hook ((prog-mode text-mode) . yas-minor-mode)) (use-package yasnippet-snippets :straight t) #+end_src ** LSP Use lsp-mode and lsp-ui for LSP functionality. Emacs has its own internal LSP client called eglot, but I've never used it and I'm relatively happy with lsp-mode. LSP sets it's prefix key to =s-l= by default, which uses the Super key which I use as my Mod key in sway, so I can't use it in emacs. #+begin_src emacs-lisp (use-package lsp-mode :straight t :defer t :hook (prog-mode . lsp) :hook (lsp-mode . lsp-enable-which-key-integration) :hook (lsp-mode . lsp-inlay-hints-mode) :init (setq lsp-keymap-prefix "C-l") :commands lsp :general-config (leader-def "l" '(:keymap lsp-command-map :wk "LSP") ) :config (setq lsp-idle-delay 0.5 lsp-inlay-hint-enable t lsp-modeline-code-actions-enable t lsp-modeline-code-actions-segments '(name count) lsp-modeline-diagnostics-enable t lsp-completion-enable-additional-text-edit t lsp-rust-server 'rust-analyzer lsp-rust-analyzer-server-display-inlay-hints t lsp-rust-analyzer-proc-macro-enable t lsp-rust-analyzer-completion-auto-import-enable t lsp-rust-analyzer-proc-macro-enable t lsp-rust-analyzer-binding-mode-hints t lsp-rust-analyzer-display-closure-return-type-hints t lsp-rust-analyzer-server-format-inlay-hints t) (setq lsp-glsl-executable '("glsl_analyzer")) (add-to-list 'lsp-language-id-configuration '("CMakeLists.txt" . "cmake")) ) (use-package consult-lsp :straight t :general (with-eval-after-load 'lsp-mode (general-define-key :keymaps 'lsp-command-map "c s" '("Symbols" . consult-lsp-symbols) "c f" '("Symbols" . consult-lsp-file-symbols) "c d" '("Diagnostics" . consult-lsp-diagnostics) ) ) ) (use-package lsp-treemacs :straight t :commands lsp-treemacs-errors-list) (use-package lsp-ui :config (setq ;; lsp-modeline-code-actions-enable nil ;; lsp-rust-analyzer-cargo-all-targets nil ;; lsp-rust-analyzer-server-command "xwin-env rust-analyzer" lsp-ui-sideline-show-code-actions t lsp-ui-doc-enable t lsp-ui-doc-delay 0.5 lsp-ui-doc-show-with-cursor t ; lsp-ui-doc-use-childframe t ; lsp-ui-doc-use-webkit t ) :commands lsp-ui-mode) #+end_src ** Graphics glsl-mode: #+begin_src emacs-lisp (use-package glsl-mode :straight t :defer t :init (add-to-list 'auto-mode-alist '("\\.vert\\'" . glsl-mode)) (add-to-list 'auto-mode-alist '("\\.glsl\\'" . glsl-mode)) (add-to-list 'auto-mode-alist '("\\.frag\\'" . glsl-mode)) ) #+end_src hlsl-mode: #+begin_src emacs-lisp (require 'hlsl-mode) (with-eval-after-load 'hlsl-mode (add-to-list 'auto-mode-alist '("\\.hlsl\\'" . hlsl-mode)) (add-to-list 'auto-mode-alist '("\\.frag\\'" . hlsl-mode)) ) #+end_src ** Rust [[https://github.com/rust-lang/rust-mode][=rust-mode=]] #+begin_src emacs-lisp (use-package rust-mode :straight t ;; :hook (rust-mode . lsp-mode) :init (setq rust-mode-treesitter-derive t )) #+end_src [[https://github.com/brotzeit/rustic][=rustic-mode=]] #+begin_src emacs-lisp (use-package rustic :straight t :defer t :after (rust-mode) :general-config (general-define-key :prefix-map 'nemo/rust-map "c v" '("Check" . rustic-cargo-check) "c c" '("Compile" . rustic-compile) "c b" '("Build" . rustic-cargo-build) ;;"c B" '("Build" . rust-cargo-relea) "c c" '("Clean" . rustic-cargo-clean) "c x" '("Run" . rustic-cargo-run) "c f" '("Format Buffer" . rustic-format-buffer) "t t" '("Current Test" . rustic-cargo-current-test) "t a" '("Test" . rustic-cargo-test) "c a" '("cargo-add" . rustic-cargo-add) "c r" '("cargo-rm" . rustic-cargo-remove) "c l" '("lint" . rustic-cargo-clippy) "c L" '("lint" . rustic-cargo-clippy-fix) "c n" '("new" . rustic-cargo-new) "c i" '("init" . rustic-cargo-init) "c d" '("Docs" . rustic-cargo-doc) ) (leader-major-def :keymaps 'rustic-mode-map "m" '(:keymap nemo/rust-map :wk "Cargo") ) :config (setq rustic-format-on-save t rustic-format-on-save-method #'rustic-format-buffer rustic-analyzer-command '("/usr/bin/rust-analyzer") ) ) #+end_src #+begin_src emacs-lisp (use-package rust-playground :straight t :defer t) #+end_src ** Debugging #+begin_src emacs-lisp (use-package dap-mode :straight t :after (lsp-mode) :config ;; (require 'dap-cpptools) ;; (dap-cpptools-setup) (require 'dap-gdb-lldb) (dap-register-debug-template "Rust::GDB Run Configuration" (list :type "gdb" :request "launch" :name "GDB::Run" :gdbpath "rust-gdb" :target nil :cwd nil)) (dap-auto-configure-mode)) #+end_src ** Web #+begin_src emacs-lisp (use-package web-mode :straight t :defer t :mode (("\\.phtml\\'" . web-mode) ("\\.tpl\\.php\\'" . web-mode) ("\\.twig\\'" . web-mode) ("\\.xml\\'" . web-mode) ("\\.html\\'" . web-mode) ("\\.htm\\'" . web-mode) ("\\.[gj]sp\\'" . web-mode) ("\\.as[cp]x?\\'" . web-mode) ("\\.eex\\'" . web-mode) ("\\.erb\\'" . web-mode) ("\\.mustache\\'" . web-mode) ("\\.handlebars\\'" . web-mode) ("\\.hbs\\'" . web-mode) ("\\.eco\\'" . web-mode) ("\\.ejs\\'" . web-mode) ("\\.svelte\\'" . web-mode) ("\\.ctp\\'" . web-mode) ("\\.djhtml\\'" . web-mode)) :config (setq web-mode-css-indent-offset 2 web-mode-code-indent-offset 2 web-mode-attr-indent-offset 2 web-mode-markup-indent-offset 2)) #+end_src ** C/C++ clang-format #+begin_src emacs-lisp (use-package clang-format :straight t :defer t) #+end_src #+begin_src emacs-lisp (use-package cmake-mode :straight t) #+end_src ** Zig Use =zls= automatically with lsp, and =zig-mode=: #+begin_src emacs-lisp (use-package zig-mode :straight t :config (add-to-list 'auto-mode-alist '("\\.zig\\'" . zig-mode)) (setq lsp-zig-enable-build-on-save t lsp-zig-enable-inlay-hints nil lsp-zig-build-on-save-step "check")) #+end_src * User Functions =PascalCase= to =snake_case=: #+begin_src emacs-lisp (defun snake-case () "convert region from PascalCase into snake_case" (interactive) (save-excursion (let ((bounds (bounds-of-thing-at-point 'symbol))) (replace-regexp "\\([A-Z]\\)" "_\\1" nil (1+ (car bounds)) (cdr bounds)) (downcase-region (car bounds) (cdr (bounds-of-thing-at-point 'word))))) ) #+end_src Shortcut for opening this file: #+begin_src emacs-lisp (defun nemo/edit-init-org () "Open the init.org file." (interactive) (find-file (concat user-emacs-directory "/init.org")) ) #+end_src Dotfiles with =magit=: #+begin_src emacs-lisp (defun nemo/magit-dotfiles () "View Dotfiles repository with magit" (interactive) (require 'magit) (let ((magit-git-global-arguments `(,(substitute-env-vars "--git-dir=$HOME/.cfg") ,(substitute-env-vars "--work-tree=$HOME") ,@magit-git-global-arguments))) (magit-status "~") (recursive-edit))) #+end_src Open the current file with a =sudo= tramp: #+begin_src emacs-lisp (set-default 'tramp-default-proxies-alist (quote ((".*" "\\`root\\'" "/ssh:%h:")))) (require 'tramp) (defun sudo-edit-current-file () (interactive) (let ((position (point))) (find-alternate-file (if (file-remote-p (buffer-file-name)) (let ((vec (tramp-dissect-file-name (buffer-file-name)))) (tramp-make-tramp-file-name "sudo" (tramp-file-name-user vec) (tramp-file-name-host vec) (tramp-file-name-localname vec))) (concat "/sudo:root@localhost:" (buffer-file-name)))) (goto-char position))) (leader-def "fe" 'sudo-edit-current-file) (defun nemo/switch-to-last-buffer () (interactive) (switch-to-buffer nil) ) #+end_src * Keybinds #+begin_src emacs-lisp (define-key custom-bindings-map (kbd "C-c l") 'org-store-link) (define-key custom-bindings-map (kbd "C-c a") 'org-agenda) (define-key custom-bindings-map (kbd "C-c c") 'org-capture) (define-key custom-bindings-map (kbd "C-c t") 'org-todo) (define-key custom-bindings-map (kbd "M-h") 'org-todo) #+end_src