Skip to content

kwpav/emacs-config

Repository files navigation

Emacs Config

About

This is my personal Emacs configuration using basemacs at its core, Meow for editing, and Eglot for coding. It is essentially cobbled together from bits and pieces of other configs I have found online. I have been tweaking it (and will probably continue to tweak it) for several years now. It is a “Literate” config, where prose and code live together in harmony… what this basically means is that configuration code is in this Org file, along with some text that describes what it’s doing.

The primary goal is to (try) to keep things simple. My hope is that this will keep things easy to understand, fast, and bug-free. Some ways I attempt do this are:

  • Using use-package for almost everything.
  • Using built in packages where I think it makes sense (e.g. project.el over projectile).
  • Keeping things modular.

How it Works

The code here is tangled using M-x org-babel-tangle, which creates init.el and various “modules” in the lisp/ directory.

Startup

Let’s get everything setup!

;;; init.el --- -*- lexical-binding: t no-byte-compile: t -*-

Add the lisp/ directory to the load-path so we can load basemacs-core, modules, and any other custom lisp code.

(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

Load the base config. This gives us use-package (along with some other handy packages) and some sane defaults to start with.

(require 'basemacs-core)

Set up the leader keys for general.el.

(use-package basemacs-core
  :custom
  (basemacs-leader "C-c")
  (basemacs-local-leader "C-c m"))

Modules

Here is where the magic happens. Let’s load some modules!

;; emacs
(require 'kpav-defaults)
(require 'kpav-system)
(require 'kpav-meow)
;; (require 'kpav-evil)
(require 'kpav-keys)
(require 'kpav-narrowing)
(require 'kpav-org)
(require 'kpav-ui)
(require 'kpav-windows-and-buffers)
;; prose
(require 'kpav-markdown)
(require 'kpav-org-roam)
;; tools
(require 'kpav-project)
(require 'kpav-git)
(require 'kpav-vterm)
;; (require 'kpav-eshell)
(require 'kpav-docker)
(require 'kpav-irc)
(require 'kpav-guix)
;; code
(require 'kpav-code)
(require 'kpav-php)
(require 'kpav-elisp)
(require 'kpav-clojure)
(require 'kpav-common-lisp)
(require 'kpav-scheme)
(require 'kpav-js)
(require 'kpav-python)
;; (require 'kpav-c)
(require 'kpav-yaml)
;; (require 'kpav-grpc)
(require 'kpav-graphql)
(require 'kpav-nix)

System specific configuration is done in lisp/kpav-local.el. This loads that file if it exists.

(defconst kpav-local-file (expand-file-name "lisp/kpav-local.el" user-emacs-directory))

(when (file-exists-p kpav-local-file)
  (load kpav-local-file))

Emacs

Things that modify Emacs itself.

Opininionated Defaults

Some of my more “opinionated” default settings.

;;; kpav-defaults.el --- -*- lexical-binding: t -*-

Mouse

Add pixel-scroll-precision mode. Emacs treats images like a single character

(use-package emacs
  :straight (:type built-in)
  :custom
  (pixel-scroll-mode +1))

Pair Matching

Insert matching pairs for things like parens, brackets, quotes, etc. Use it in programming and org modes.

(use-package elec-pair
  :straight (:type built-in)
  :gfhook
  ('(prog-mode-hook org-mode-hook) #'electric-pair-mode))

Highlight paren when the cursor is over its matching pair. Also reduce the delay of it to 0 so it will be instant.

(use-package paren
  :straight (:type built-in)
  :custom
  (show-paren-delay 0)
  :config
  (show-paren-mode +1))

Files

Emacs likes to create lots of extra files for things, these settings prevent that. Also, create a newline at the end of the file on save.

(use-package files
  :straight (:type built-in)
  :custom
  (make-backup-files nil)    ;; stop creating backup~ files
  (auto-save-default nil)    ;; stop creating #autosave# files
  (create-lockfiles nil)     ;; stop creating .# files
  ;; (require-final-newline t)
  ) ;; auto add newline at the end of file

When you visit a file, point goes to the last place where it was when you previously visited the same file.

(use-package saveplace
  :straight (:type built-in)
  :config
  (save-place-mode +1))

See recently opened files.

(use-package recentf
  :straight (:type built-in)
  :ghook
  ('after-init-hook #'recentf-mode))

Whitespace

Show whitespace and newlines.

(use-package emacs
  :straight (:type built-in)
  :gfhook
  ('(prog-mode-hook org-mode-hook)
   (lambda ()
     (setq show-trailing-whitespace t
           indicate-buffer-boundaries 'left))))

Periods should be followed by a single space, not double spaces

(use-package emacs
  :straight (:type built-in)
  :config
  (setq sentence-end-double-space nil))

Use spaces for indentation, not tabs. Set the width to 4 instead of the default of 8.

(use-package emacs
  :straight (:type built-in)
  :config
  (setq-default indent-tabs-mode nil
                tab-width 4))

Auto Saving

super-save auto-saves your buffers, when certain events happen - e.g. you switch between buffers, an Emacs frame loses focus, etc.

(use-package super-save
  :straight t
  :init
  :config
  (add-to-list 'super-save-triggers 'ace-window)
  (super-save-mode +1))

Other

Workaround for compat issue and elpa, see: magit/magit#4841 radian-software/straight.el#1049 radian-software/gnu-elpa-mirror#14

;; (straight-use-package '(compat :host github :repo "emacs-compat/compat"))

EOF

(provide 'kpav-defaults)
;;; kpav-defaults.el ends here

Text Editing

Anzu

Find and replace

;;; kpav-text-editing.el --- -*- lexical-binding: t -*-
(use-package anzu
  :straight t
  :config
  (global-anzu-mode +1))
(provide 'kpav-defaults)
;;; kpav-text-editing.el ends here

Meow Mode

;;; kpav-meow.el --- -*- lexical-binding: t -*-

Paren mode

(setq meow-paren-keymap (make-keymap))
(meow-define-state paren
  "meow state for interacting with paredit"
  :lighter " [P]"
  :keymap meow-paren-keymap)

;; meow-define-state creates the variable
(setq meow-cursor-type-paren 'hollow)

(meow-define-keys 'paren
  '("<escape>" . meow-normal-mode)
  '("l" . paredit-forward)
  '("h" . paredit-backward)
  '("j" . paredit-forward-down)
  '("k" . paredit-forward-up)
  '("J" . paredit-forward-slurp-sexp)
  '("K" . paredit-forward-barf-sexp)
  '("L" . paredit-backward-barf-sexp)
  '("H" . paredit-backward-slurp-sexp)
  '("u" . meow-undo))
(use-package meow
  :straight t
  :custom
  (meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
  ;; make "a" act like vim/evil
  (meow-use-cursor-position-hack t)
  :config
  <<paren-mode>>
  (setq meow-replace-state-name-list
   '((normal . "<N>")
     (motion . "MOTION")
     (keypad . "KEYPAD")
     (insert . "<I>")
     (beacon . "BEACON")
     (paren . "<λ>")))
  (meow-leader-define-key
   ;; SPC j/k will run the original command in MOTION state.
   '("j" . "H-j")
   '("k" . "H-k")
   ;; Use SPC (0-9) for digit arguments.
   '("1" . meow-digit-argument)
   '("2" . meow-digit-argument)
   '("3" . meow-digit-argument)
   '("4" . meow-digit-argument)
   '("5" . meow-digit-argument)
   '("6" . meow-digit-argument)
   '("7" . meow-digit-argument)
   '("8" . meow-digit-argument)
   '("9" . meow-digit-argument)
   '("0" . meow-digit-argument)
   '("/" . meow-keypad-describe-key)
   '("?" . meow-cheatsheet))
  (meow-normal-define-key
   '(";" . meow-comment)
   '("/" . ctrlf-forward-fuzzy)
   '("0" . meow-expand-0)
   '("9" . meow-expand-9)
   '("8" . meow-expand-8)
   '("7" . meow-expand-7)
   '("6" . meow-expand-6)
   '("5" . meow-expand-5)
   '("4" . meow-expand-4)
   '("3" . meow-expand-3)
   '("2" . meow-expand-2)
   '("1" . meow-expand-1)
   '("-" . negative-argument)
   '(")" . meow-reverse)
   '("," . meow-inner-of-thing)
   '("." . meow-bounds-of-thing)
   '("[" . meow-beginning-of-thing)
   '("]" . meow-end-of-thing)
   '("a" . meow-append)
   '("A" . meow-open-below)
   '("b" . meow-back-word)
   '("B" . meow-back-symbol)
   '("c" . meow-change)
   '("d" . meow-delete)
   '("D" . meow-backward-delete)
   '("e" . meow-next-word)
   '("E" . meow-next-symbol)
   '("f" . meow-find)
   '("g" . meow-cancel-selection)
   '("G" . meow-grab)
   '("h" . meow-left)
   '("H" . meow-left-expand)
   '("i" . meow-insert)
   '("I" . meow-open-above)
   '("j" . meow-next)
   '("J" . meow-next-expand)
   '("k" . meow-prev)
   '("K" . meow-prev-expand)
   '("l" . meow-right)
   '("L" . meow-right-expand)
   '("m" . meow-join)
   '("n" . meow-search)
   '("o" . meow-block)
   '("O" . meow-to-block)
   '("p" . meow-yank)
   '("q" . meow-quit)
   '("Q" . meow-goto-line)
   '("r" . meow-replace)
   '("R" . meow-swap-grab)
   '("s" . meow-kill)
   '("t" . meow-till)
   '("u" . meow-undo)
   '("U" . meow-undo-in-selection)
   '("v" . meow-visit)
   '("w" . meow-mark-word)
   '("W" . meow-mark-symbol)
   '("x" . meow-line)
   '("X" . meow-goto-line)
   '("y" . meow-save)
   '("Y" . meow-sync-grab)
   '("z" . meow-pop-selection)
   '("'" . repeat)
   '("<escape>" . ignore))

  (meow-global-mode 1))
(provide 'kpav-meow)
;;; kpav-meow.el ends here

Evil Mode

;;; kpav-evil.el --- -*- lexical-binding: t -*-

Evil mode is vim in Emacs! Using undo-fu here instead of undo-tree as I have found that undo-fu seems to be quicker and less buggy than undo-tree.

(use-package evil
  :straight t
  :general
  ;; make <tab> expand things in org mode for evil
  (general-nmap org-mode-map
    "<tab>" 'org-cycle)
  :init
  (use-package undo-fu :straight t)
  (setq evil-want-keybinding nil ;; evil-collection assumes this
        evil-undo-system 'undo-fu
        evil-disable-insert-state-bindings t) ;; emacs keys in insert mode
  :config
  (general-evil-setup)
  (evil-mode +1))

The bit about using Emacs keybinds in evil mode is from https://stackoverflow.com/questions/25542097/emacs-evil-mode-how-to-change-insert-state-to-emacs-state-automatically

Use evil keys in various modes..

(use-package evil-collection
  :straight t
  :after evil
  :config
  (evil-collection-init))

surround.vim emulation.

(use-package evil-surround
  :straight t
  :after evil
  :config
  (global-evil-surround-mode +1))

vim-commentary emulation

(use-package evil-commentary
  :straight t
  :config
  (evil-commentary-mode +1))
(provide 'kpav-evil)
;;; kpav-evil.el ends here

Keys

;;; kpav-keys.el --- -*- lexical-binding: t -*-

Press any two keys for keybinds.

(use-package key-chord
  :straight t
  :config
  (key-chord-mode +1))
(provide 'kpav-keys)
;;; kpav-keys.el ends here

UI

;;; kpav-ui.el --- -*- lexical-binding: t -*-

Fonts

Set up fonts, This sets up the default typeface, and the ones to be used in variable-pitch-mode, variable-pitch and fixed-pitch.

(use-package faces
  :straight (:type built-in)
  :init
  ;; Main typeface
  (set-face-attribute 'default nil :family "Recursive Mn Lnr St" :height 130)
  ;; Proportionately spaced typeface
  (set-face-attribute 'variable-pitch nil :family "Recursive Sn Lnr St" :height 1.0)
  ;; Monospaced typeface
  (set-face-attribute 'fixed-pitch nil :family "Recursive Mn Lnr St" :height 1.0))

Increase the line spacing to let the text breathe a bit.

(use-package emacs
  :straight (:type built-in)
  :init
  (setq-default line-spacing 4))

Catpuccin

(use-package catppuccin-theme
  :straight t
  :custom
  (catppuccin-flavor 'mocha)
  ;; (catppuccin-flavor 'latte)
  ;; (catppuccin-flavor 'latte)
  ;; (catppuccin-flavor 'latte)
  :config
  (load-theme 'catppuccin :no-confirm))

Variable Pitch

Variable Pitch allows us to have multiple fonts in a single buffer. This is useful for Org Mode which can have prose, code, and other things, in the same file.

Turn variable-pitch-mode on for org-mode.

(use-package face-remap
  :straight (:type built-in)
  :after org
  :gfhook
  ('org-mode-hook #'variable-pitch-mode)
  :config
  ;; from https://zzamboni.org/post/beautifying-org-mode-in-emacs/
  (custom-theme-set-faces
   'user
   '(org-block ((t (:inherit fixed-pitch))))
   '(org-code ((t (:inherit (shadow fixed-pitch)))))
   '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
   '(org-indent ((t (:inherit (org-hide fixed-pitch)))))
   '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
   '(org-property-value ((t (:inherit fixed-pitch))) t)
   '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
   '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8))))
   '(org-verbatim ((t (:inherit (shadow fixed-pitch)))))))

Modeline

Set up for doom-modeline. Nice looking modeline that plays well with evil and lots of other stuff.

You need to run M-x all-the-icons-install-fonts to get the fancy fonts in the modeline

(use-package all-the-icons
  :straight t
  :defer t)

column-number-mode displays the cursors current line on the modeline

(use-package doom-modeline
  :straight t
  :demand t
  :preface
  :init
  (column-number-mode +1)
  :ghook
  'after-init-hook
  :custom
  (doom-modeline-icon nil)
  (doom-modeline-vcs-max-length 50)
  (doom-modeline-buffer-file-name-style 'auto)
  (doom-modeline-buffer-encoding nil)
  (doom-modeline-indent-info nil)
  (doom-modeline-major-mode-icon nil)
  (doom-modeline-modal-icon nil)
  (doom-modeline-persp-name t)
  (doom-modeline-workspace-name nil))

Dashboard

(use-package dashboard
  :straight t
  :custom
  ;; (dashboard-startup-banner 'logo)
  (dashboard-startup-banner (expand-file-name "img/gnu_color.png" user-emacs-directory))
  (dashboard-center-content t)
  (dashboard-projects-backend 'project-el)
  (dashboard-items '((recents . 5)
                     (bookmarks . 5)
                     (projects . 5)
                     (agenda . 5)))
  :config
  (dashboard-setup-startup-hook))

Rainbow Delimiters

Add rainbow delimiters in all programming language modes

(use-package rainbow-delimiters
  :straight t
  :ghook
  ('prog-mode-hook #'rainbow-delimiters-mode))

Cursor

Don’t blink the cursor.

(use-package frame
  :straight (:type built-in)
  :config
  (blink-cursor-mode -1))

Highlight the line the cursor is on.

(use-package hl-line
  :straight (:type built-in)
  :config
  (global-hl-line-mode +1))

EOF

(provide 'kpav-ui)
;;; kpav-ui.el ends here

Windows and Buffers

;;; kpav-windows-and-buffers.el --- -*- lexical-binding: t -*-

Keys

(use-package window
  :straight (:type built-in)
  ;:general
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "b" '(:ignore t :wk "buffers")
  ;;   "w" '(:ignore t :wk "windows")
  ;;   ;; "bb" 'switch-to-buffer
  ;;   ;; "bb" 'consult-buffer
  ;;   ;; "bk" 'kill-buffer
  ;;   "wo" 'split-window-horizontally
  ;;   "wu" 'split-window-vertically
  ;;   "wd" 'delete-window)
  :config
  (meow-leader-define-key
   '("wo" . split-window-horizontally)
   '("wu" . split-window-vertically)
   '("wd" . delete-window)))

Navigation

Windmove provides a way to move around emacs windows.

Default keybindings are: S-arrowkey (e.g. S-Left) to move around

(use-package windmove
  :straight (:type built-in)
  ;:general
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "wh" 'windmove-left
  ;;   "wj" 'windmove-down
  ;;   "wk" 'windmove-up
  ;;   "wl" 'windmove-right)
  :init
  (meow-leader-define-key
   '("wh" . windmove-left)
   '("wj" . windmove-down)
   '("wk" . windmove-up)
   '("wl" . windmove-right))
  :config
  (windmove-default-keybindings))

ace-window lets you jump around windows with a single key

(use-package ace-window
  :straight t
  :general
  ("M-o" 'ace-window)
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "ww" 'ace-window)
  :custom
  ;; use home row instead of numbers
  (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  :init
  (meow-leader-define-key
   '("ww" . ace-window)))

perspective

Default key is C-x x. Change it with perp-mode-prefix-key Each frame gets its own perspective. Switch buffer command only looks at current perspective

(use-package perspective
  :straight t
  :custom
  (persp-suppress-no-prefix-key-warning t)
  ;:general
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "b`" 'persp-switch-by-number
  ;;   "bb" 'persp-switch-to-buffer
  ;;   "bk" 'persp-remove-buffer
  ;;   "bc" 'persp-kill
  ;;   "br" 'persp-rename
  ;;   "ba" 'persp-add-buffer
  ;;   "bA" 'persp-set-buffer
  ;;   "bi" 'persp-import
  ;;   "bn" 'persp-next
  ;;   "bp" 'persp-prev
  ;;   "bm" 'persp-merge
  ;;   "bu" 'persp-unmerge
  ;;   "bg" 'persp-add-buffer-to-frame-global
  ;;   "b C-s" 'persp-state-save
  ;;   "b C-l" 'persp-state-load
  ;;   "bs" 'persp-switch)
  :init
  (meow-leader-define-key
   ;; '("bb" . persp-switch-to-buffer)
   '("bk" . persp-remove-buffer)
   '("bc" . persp-kill)
   '("br" . persp-rename)
   '("ba" . persp-add-buffer)
   '("bA" . persp-set-buffer)
   '("bi" . persp-import)
   '("bn" . persp-next)
   '("bp" . persp-prev)
   '("bm" . persp-merge)
   '("bu" . persp-unmerge)
   '("bg" . persp-add-buffer-to-frame-global)
   '("bS" . persp-state-save)
   '("bL" . persp-state-load)
   '("bs" . persp-switch))
  :config
  (persp-mode +1))

EOF

(provide 'kpav-windows-and-buffers)
;;; kpav-windows-and-buffers.el ends here

Narrowing

;;; kpav-narrowing.el --- -*- lexical-binding: t -*-

Set up Vertico and various packages that play well with it.

Vertico

Vertico comes with several extensions in an extensions/ folder. These don’t get automatically loaded with :straight t, so it needs a custom recipe.

(use-package vertico
  :straight (vertico :files (:defaults "extensions/*")
                     :includes (vertico-buffer
                                vertico-directory
                                vertico-flat
                                vertico-indexed
                                vertico-mouse
                                vertico-quick
                                vertico-repeat
                                vertico-reverse))
  :init
  (vertico-mode +1))

A posframe extension to display it outside of the minibuffer.

(use-package vertico-posframe
  :straight t
  :custom
  (vertico-posframe-parameters
   '((left-fringe . 10)
     (right-fringe . 10)))
  (vertico-posframe-poshandler #'posframe-poshandler-frame-center))

The multiform extension allows configuration per command. Using reverse by default, which I like because what you type does not move positions. Using buffer for ripgrep results as the list could be long. NOTE - seems that reverse is unusable with vertico-posframe!

(use-package vertico-multiform
  :custom
  ;; (vertico-buffer-display-action
  ;;  (const :tag "Bottom of frame"
  ;;         (display-buffer-at-bottom
  ;;          (window-height . ,(+ 3 vertico-count)))))
  (vertico-multiform-commands
   '((consult-ripgrep buffer)
     (t posframe)))
  :init
  (vertico-multiform-mode +1))
;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :straight (:type built-in)
  :init
  (savehist-mode +1))

;; A few more useful configurations...
(use-package emacs
  :straight (:type built-in)
  :init
  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  (setq read-extended-command-predicate
        #'command-completion-default-include-p)
  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

Orderless

(use-package orderless
  :straight t
  :init
  (setq completion-styles '(orderless basic)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

ctrlf

a from scratch redesigned buffer-search interface

A replacement for the built in I-Search. This replaces the evil search and the built in search. The bit in general handles evil and ctrlf-mode automatically redefines C-s.

  • C-s - forward search
  • C-r - backward search
(use-package ctrlf
  :straight t
  ;:general
  ;(evil-normal-state-map "/" 'ctrlf-forward-fuzzy-regexp)
  :init
  (ctrlf-mode +1))

Marginalia

Marginalia adds information to the completions provided by Vertico (keybinds, info about command, etc.)

(use-package marginalia
  :straight t
  :general
  ("M-A" 'marginalia-cycle)
  (:keymaps
   'minibuffer-local-map
   "M-A" 'marginalia-cycle)
  :init
  (marginalia-mode +1))

Consult

Consult provides practical commands based on the Emacs completion function completing-read.

Keys copied from projects README.

(use-package consult
  ;; :after projectile ;; needed to set `consult-project-root-function'
  :straight t
  :general
  ;; C-c bindings (mode-specific-map)
  ("C-c h" 'consult-history)
  ("C-c m" 'consult-mode-command)
  ;; ("C-c b" 'consult-bookmark)
  ("C-c k" 'consult-kmacro)
  ;; C-x bindings (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
  ;; Custom M-# bindings for fast register access
  ("M-#" 'consult-register-load)
  ("M-'" 'consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
  ("C-M-#" 'consult-register)
  ;; Other custom bindings
  ("M-y" 'consult-yank-pop)                ;; orig. yank-pop
  ("<help> a" 'consult-apropos)            ;; orig. apropos-command
  ;; M-g bindings (goto-map)
  ("M-g e" 'consult-compile-error)
  ("M-g f" 'consult-flycheck)              ;; 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 (search-map)
  ("M-s f" 'consult-find)
  ("M-s F" '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 m" 'consult-multi-occur)
  ("M-s k" 'consult-keep-lines)
  ("M-s u" 'consult-focus-lines)
  ;; Isearch integration
  ("M-s e" 'consult-isearch)
  (:keymaps
   'isearch-mode-map
   "M-e" 'consult-isearch                 ;; orig. isearch-edit-string
   "M-s e" 'consult-isearch               ;; 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
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "pg" 'consult-ripgrep
  ;;   "pG" 'consult-git-grep)
  :init
  (meow-leader-define-key
   '("rb" . consult-bookmark)
   '("bb" . consult-buffer)
   '("pg" . consult-ripgrep)
   '("pG" . consult-git-grep))
  ;; Install ripgrep for consult-ripgrep
  (use-package ripgrep :straight t)

  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (setq register-preview-delay 0
        register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  ;; (advice-add #'register-preview :override #'consult-register-window)

  ;; Optionally replace `completing-read-multiple' with an enhanced version.
  (advice-add #'completing-read-multiple :override #'consult-completing-read-multiple)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)
  :config
  ;; projectile
  ;; for this t0 work, either need to autoload it, or use :after projectile
  ;; (autoload 'projectile-project-root "projectile")
  ;; (setq consult-project-root-function #'projectile-project-root)
  ;; project.el
  ;; (setq consult-project-root-function
  ;;       (lambda ()
  ;;         (when-let (project (project-current))
  ;;           (car (project-roots project)))))
  )

Embark

(use-package embark
  :straight t
  :general
  ;; ("C-'" 'embark-act)
  ;; ("C-;" 'embark-dwim)
  ("C-h B" 'embark-bindings)
  ("C-;" 'embark-act)
  ("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)
  :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
  :straight t
  :after (embark consult)
  :demand t ; only necessary if you have the hook below
  ;; if you want to have consult previews as you move around an
  ;; auto-updating embark collect buffer
  :ghook
  ('embark-collect-mode #'consult-preview-at-point-mode))

EOF

(provide 'kpav-narrowing)
;;; kpav-narrowing.el ends here

System Specific

;;; kpav-system.el --- -*- lexical-binding: t -*-

Ensure that Emacs uses the correct environment. This is especially useful for OSX, as Emacs may inherit a default set of environment variables, not the ones that you see in a terminal.

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns x))
  :straight t
  :config
  (exec-path-from-shell-initialize))

The railwaycat/emacs-mac port maps the command key to meta, I like it bound to super because it matches my external keyboard better.

(use-package emacs
  :straight (:type built-in)
  :if (eq system-type 'darwin)
  :custom
  (mac-option-modifier 'meta)
  (mac-command-modifier 'super))
(provide 'kpav-system)
;;; kpav-system.el ends here

Random

Make ESC close any prompts

(use-package emacs
  :straight (:type built-in)
  :general
  ("<escape>" 'keyboard-escape-quit))

Stop warnings buffer from automatically coming up. (Emacs 28)

(use-package emacs
  :straight (:type built-in)
  :init
  (setq native-comp-async-report-warnings-errors nil))

Get list of minor modes in current buffer

(defun kpav/minor-modes ()
  (interactive)
  (completing-read
   "Minor modes: "
   local-minor-modes
   nil t))

Prose

Modes and other things that deal with prose.

Org Mode

;;; kpav-org.el --- -*- lexical-binding: t -*-
  • Use evil way of closing and quitting (:q :x) when editing code in org mode
  • Define some keys
  • Disable the checkdock check because most of my elisp code is in snippets in this config
  • org-use-fast-todo-selection
    • Change the status of the todo state by pressing C-c C-c t <KEY>
    • the <KEY> is the the letter in the parens after the state (e.g. TODO(t))
  • org-tempo adds Structure Template completion
    • e.g. <s TAB turns into #+begin_src / #end_src
(use-package org
  :straight t
  ;:general
  ;; (org-src-mode-map
  ;;  [remap evil-save-and-close] 'org-edit-src-exit
  ;;  [remap evil-save-modified-and-close] 'org-edit-src-exit
  ;;  [remap evil-quit] 'org-edit-src-abort)
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "a" 'org-agenda)
  ;; (base-local-leader-def
  ;;   :keymaps 'org-mode-map
  ;;   :states 'normal
  ;;   "b" 'org-babel-tangle)
  :preface
  (defun my-disable-flycheck-for-elisp ()
    (setq flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
  :gfhook
  ('org-src-mode-hook #'my-disable-flycheck-for-elisp)
  #'visual-line-mode
  :init
  (meow-leader-define-key
   '("a" . org-agenda))
  (setq org-startup-indented t)
  :custom
  (org-agenda-files (list "~/org/"))
  (org-use-fast-todo-selection t)
  (org-todo-keywords
   '((sequence "TODO(t)" "NEXT(n)" "CURRENT(c)" "|" "DONE(d)")
     (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(a@/!)")))
  ;; Styling
  (org-auto-align-tags nil)
  (org-tags-column 0)
  (org-hide-emphasis-markers t)
  (org-pretty-entities t)
  (org-ellipsis "")
  :config
  ;; structure template completion
  (require 'org-tempo))

Install org-contrib

(use-package org-contrib
  :straight t)

Prettify headings and lists with org-superstar-mode

(use-package org-superstar
  :straight t
  :ghook
  ('org-mode-hook (lambda () (org-superstar-mode +1))))

Capture Templates

  • agenda.org is for my tasks
  • slipbox.org is where I put my “fleeting” notes
  • grow.org is for my garden
(use-package org-capture
  :straight nil
  ;:general
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "C" 'org-capture)
  :custom
  (org-capture-templates
   '(;; ("t" "Todo" entry (file "~/org/agenda.org")
     ;;  "* TODO %?\n %i\n %^{LINK}p")
     ("t" "Agenda Todo" entry (file "~/org/agenda.org"))
     ("s" "Slipbox" entry (file+datetree "~/org/roam/inbox.org")
      "* %?\n")
     ("g" "Grow Log" entry (file+olp+datetree "~/grow/grow.org" "Log")
      "* Day Xn\n** Log\n** Notes\n %?\n %i\n"))
   :init
   (meow-leader-define-key
    '("C" . org-capture))))

Org babel languages TODO: figure out a way to put this in language specific configs

(org-babel-do-load-languages
 'org-babel-load-languages
 '((clojure . t)
   (dot . t)
   (emacs-lisp . t)
   (js . t)
   (python . t)
   (sql . t)))
(provide 'kpav-org)
;;; kpav-org.el ends here

Org Roam

;;; kpav-org-roam.el --- -*- lexical-binding: t -*-

Trying out Org-roam to handle my note taking instead of my previous attempt at using a single notes.org and capture templates. I had a hard time keeping things organized, even though I don’t take a large amount of notes. I think this is because the approach of having a single file with headers is too rigid for me, as I feel like I need to be meaningful with my headers or things would get too cluttered, but then things get cluttered anyway as I end up sticking unrelated things in a single header.

Org-roam promotes the Zettelkasten Method. Basically, you make small notes and link them together. This approach should allow me to take and delete as many (or as few…) notes as I need without abandon, while Org-roam allows me to easily add, link, and search through them. I can then change things as I need by adding new subfolders or filetags to things. I can even keep using large org files for things if I want (like my blog).

Note structure is:

  • daily/ - used for org-roam-dailies
  • main/ - the default place for notes
  • reference/ - notes about books, articles, videos, etc.
  • work/ - notes for work
    • jira/ - for jira tickets, will most likely link to other notes in work/
;; (use-package emacsql-sqlite-module
;;   :straight t)
(use-package emacsql-sqlite3
  :straight t)

;; (use-package emacsql-sqlite-builtin
;;   :straight t)

(use-package org-roam
  :straight t
  :after emacsql-sqlite3
  :custom
  (org-roam-database-connector 'sqlite3)
  ;; (org-roam-database-connector 'sqlite-builtin)
  (org-roam-directory (file-truename "~/org/roam"))
  (org-roam-capture-templates
   '(("m" "main" plain "%?"
      :if-new (file+head "main/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)
     ("w" "work" plain "%?"
      :if-new (file+head "work/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)
     ("r" "reference" plain "%?"
      :if-new (file+head "reference/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)
     ("j" "jira" plain "* TODO ${title}\nhttps://reifyhealth.atlassian.net/browse/${title}\n%?"
      :if-new (file+head "work/jira/%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+filetags: :jira:\n")
      :unnarrowed t)))
  (org-roam-dailies-directory "daily/")
  (org-roam-dailies-capture-templates
   '(("d" "default" entry
      "* %?"
      :target (file+head "%<%Y-%m-%d>.org"
                         "#+title: %<%Y-%m-%d>\n"))))
  :ghook
  ('after-init-hook #'org-roam-db-autosync-mode)
  ;:general
  ;; (base-leader-def
  ;;   :states '(normal visual)
  ;;   "n" '(:ignore :wk "notes")
  ;;   "ni" 'org-roam-node-insert
  ;;   "nf" 'org-roam-node-find
  ;;   "nc" 'org-roam-node-capture
  ;;   "nb" 'org-roam-buffer-toggle
  ;;   "nd" '(:ignore :wk "dailies")
  ;;   "ndt" 'org-roam-dailies-capture-today
  ;;   "ndT" 'org-roam-dailies-goto-today
  ;;   "ndy" 'org-roam-dailies-capture-yesterday
  ;;   "ndY" 'org-roam-dailies-goto-yesterday)
  :init
  (meow-leader-define-key
   '("ni" . org-roam-node-insert)
   '("nf" . org-roam-node-find)
   '("nc" . org-roam-node-capture)
   '("nb" . org-roam-buffer-toggle)
   '("ndt" . org-roam-dailies-capture-today)
   '("ndT" . org-roam-dailies-goto-today)
   '("ndy" . org-roam-dailies-capture-yesterday)
   '("ndY" . org-roam-dailies-goto-yesterday))
  :config
  ;; Display the `node' (e.g. main/work/jira) and filetags when searching
  ;; from https://jethrokuan.github.io/org-roam-guide/
  (cl-defmethod org-roam-node-type ((node org-roam-node))
    "Return the TYPE of NODE."
    (condition-case nil
        (file-name-nondirectory
         (directory-file-name
          (file-name-directory
           (file-relative-name (org-roam-node-file node) org-roam-directory))))
      (error "")))
  (setq org-roam-node-display-template
        (concat "${type:15} ${title:*} " (propertize "${tags:10}" 'face 'org-tag))))
(use-package org-roam-ui
  :straight
  (:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
  :after org-roam
  :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)
  ;:general
  ;; (base-leader-def
  ;;   :states '(normal visual)
  ;;   "nu" 'org-roam-ui-mode)
  :init
  (meow-leader-define-key
   '("nu" . org-roam-ui-mode)))
(provide 'kpav-org-roam)
;;; kpav-org-roam.el ends here

Markdown

;;; kpav-markdown.el --- -*- lexical-binding: t -*-
(use-package markdown-mode
  :straight t
  :defer t)
(provide 'kpav-markdown)
;;; kpav-markdown.el ends here

Tools

Add and configure some additional tools.

Git

;;; kpav-git.el --- -*- lexical-binding: t -*-

I only use git for version control, and I assume most others do as well. This is probably why there are so many great git packages for Emacs!

magit

Magit may be one of the best front ends for git ever. It makes using git, both the simple and complex parts of it, easy and intuitive to use, right within Emacs! It also provides some neat stuff like spinoff, which will create a branch of any unpushed commits. This is handy if you accidentally starting commiting work to the wrong branch, e.g. master or develop.

I have a function here which copies the current branch name. This is handy if you need the branch name for something like a CI/CD system.

(use-package magit
  :straight t
  :defer t
  :general
  ("C-x g" 'magit-status)
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "g" '(:ignore t :wk "git")
  ;;   "gs" 'magit-status
  ;;   "gc" 'magit-checkout
  ;;   "gC" 'magit-commit
  ;;   "gb" 'magit-blame
  ;;   "gS" 'magit-stage-file
  ;;   "gU" 'magit-unstage-file
  ;;   "gy" 'kpav/magit-yank-branch-name)
  :init
  (meow-leader-define-key
   '("ss" . magit-status)
   '("sc" . magit-checkout)
   '("sC" . magit-commit)
   '("sb" . magit-blame)
   '("sS" . magit-stage-file)
   '("sU" . magit-unstage-file)
   '("sy" . kpav/magit-yank-branch-name))
  :config
  (defun kpav/magit-yank-branch-name ()
    "Show the current branch in the echo-area and add it to the `kill-ring'."
    (interactive)
    (let ((branch (magit-get-current-branch)))
      (if branch
          (progn (kill-new branch)
                 (message "%s" branch))
        (user-error "There is not current branch")))))

git-gutter-fringe

Add git change icons in the fringe, e.g. when somethings been added, changed, or removed. Modus themes makes this look decent.

(use-package git-gutter-fringe
  :straight t
  :config
  (global-git-gutter-mode +1)
  (setq-default fringes-outside-margins t))

git-timemachine

Go through commit history on a file.

(use-package git-timemachine
  :straight t
  :defer t
  ;:general
  ;; (base-leader-def
  ;;   :states 'normal
  ;;   "gt" 'git-timemachine)
  :init
  (meow-leader-define-key
   '("gt" . git-timemachine)))

git-link

(use-package git-link
  :straight t
  ;:general
  ;; (base-leader-def
  ;;   :states '(normal visual)
  ;;   "gl" '(:ignore t :wk "git link")
  ;;   "gll" 'git-link
  ;;   "glc" 'git-link-commit
  ;;   "glh" 'git-link-homepage)
  :init
  (meow-leader-define-key
   '("sll" . git-link)
   '("slc" . git-link-commit)
   '("slh" . git-link-homepage)))

EOF

(provide 'kpav-git)
;;; kpav-git.el ends here

Projects

;;; kpav-project.el --- -*- lexical-binding: t -*-

A large part of my workflow is working in projects, which may or may not interact together, so I like to only open and interact with files and buffers on a per project basis. I use persp-mode to create perspectives for each project, then use the following packages to further interact with them, e.g. opening files or searching for some specific text.

project.el

Using the built-in project.el. It works with marginalia to give extra info about files and buffers

(use-package project
  :straight t
  ;:general
  ;; (base-leader-def
  ;;  :states 'normal
  ;;  "p" '(:ignore t :wk "projects")
  ;;  "pp" 'project-switch-project
  ;;  "pf" 'project-find-file
  ;;  "pd" 'project-find-dir
  ;;  "pb" 'project-switch-to-buffer)
  :init
  (meow-leader-define-key
   '("pp" . project-switch-project)
   '("pf" . project-find-file)
   '("pd" . project-find-dir)
   '("pb" . consult-project-buffer)))

;; (use-package projectile
;;   :straight t
;;   :init
;;   (use-package ripgrep :straight t)
;;   :general
;;   (base-leader-def
;;    :states 'normal
;;    "p" '(:ignore t :wk "projects")
;;    "pd" 'projectile-find-dir
;;    "pp" 'projectile-switch-project
;;    "pP" 'projectile-switch-open-project)
;;   :config
;;   (projectile-mode +1))

Treemacs

Treemacs provides a nice file explorer for projects.

(use-package treemacs
  :straight t
  :defer t
  :general
  ([f8] 'treemacs)
  ;; (base-leader-def
  ;;   :states '(normal)
  ;;   "po" 'treemacs)
  :init
  (meow-leader-define-key
   '("po" . treemacs)))

;; (use-package treemacs-evil
;;   :straight t
;;   :after (evil treemacs))

(use-package treemacs-magit
  :straight t
  :after (treemacs magit))

Direnv

Provide direnv support for projects with .envrc files.

(use-package envrc
  :straight t
  :config
  (envrc-global-mode +1))

Use envrc-allow to explicitly run direnv allow.

Editorconfig

(use-package editorconfig
  :straight t
  :config
  (editorconfig-mode +1))

EOF

(provide 'kpav-project)
;;; kpav-project.el ends here

Terminal

vterm

;;; kpav-vterm.el --- -*- lexical-binding: t -*-

This provides a terminal emulator powered by libvterm. It is essentially a full terminal emulator.

(use-package vterm
  :straight t)

Open multiple vterms. Evil configuration from it’s README.

(use-package multi-vterm
  :straight t
  ;:general
  ;; (base-leader-def
  ;;  :states 'normal
  ;;  "t" '(:ignore t :wk "term")
  ;;  "tt" 'multi-vterm
  ;;  "tn" 'multi-vterm-next
  ;;  "tp" 'multi-vterm-prev)
  :init
  (meow-leader-define-key
   '("tt" . multi-vterm)
   '("tn" . multi-vterm-next)
   '("tp" . multi-vterm-prev))
  ;;:gfhook #'evil-insert-state
  ;:config
  ;(define-key vterm-mode-map [return]                      #'vterm-send-return)
  ;(setq vterm-keymap-exceptions nil)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-e")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-f")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-a")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-v")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-b")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-w")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-u")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-d")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-n")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-m")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-p")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-j")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-k")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-r")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-t")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-g")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-c")      #'vterm--self-insert)
  ;(evil-define-key 'insert vterm-mode-map (kbd "C-SPC")    #'vterm--self-insert)
  ;(evil-define-key 'normal vterm-mode-map (kbd "C-d")      #'vterm--self-insert)
  ;(evil-define-key 'normal vterm-mode-map (kbd ",c")       #'multi-vterm)
  ;(evil-define-key 'normal vterm-mode-map (kbd ",n")       #'multi-vterm-next)
  ;(evil-define-key 'normal vterm-mode-map (kbd ",p")       #'multi-vterm-prev)
  ;(evil-define-key 'normal vterm-mode-map (kbd "i")        #'evil-insert-resume)
  ;(evil-define-key 'normal vterm-mode-map (kbd "o")        #'evil-insert-resume)
  ;(evil-define-key 'normal vterm-mode-map (kbd "<return>") #'evil-insert-resume)
  )
(provide 'kpav-vterm)
;;; kpav-vterm.el ends here

eshell

;;; kpav-eshell.el --- -*- lexical-binding: t -*-
(use-package eshell
  :straight t)

(use-package multi-eshell
  :straight t
  :general
  (base-leader-def
    :states 'normal
    "ee" 'multi-eshell
    "en" 'multi-eshell-switch-to-next-live-shell
    "ep" 'multi-eshell-go-back))
(provide 'kpav-eshell)
;;; kpav-eshell.el ends here

Docker

;;; kpav-docker.el --- -*- lexical-binding: t -*-

Get syntax highlighting for Dockerfile files

(use-package dockerfile-mode
  :straight t
  :defer t)

Use docker commands in Emacs

(use-package docker
  :straight t
  :defer t)
(provide 'kpav-docker)
;;; kpav-docker.el ends here

IRC

;;; kpav-irc.el --- -*- lexical-binding: t -*-

Passwords are stored in the ~/.authinfo file, with the format:

machine irc.libera.chat login kpav password <PASSWORD> port 6697
(use-package erc
  :straight (:type built-in)
  :defer t
  :custom
  (erc-server "irc.libera.chat")
  (erc-nick "kpav")
  (erc-autojoin-channels-alist  '(("irc.libera.chat" "#emacs" "#clojure" "#sr.ht" "#archlinux" "#stumpwm"))))
(provide 'kpav-irc)
;;; kpav-irc.el ends here

Guix

;;; kpav-guix.el --- -*- lexical-binding: t -*-
(use-package guix
  :straight t
  :defer t)
(provide 'kpav-guix)
;;; kpav-guix.el ends here

Code

Things that modify coding stuff (mostly programming langs and LSP).

General

Things that can be used across all languages.

;;; kpav-code.el --- -*- lexical-binding: t -*-

Code Completion

Works with Orderless and LSPs (lsp-mode and eglot)

Corfu

(use-package corfu
  :straight t
  :custom
  (corfu-auto t)
  (corfu-quit-no-match 'separator)
  :init
  (global-corfu-mode))

;; A few more useful configurations...
(use-package emacs
  :straight (:type built-in)
  :init
  ;; TAB cycle if there are only few candidates
  (setq completion-cycle-threshold 3)

  ;; Emacs 28: 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)

  ;; Enable indentation+completion using the TAB key.
  ;; `completion-at-point' is often bound to M-TAB.
  (setq tab-always-indent 'complete))

Error Checking - Flymake

FlyMake is the built-in syntax checker for Emacs. It works for all (most?) of the languages that I use. Trying this out instead of Flycheck in my move to stick to built-in packages.

(use-package flymake
  :straight t
  :ghook
  ('prog-mode-hook #'flymake-mode-on))

Snippets

YA Snippet is a template system for Emacs.

Set the snippets directory to be in this folder, automatically create it if it does not exist.

(use-package yasnippet
  :straight t
  :preface
  (defconst basemacs-snippets-dir (expand-file-name "snippets/" user-emacs-directory))
  (make-directory basemacs-snippets-dir :parents)
  :custom
  (yas-snippet-dirs (list basemacs-snippets-dir))
  :config
  (yas-global-mode +1))

Install the official snippet collection, this contains snippets for several programming languages.

(use-package yasnippet-snippets
  :straight t
  :after yasnippet)

Eglot

Emacs Polyglot: an Emacs LSP client that stays out of your way:

A simple (compared to lsp-mode) way to use Language Servers in Emacs. It uses built-in commands (e.g. xref-find-definitions) and packages (e.g. flymake).

(use-package eglot
  :straight t
  :custom
  (eglot-connect-timeout 120)
  ;:general
  ;; (base-leader-def
  ;;   :states '(normal visual)
  ;;   "l" '(:ignore :wk "lsp")
  ;;   "lg" 'xref-find-definitions
  ;;   "lr" 'xref-find-references
  ;;   "lb" 'xref-go-back
  ;;   "lf" 'eglot-format
  ;;   "lF" 'eglot-format-buffer
  ;;   "la" 'eglot-code-actions
  ;;   "lo" 'eglot-code-action-organize-imports
  ;;   "lR" 'eglot-rename
  ;;   "lh" 'eldoc)
  :init
  (meow-leader-define-key
   '("lg" . xref-find-definitions)
   '("lr" . xref-find-references)
   '("lb" . xref-go-back)
   '("lf" . eglot-format)
   '("lF" . eglot-format-buffer)
   '("la" . eglot-code-actions)
   '("lo" . eglot-code-action-organize-imports)
   '("lR" . eglot-rename)
   '("lh" . eldoc)))

Although Eglot depends on built-in packages, it needs the newest versions of them. Most of the packages are loaded elsewhere in the config, eldoc and xref are all that is left

(use-package eldoc
  :straight t)

(use-package xref
  :straight t)

Move eldoc out of the minibuffer when eglot is enabled.

;; (use-package eldoc-box
;;   :straight t
;;   :ghook
;;   ('eglot-managed-mode-hook #'eldoc-box-hover-at-point-mode t))

Tree Sitter

(use-package tree-sitter
  :defer t
  :straight t)

(use-package tree-sitter-langs
  :after tree-sitter
  :straight t)

EOF

(provide 'kpav-code)
;;; kpav-code.el ends here

Languages (and more)

Lisps

Shared

;;; kpav-lisp-core.el --- -*- lexical-binding: t -*-

Lisps can share a lot of the same config code due to the nature of the syntax.

List of all lisp mode hooks. This is used to enable modes for all of them.

(defconst kpav-lisp-mode-hooks
  '(lisp-mode-hook
    ;; sly-mrepl-mode-hook
    emacs-lisp-mode-hook
    clojure-mode-hook
    scheme-mode-hook
    ;; cider-repl-mode-hook
    ))
(use-package paredit
  :straight t
  :ghook kpav-lisp-mode-hooks)

Lispy provides vim-like commands to navigate and edit Lisp code.

;; (use-package lispy
;;   :straight t
;;   :ghook kpav-lisp-mode-hooks)

;; (use-package lispyville
;;   :straight t
;;   :ghook
;;   ('lispy-mode-hook #'lispyville-mode))

Symex provides even more vim-like commands (compared t0 lispy) to navigate and edit lisp code. Press ~C-‘~ to start.

;; (use-package symex
;;   :straight t
;;   :custom
;;   (symex-modal-backend 'evil)
;;   :general
;;   ("C-'" 'symex-mode-interface)
;;   (:keymaps 'normal
;;             (general-chord "jk") 'symex-mode-interface)
;;   :config
;;   (symex-initialize))

This is a nice guide: https://countvajhula.com/2021/09/25/the-animated-guide-to-symex/

Automatically indent code.

;; (use-package aggressive-indent
;;   :straight t
;;   :ghook kpav-lisp-mode-hooks)

NOTE: commenting this out for now, useful for me, but not for work projects…too many whitespace changes.

Highlight whats being eval’ed

(use-package eval-sexp-fu
  :straight t
  :ghook
  (kpav-lisp-mode-hooks #'eval-sexp-fu-flash-mode))
(provide 'kpav-lisp-core)
;;; kpav-lisp-core.el ends here

Clojure

;;; kpav-clojure.el --- -*- lexical-binding: t -*-
(require 'kpav-lisp-core)

Use LSP with all clojure-related modes.

(use-package clojure-mode
  :straight t
  :defer t
  :ghook
  ;; ('clojure-mode-hook #'lsp)
  ;; ('clojurec-mode-hook #'lsp)
  ;; ('clojurescript-mode #'lsp)
  ('clojure-mode-hook #'eglot-ensure)
  ('clojurec-mode-hook #'eglot-ensure)
  ('clojurescript-mode #'eglot-ensure)
  ;; :custom
  ;; (lsp-enable-indentation nil)
  )
;; is this needed?
;; :config
;; (dolist (m '(clojure-mode
;;              clojurec-mode
;;              clojurescript-mode
;;              clojurex-mode))
;;   (add-to-list 'lsp-language-id-configuration `(,m . "clojure")))

CIDER is the Clojure(Script) Interactive Development Environment that Rocks!

(use-package cider
  :straight t
  :after clojure-mode
  :ghook
  ('cider-repl-mode-hook #'rainbow-delimiters-mode)
  ;; ('cider-connected-hook #'cider-upgrade-nrepl-connection)
  :custom
  (cider-inject-dependencies-at-jack-in t)
  ;; clojure-lsp conflicts
  (cider-eldoc-display-for-symbol-at-point nil)
  (cider-use-xref nil)
  :general
  (base-local-leader-def
    ;:states '(normal visual)
    :keymaps 'clojure-mode-map
    "r" '(:ignore t :wk "repl")
    "rr" 'cider
    "rR" 'cider-restart
    "rn" 'cider-repl-set-ns
    "rb" 'cider-switch-to-repl-buffer
    "rc" 'cider-find-and-clear-repl-output
    "rl" 'cider-load-buffer
    "rq" 'cider-quit
    ;; eval
    "eD" 'cider-insert-defun-in-repl
    "eE" 'cider-insert-last-sexp-in-repl
    "eR" 'cider-insert-region-in-repl
    "eb" 'cider-eval-buffer
    "ed" 'cider-eval-defun-at-point
    "ee" 'cider-eval-last-sexp
    "er" 'cider-eval-region
    "eu" 'cider-undef
    "em" 'cider-macroexpand-1
    "eM" 'cider-macroexpand-all
    ;; help
    "h" '(:ignore t :wk "help")
    "ha" 'cider-apropos
    "hh" 'cider-doc
    "hj" 'cider-javadoc
    "hn" 'cider-browse-ns
    "hN" 'cider-browse-ns-all
    "hs" 'cider-browse-spec
    "hS" 'cider-browse-spec-all
    ;; inspect
    "i" '(:ignore t :wk "inspect")
    "ii" 'cider-inspect
    "ie" 'cider-enlighten-mode
    "ir" 'cider-inspect-last-result
    ;; pprint
    "p" '(:ignore t :wk "pprint")
    "pd" 'cider-pprint-eval-defun-at-point
    "pD" 'cider-pprint-eval-defun-to-comment
    "pD" 'cider-pprint-eval-last-sexp-to-repl
    ;; format
    "f" '(:ignore t :wk "format")
    "fr" 'cider-format-region
    "fb" 'cider-format-buffer
    "ff" 'cider-format-defun
    ;; goto
    "g" '(:ignore t :wk "goto")
    "gg" 'cider-find-dwim
    "gv" 'cider-find-var
    "gn" 'cider-find-ns
    "gN" 'cider-browse-ns-all
    "d" '(:ignore t :wk "debug")
    "dr" 'cider-ns-reload
    "dR" 'cider-ns-reload-all
    "di" 'cider-inspect-defun-at-point)
  :init
  ;; (meow-leader-define-key
  ;;  '("mrr" . cider)
  ;;  '("mrR" . cider-restart)
  ;;  '("mrn" . cider-repl-set-ns)
  ;;  '("mrb" . cider-switch-to-repl-buffer)
  ;;  '("mrc" . cider-find-and-clear-repl-output)
  ;;  '("mrl" . cider-load-buffer)
  ;;  '("mrq" . cider-quit)
  ;;   ;; eval
  ;;  '("meD" . cider-insert-defun-in-repl)
  ;;  '("meE" . cider-insert-last-sexp-in-repl)
  ;;  '("meR" . cider-insert-region-in-repl)
  ;;  '("meb" . cider-eval-buffer)
  ;;  '("med" . cider-eval-defun-at-point)
  ;;  '("mee" . cider-eval-last-sexp)
  ;;  '("mer" . cider-eval-region)
  ;;  '("meu" . cider-undef)
  ;;  '("mem" . cider-macroexpand-1)
  ;;  '("meM" . cider-macroexpand-all)
  ;;   ;; help
  ;;  '("mha" . cider-apropos)
  ;;  '("mhh" . cider-doc)
  ;;  '("mhj" . cider-javadoc)
  ;;  '("mhn" . cider-browse-ns)
  ;;  '("mhN" . cider-browse-ns-all)
  ;;  '("mhs" . cider-browse-spec)
  ;;  '("mhS" . cider-browse-spec-all)
  ;;   ;; inspect
  ;;  '("mii" . cider-inspect)
  ;;  '("mie" . cider-enlighten-mode)
  ;;  '("mir" . cider-inspect-last-result)
  ;;   ;; pprint
  ;;  '("mpd" . cider-pprint-eval-defun-at-point)
  ;;  '("mpD" . cider-pprint-eval-defun-to-comment)
  ;;  '("mpD" . cider-pprint-eval-last-sexp-to-repl)
  ;;   ;; format
  ;;  '("mfr" . cider-format-region)
  ;;  '("mfb" . cider-format-buffer)
  ;;  '("mff" . cider-format-defun)
  ;;   ;; goto
  ;;  '("mgg" . cider-find-dwim)
  ;;  '("mgv" . cider-find-var)
  ;;  '("mgn" . cider-find-ns)
  ;;  '("mgN" . cider-browse-ns-all)
  ;;  ;; debug
  ;;  '("mdr" . cider-ns-reload)
  ;;  '("mdR" . cider-ns-reload-all)
  ;;  '("mdi" . cider-inspect-defun-at-point))
  )

Refactoring functionality for Clojure.

(use-package clj-refactor
  :straight t
  :after cider
  :ghook
  ('clojure-mode-hook (lambda ()
                        (clj-refactor-mode +1)
                        (yas-minor-mode +1) ; for adding require/use/import statements
                        ;; Leaves cider-macroexpand-1 unbound
                        (cljr-add-keybindings-with-prefix "C-c C-m")))
  :custom
  ;; conflicts with clojure-lsp
  (cljr-add-ns-to-blank-clj-files nil)
  :general
  (base-local-leader-def
    :states '(normal visual)
    :keymaps 'clojure-mode-map
    "R" 'hydra-cljr-help-menu/body))

Highlight what’s being eval’ed

(use-package cider-eval-sexp-fu
  :straight t)
(provide 'kpav-clojure)
;;; kpav-clojure.el ends here

Common Lisp

;;; kpav-common-lisp.el --- -*- lexical-binding: t -*-
(require 'kpav-lisp-core)

Sly provides a Common Lisp REPL for Emacs.

(use-package sly
  :straight t
  :defer t
  :ghook
  ('sly-mrepl-mode-hook #'rainbow-delimiters-mode)
  :general
  (base-local-leader-def
   :states 'normal
   :keymaps 'lisp-mode-map
   "eb" 'sly-eval-buffer
   "el" 'sly-eval-last-expression
   "ed" 'sly-eval-defun
   "er" 'sly-eval-region)
  :custom
  (inferior-lisp-program "/usr/bin/sbcl"))

(use-package sly-quicklisp
  :straight t
  :after sly)

(use-package sly-asdf
  :straight t
  :after sly)
(provide 'kpav-common-lisp)
;;; kpav-common-lisp.el ends here

Emacs Lisp

;;; kpav-elisp.el --- -*- lexical-binding: t -*-
(require 'kpav-lisp-core)

Set up keys for evaling elisp.

(use-package emacs
  :straight (:type built-in)
  :general
  (base-local-leader-def
   :states '(normal visual)
   :keymaps 'emacs-lisp-mode-map
   "e" '(:ignore t :wk "eval")
   "eb" 'eval-buffer
   "el" 'eval-last-sexp
   "ed" 'eval-defun
   "er" 'eval-region)
  (base-local-leader-def
   :states 'normal
   :keymaps 'lisp-interaction-mode-map
   "e" '(:ignore t :wk "eval")
   "eb" 'eval-buffer
   "el" 'eval-last-sexp
   "ed" 'eval-defun
   "er" 'eval-region))
(provide 'kpav-elisp)
;;; kpav-elisp.el ends here

Scheme

;;; kpav-scheme.el --- -*- lexical-binding: t -*-
(require 'kpav-lisp-core)
(use-package geiser
  :straight t
  :custom
  (geiser-activate-implementations '(guile)))

(use-package geiser-guile
  :straight t)
(provide 'kpav-scheme)
;;; kpav-scheme.el ends here

PHP

;;; kpav-php.el --- -*- lexical-binding: t -*-

I use PHP for my job, so I need to use the WellspringCodingStandard.

(use-package php-mode
  :straight t
  :mode "\\.php\\'"
  :gfhook #'my-php-setup
  :general
  (:keymaps 'php-mode-map
    "C-c a" 'my/align-php-dbl-arrow)
  :custom
  ;; align -> on successive lines
  (php-lineup-cascaded-calls t)
  (flycheck-phpcs-standard "WellspringCodingStandard"))

Setup the default coding style and LSP for php. Need to set lsp-enable-file-watchers to nil because the project has a large amount of files and it causes performance issues.

(defun my-php-setup ()
  (php-enable-default-coding-style)
  (setq lsp-enable-file-watchers nil)
  (lsp))

Align the ==>= in arrays

(defun my/align-php-dbl-arrow ()
  "Align the => in arrays."
  (interactive)
  (align-regexp
   (region-beginning) (region-end)
   "\\(\\s-*\\) => " 1 0 nil))

Use PHP_CodeSniffer to format files

(use-package phpcbf
  :straight t
  :after (php-mode)
  :custom
  (phpcbf-executable "/usr/local/bin/phpcbf")
  (phpcbf-standard "WellspringCodingStandard"))

psysh is a php repl

(use-package psysh
  :straight t
  :defer t)
(provide 'kpav-php)
;;; kpav-php.el ends here

Python

;;; kpav-python.el --- -*- lexical-binding: t -*-

Use pyenv for managing virtual environments Set python version with: M-x pyvenv-mode-set Then M-x run-python

(use-package pyenv-mode
  :straight t
  :ghook
  ('python-mode-hook #'pyenv-mode))

Set up LSP with eglot

(use-package python
  :straight t
  :mode "\\.py\\'"
  :gfhook
  #'tree-sitter-mode
  #'tree-sitter-hl-mode
  #'eglot-ensure
  :ghook
  ('inferior-python-mode-hook #'eglot-ensure))
(provide 'kpav-python)
;;; kpav-python.el ends here

JavaScript / TypeScript

;;; kpav-js.el --- -*- lexical-binding: t -*-

JavasScript

Using the built in js-mode. This handles both vanilla js and .jsx.

(use-package js-mode
  :straight (:type built-in)
  :mode "\\.js\\'"
  :interpreter "node"
  :gfhook
  ;;#'lsp
  #'tree-sitter-mode
  #'tree-sitter-hl-mode
  #'eglot-ensure)

TypeScript

Set up for standard TypeScript (.ts) files.

(use-package typescript-mode
  :straight t
  :mode "\\.ts\\'"
  :commands (typescript-mode)
  :gfhook
  ;;#'lsp
  #'eglot-ensure
  #'tree-sitter-mode
  #'tree-sitter-hl-mode)

typescript-mode can’t handle .tsx files, but web-mode can.

(use-package web-mode
  :straight t
  :mode "\\.tsx\\'"
  :commands (web-mode)
  :gfhook
  #'eglot-ensure
  #'tree-sitter-mode
  #'tree-sitter-hl-mode)

Have web-mode use the TS language server because otherwise eglot does not know which server to use.

(use-package eglot
  :config
  (add-to-list 'eglot-server-programs '(web-mode . ("typescript-language-server" "--stdio"))))

JSON

(use-package json-mode
  :straight t
  :mode "\\.json\\'"
  :gfhook
  #'tree-sitter-mode
  #'tree-sitter-hl-mode)

EOF

(provide 'kpav-js)
;;; kpav-js.el ends here

C / C++

;;; kpav-c.el --- -*- lexical-binding: t -*-

Set up the ccls language server

(use-package ccls
  :straight t
  :hook ((c-mode c++-mode objc-mode cuda-mode) .
         (lambda () (require 'ccls) ;;(lsp)
           (eglot)))
  :config
  (setq ccls-executable "/usr/bin/ccls")
  ;; use flycheck instead of flymake
  ;;(setq lsp-prefer-flymake nil)
  (setq lsp-prefer-flymake t)
  (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc)))
(provide 'kpav-c)
;;; kpav-c.el ends here

YAML

;;; kpav-yaml.el --- -*- lexical-binding: t -*-
(use-package yaml-mode
  :straight t
  :defer t)
(provide 'kpav-yaml)
;;; kpav-yaml.el ends here

gRPC

;;; kpav-grpc.el --- -*- lexical-binding: t -*-
(use-package protobuf-mode
  :straight t
  :defer t)
(provide 'kpav-grpc)
;;; kpav-grpc.el ends here

GraphQL

;;; kpav-graphql.el --- -*- lexical-binding: t -*-
(use-package graphql-mode
  :straight t
  :defer t)
(provide 'kpav-graphql)
;;; kpav-graphql.el ends here

Nix

Mode for the nix language.

;;; kpav-nix.el --- -*- lexical-binding: t -*-
(use-package nix-mode
  :straight t
  :mode "\\.nix\\'")
(provide 'kpav-nix)
;;; kpav-nix.el ends here

EOF

(provide 'init)
;;; init.el ends here

To Do List

Test JS / TS stuff more

Add JS REPL (skewer?)

Add keys for LSP

jumping and stuff

Add Hydra

Should probably be in basemacs-core

Add keys for files, M-x, C-x?

e.g. SPC f

Look into Selectrum and co. config more

See what other settings there are

Bookmark keys

Git keys

CANCELLED Look into TabNine / company-tabnine

  • State “CANCELLED” from “TODO” [2023-05-21 Sun 21:53]
    using corfu

AI completion engine?

Remove org from basemacs-core OR set :straight t

conflicts with other org customization

Remove magit from basemacs-core?

Look into Corfu to replace company

Use org-modern and make it look good

Look into dizzee

Start projects easier https://github.com/davidmiller/dizzee

CURRENT Look into meow instead of evil

About

My personal Emacs configuration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published