Files
dotfiles/Emacs/.config/emacs/core.org

25 KiB
Raw Blame History

Core packages

Packages that are absolutely necessary for the rest of the configuration. Window management, Keybindings or completion frameworks; they're all done here.

General

General provides a great solution for binding keys. It has evil and use-package support so it fits nicely into configuration. In this case, I define a "definer" for the "LEADER" keys. Leader is bound to SPC and it's functionally equivalent to the doom/spacemacs leader. Local leader is bound to SPC , and it's similar to doom/spacemacs leader but doesn't try to fully assimilate the local-leader map, instead just picking stuff I think is useful. This forces me to learn only as many bindings as I find necessary; no more, no less.

I also define prefix leaders for differing applications. These are quite self explanatory by their name and provide a nice way to visualise all bindings under a specific heading just by searching the code.

(use-package general
  :straight t
  :demand t
  :config
  ;; General which key definitions for leaders
  (general-def
    :states '(normal motion)
    "SPC"   'nil
    "\\"    '(nil :which-key "Local leader")
    "SPC a" '(nil :which-key "Applications")
    "SPC b" '(nil :which-key "Buffers")
    "SPC c" '(nil :which-key "Code")
    "SPC d" '(nil :which-key "Directories")
    "SPC f" '(nil :which-key "Files")
    "SPC i" '(nil :which-key "Insert")
    "SPC m" '(nil :which-key "Modes")
    "SPC r" '(nil :which-key "Tabs")
    "SPC s" '(nil :which-key "Search")
    "SPC t" '(nil :which-key "Shell")
    "SPC q" '(nil :which-key "Quit/Literate"))

  (general-create-definer leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC")

  (general-create-definer local-leader
    :states '(normal motion)
    :prefix "\\")

  (general-create-definer code-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC c")

  (general-create-definer file-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC f")

  (general-create-definer shell-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC t")

  (general-create-definer tab-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC r")

  (general-create-definer mode-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC m")

  (general-create-definer app-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC a")

  (general-create-definer search-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC s")

  (general-create-definer buffer-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC b")

  (general-create-definer quit-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC q")

  (general-create-definer insert-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC i")

  (general-create-definer dir-leader
    :states '(normal motion)
    :keymaps 'override
    :prefix "SPC d")

  (general-create-definer general-nmmap
    :states '(normal motion))

  (defalias 'nmmap #'general-nmmap)

  (general-evil-setup t))

Some binds for Emacs

Some bindings that I couldn't fit elsewhere easily.

(use-package emacs
  :straight nil
  :general
  ("C-x d" #'delete-frame)

  (nmmap
    "M-;" #'eval-expression
    "g="  #'align-regexp
    "C--" #'text-scale-decrease
    "C-=" #'text-scale-increase
    "C-+" #'text-scale-adjust)

  (leader
    "SPC" '(execute-extended-command :which-key "M-x")
    "p"   `(,project-prefix-map :which-key "Project")
    "'"   '(browse-url-emacs :which-key "Download URL to Emacs")
    ":"   `(,(proc (interactive) (switch-to-buffer "*scratch*"))
            :which-key "Switch to *scratch*")
    "!"   '(async-shell-command :which-key "Async shell command")
    "h"   '(help-command :which-key "Help"))

  (mode-leader
    "T" #'+oreo/switch-theme)

  (code-leader
    "F" `(,(proc (interactive) (find-file "~/Code/"))
          :which-key "Open ~/Code/"))

  (file-leader
    "f" #'find-file
    "F" #'find-file-other-frame
    "s" #'save-buffer)

  (quit-leader
    "q" #'save-buffers-kill-terminal
    "c" #'+literate/compile-config
    "C" #'+literate/clean-config
    "l" #'+literate/load-config)

  (search-leader "i" #'imenu))

Evil

My editor journey started off with Vim rather than Emacs, so my brain has imprinted on its style. Thankfully Emacs is super extensible so there exists a package (more of a supreme system) for porting Vim's modal editing style to Emacs, called Evil (Emacs Vi Layer).

However there are a lot of packages in Vim that provide greater functionality, for example 'vim-surround'. Emacs, by default, has these capabilities but there are further packages which integrate them into Evil.

Evil core

Setup the evil package, with some opinionated keybindings:

  • Switch evil-upcase and evil-downcase because I use evil-upcase more
  • Switch evil-goto-mark and evil-goto-mark-line as I'd rather have the global one closer to the home row
  • Use 'T' character as an action for transposing objects
(use-package evil
  :defer t
  :hook (after-init-hook . evil-mode)
  :general
  (leader
    "w"  '(evil-window-map :which-key "Window")
    "wT" #'window-swap-states
    "wd" #'evil-window-delete)

  (nmmap
    "K"   #'man
    "TAB" #'evil-jump-item
    "r"   #'evil-replace-state
    "zC"  #'hs-hide-level
    "zO"  #'hs-show-all
    "'"   #'evil-goto-mark
    "`"   #'evil-goto-mark-line
    "C-w" #'evil-window-map
    "gu"  #'evil-upcase
    "gU"  #'evil-downcase
    "T"   nil)

  (nmmap
    :infix "T"
    "w" #'transpose-words
    "c" #'transpose-chars
    "s" #'transpose-sentences
    "p" #'transpose-paragraphs
    "e" #'transpose-sexps
    "l" #'transpose-lines)
  :init
  (setq evil-want-keybinding nil
        evil-split-window-below t
        evil-vsplit-window-right t
        evil-want-abbrev-expand-on-insert-exit t
        evil-undo-system #'undo-tree))

Evil surround

Evil surround is a port for vim-surround.

(use-package evil-surround
  :after evil
  :config
  (global-evil-surround-mode))

Evil commentary

Allows generalised commenting of objects easily.

(use-package evil-commentary
  :after evil
  :config
  (evil-commentary-mode))

Evil multi cursor

Setup for multi cursors in Evil mode. Don't let evil-mc setup it's own keymap because it uses 'gr' as its prefix, which I don't like.

(use-package evil-mc
  :after evil
  :init
  (defvar evil-mc-key-map (make-sparse-keymap))
  :general
  (nmap
    :infix "gz"
    "q" #'evil-mc-undo-all-cursors
    "d" #'evil-mc-make-and-goto-next-match
    "j" #'evil-mc-make-cursor-move-next-line
    "k" #'evil-mc-make-cursor-move-prev-line
    "j" #'evil-mc-make-cursor-move-next-line
    "m" #'evil-mc-make-all-cursors
    "z" #'evil-mc-make-cursor-here
    "r" #'evil-mc-resume-cursors
    "s" #'evil-mc-pause-cursors
    "u" #'evil-mc-undo-last-added-cursor)
  :config
  ;; (evil-mc-define-vars)
  ;; (evil-mc-initialize-vars)
  ;; (add-hook 'evil-mc-before-cursors-created #'evil-mc-pause-incompatible-modes)
  ;; (add-hook 'evil-mc-before-cursors-created #'evil-mc-initialize-active-state)
  ;; (add-hook 'evil-mc-after-cursors-deleted  #'evil-mc-teardown-active-state)
  ;; (add-hook 'evil-mc-after-cursors-deleted  #'evil-mc-resume-incompatible-modes)
  ;; (advice-add #'evil-mc-initialize-hooks :override #'ignore)
  ;; (advice-add #'evil-mc-teardown-hooks :override #'evil-mc-initialize-vars)
  ;; (advice-add #'evil-mc-initialize-active-state :before #'turn-on-evil-mc-mode)
  ;; (advice-add #'evil-mc-teardown-active-state :after #'turn-off-evil-mc-mode)
  ;; (add-hook 'evil-insert-state-entry-hook #'evil-mc-resume-cursors)
  (global-evil-mc-mode))

Evil collection

Provides a community based set of keybindings for most modes in Emacs. I don't necessarily like all my modes having these bindings though, as I may disagree with some. So I use it in a mode to mode basis.

(use-package evil-collection
  :after evil)

Evil number

Increment/decrement a number at point like Vim does, but use bindings that don't conflict with Emacs default.

(use-package evil-numbers
  :after evil
  :general
  (nmmap
    "+" #'evil-numbers/inc-at-pt
    "-" #'evil-numbers/dec-at-pt))

Completion

Emacs is a text based interface. Completion is its bread and butter in providing good user experience. By default Emacs provides 'completions-list' which produces a buffer of options which can be searched and selected. We can take this further though!

Ido and Icomplete are packages distributed with Emacs to provide greater completion interfaces. They utilise the minibuffer to create a more interactive experience, allowing incremental searches and option selection.

Ivy and Helm provide more modern interfaces, though Helm is quite heavy. Ivy, on the other hand, provides an interface similar to Ido with less clutter and better customisation options.

Ivy

Ivy is a completion framework for Emacs, and my preferred one. It has a great set of features with little to no pain with setting up.

Ivy Core

Setup for ivy, in preparation for counsel. Turn on ivy-mode just after init.

Setup vim-like bindings for the minibuffer ("M-(j|k)" for down|up the selection list).

(use-package ivy
  :defer t
  :hook (after-init-hook . ivy-mode)
  :general
  (general-def
    :keymaps 'ivy-minibuffer-map
    "C-j"    #'ivy-yank-symbol
    "M-j"    #'ivy-next-line-or-history
    "M-k"    #'ivy-previous-line-or-history
    "C-SPC"  #'ivy-occur)
  (general-def
    :keymaps  'ivy-switch-buffer-map
    "M-j"     #'ivy-next-line-or-history
    "M-k"     #'ivy-previous-line-or-history)
  (nmap
    :keymaps '(ivy-occur-mode-map ivy-occur-grep-mode-map)
    "RET"    #'ivy-occur-press-and-switch
    "J"      #'ivy-occur-press
    "gr"     #'ivy-occur-revert-buffer
    "q"      #'quit-window
    "D"      #'ivy-occur-delete-candidate
    "W"      #'ivy-wgrep-change-to-wgrep-mode
    "{"      #'compilation-previous-file
    "}"      #'compilation-next-file)
  :init
  (with-eval-after-load "evil"
    (evil-set-initial-state 'ivy-occur-mode 'normal)
    (evil-set-initial-state 'ivy-occur-grep-mode 'normal))
  (with-eval-after-load "amx"
    (setq amx-backend 'ivy))

  (setq ivy-height 10
        ivy-wrap t
        ivy-fixed-height-minibuffer t
        ivy-use-virtual-buffers nil
        ivy-virtual-abbreviate 'full
        ivy-on-del-error-function #'ignore
        ivy-use-selectable-prompt t)
  :config
  (require 'counsel nil t))

Counsel

Setup for counsel. Load after ivy and helpful.

(use-package counsel
  :defer t
  :general
  (search-leader
    "s" #'counsel-grep-or-swiper
    "R" #'counsel-rg)
  (file-leader
    "r" #'counsel-recentf
    "P" (proc (interactive)
              (with-current-buffer (find-file-noselect
                                    (concat user-emacs-directory "config.org"))
                (counsel-git))))
  (insert-leader
    "c" #'counsel-unicode-char)
  (general-def
    [remap describe-bindings]        #'counsel-descbinds
    [remap load-theme]               #'counsel-load-theme)
  :config
  (setq ivy-initial-inputs-alist '((org-insert-link . "^"))
        counsel-describe-function-function #'helpful-callable
        counsel-describe-variable-function #'helpful-variable
        counsel-grep-swiper-limit 1500000
        ivy-re-builders-alist '((swiper . ivy--regex-plus)
                                (counsel-grep-or-swiper . ivy--regex-plus)
                                (counsel-rg . ivy--regex-plus)
                                (t . ivy--regex-ignore-order)))
  (counsel-mode))

WAIT Ivy posframe

This makes ivy minibuffer windows use child frames. Very nice eyecandy, but can get kinda annoying.

(use-package ivy-posframe
  :hook (ivy-mode-hook . ivy-posframe-mode)
  :straight t
  :init
  (setq ivy-posframe-parameters
        '((left-fringe      . 0)
          (right-fringe     . 0)
          (background-color . "grey7")))

  (setq ivy-posframe-display-functions-alist
        '((t . ivy-posframe-display-at-window-center))))

WAIT Counsel etags

Counsel etags allows me to search generated tag files for tags. I already have a function defined to generate the tags, so it's just searching them which I find to be a bit of a hassle, and where this package comes in.

This has been replaced by xref which is inbuilt.

(use-package counsel-etags
  :after counsel
  :general
  (search-leader
    "t" #'counsel-etags-find-tag))

WAIT Ido

Ido is a very old completion package that still works great to this day. Though it is limited in its scope (and may thus be called a completion add-on rather than a full on framework), it is still a very powerful package. With the use of ido-completing-read+, it may be used similarly to a fully fledged completion framework.

(use-package ido
  :demand t
  :general
  (general-def
    :keymaps '(ido-buffer-completion-map
               ido-file-completion-map
               ido-file-dir-completion-map
               ido-common-completion-map)
    (kbd "M-j")   #'ido-next-match
    (kbd "M-k")   #'ido-prev-match
    (kbd "C-x o") #'evil-window-up)
  :init
  (setq ido-decorations
        (list "{" "}" " \n" " ..." "[" "]" " [No match]" " [Matched]"
              " [Not readable]" " [Too big]" " [Confirm]")
        completion-styles '(flex partial-completion intials emacs22))
  (setq-default ido-enable-flex-matching t
                ido-enable-dot-prefix t
                ido-enable-regexp nil)
  (with-eval-after-load "magit"
    (setq magit-completing-read-function 'magit-ido-completing-read))
  :config
  (ido-mode)
  (ido-everywhere))

Ido ubiquitous

Ido completing-read+ is a package that extends the ido package to work with more text based functions.

(use-package ido-completing-read+
  :after ido
  :config
  (ido-ubiquitous-mode +1))

Amx

Amx is a fork of Smex that works to enhance the execute-extended-command interface. It also provides support for ido or ivy (though I'm likely to use ido here) and allows you to switch between them.

It provides a lot of niceties such as presenting the key bind when looking for a command.

(use-package amx
  :config
  (amx-mode))

Orderless

Orderless sorting method for completion, probably one of the best things ever.

(use-package orderless
  :after (ivy ido)
  :config
  (setf (alist-get t ivy-re-builders-alist) 'orderless-ivy-re-builder))

Completions-list

In case I ever use the completions list, some basic commands to look around.

(use-package simple
  :straight nil
  :general
  (nmmap
    :keymaps 'completion-list-mode-map
    "l"   #'next-completion
    "h"   #'previous-completion
    "ESC" #'delete-completion-window
    "q"   #'quit-window
    "RET" #'choose-completion)
  :config
  (with-eval-after-load "evil"
    (setq evil-emacs-state-modes (cl-remove-if
                                  #'(lambda (x) (eq 'completions-list-mode x))
                                  evil-emacs-state-modes))
    (add-to-list 'evil-normal-state-modes 'completions-list-mode)))

Company

Company is the auto complete system I use. I don't like having heavy setups for company as it only makes it slower to use. In this case, just setup some evil binds for company.

(use-package company
  :straight t
  :hook
  (prog-mode-hook   . company-mode)
  (eshell-mode-hook . company-mode)
  :general
  (imap
    "C-SPC" #'company-complete
    "M-j" #'company-select-next
    "M-k" #'company-select-previous))

Pretty symbols

Prettify symbols mode allows for users to declare 'symbols' that replace text within certain modes. Though this may seem like useless eye candy, it has aided my comprehension and speed of recognition (recognising symbols is easier than words).

Essentially a use-package keyword which makes declaring pretty symbols for language modes incredibly easy. Checkout my C/C++ configuration for an example.

(use-package prog-mode
  :straight nil
  :init
  (setq prettify-symbols-unprettify-at-point t)
  :config
  (with-eval-after-load "use-package-core"
    (add-to-list 'use-package-keywords ':pretty)
    (defun use-package-normalize/:pretty (_name-symbol _keyword args)
      args)

    (defun use-package-handler/:pretty (name _keyword args rest state)
      (use-package-concat
       (use-package-process-keywords name rest state)
       (mapcar
        #'(lambda (arg)
            (let ((mode (car arg))
                  (rest (cdr arg)))
              `(add-hook
                ',mode
                #'(lambda nil
                    (setq prettify-symbols-alist ',rest)
                    (prettify-symbols-mode)))))
        args)))))

Here's a collection of keywords and possible associated symbols for any prog language of choice. Mostly for reference and copying.

("null"   . "Ø")
("list"   . "")
("string" . "𝕊")
("true"   . "")
("false"  . "⊥")
("char"   . "")
("int"    . "")
("float"  . "")
("!"      . "¬")
("&&"     . "∧")
("||"      . "")
("for"    . "∀")
("return" . "⟼")
("print"  . "")
("lambda" . "λ")

Window management

Emacs' default window management is quite bad, eating other windows on a whim and not particularly caring for the current window setup. Thankfully you can change this via the display-buffer-alist which matches buffer names with how the window for the buffer should be displayed. I add a use-package keyword to make display-buffer-alist records within a use-package call.

I have no idea whether it's optimal AT ALL, but it works for me.

2024-04-23: Found this option switch-to-buffer-obey-display-actions which makes manual buffer switches obey the same constraints via display-buffer-alist as creating the buffer automatically.

(use-package window
  :straight nil
  :general
  (buffer-leader
    "b" #'switch-to-buffer
    "d" #'kill-current-buffer
    "K" #'kill-buffer
    "j" #'next-buffer
    "k" #'previous-buffer
    "D" '(+oreo/clean-buffer-list :which-key "Kill most buffers"))
  :init
  (setq switch-to-buffer-obey-display-actions t)
  (with-eval-after-load "use-package-core"
    (add-to-list 'use-package-keywords ':display)
    (defun use-package-normalize/:display (_name-symbol _keyword args)
      args)

    (defun use-package-handler/:display (name _keyword args rest state)
      (use-package-concat
       (use-package-process-keywords name rest state)
       (mapcar
        #'(lambda (arg)
            `(add-to-list 'display-buffer-alist
                          ',arg))
        args)))))

Some display records

Using the :display keyword, setup up some display-buffer-alist records. This is mostly for packages that aren't really configured (like woman) or packages that were configured before (like Ivy).

(use-package window
  :straight nil
  :defer t
  :display
  ("\\*Process List\\*"
   (display-buffer-at-bottom)
   (window-height . 0.25))

  ("\\*\\(Ido \\)?Completions\\*"
   (display-buffer-in-side-window)
   (window-height . 0.25)
   (side . bottom))

  ("\\*ivy-occur.*"
   (display-buffer-at-bottom)
   (window-height . 0.25))

  ("\\*Async Shell Command\\*"
   (display-buffer-at-bottom)
   (window-height . 0.25)))

Tabs

Tabs in vscode are just like buffers in Emacs but way slower and harder to use. Tabs in Emacs are essentially window layouts, similar to instances in Tmux. With this setup I can use tabs quite effectively.

(use-package tab-bar
  :defer t
  :straight nil
  :init
  (setq tab-bar-show 1)
  :config
  (tab-bar-mode)
  :general
  (tab-leader
    "t" #'tab-switch
    "j" #'tab-next
    "k" #'tab-previous
    "h" #'tab-move-to
    "l" #'tab-move
    "n" #'tab-new
    "c" #'tab-close
    "d" #'tab-close
    "f" #'tab-detach
    "w" #'tab-window-detach
    "r" #'tab-rename)
  (mode-leader
    "t" #'toggle-tab-bar-mode-from-frame))

Auto typing

Snippets are a pretty nice way of automatically inserting code. Emacs provides a ton of packages by default to do this, but there are great packages to install as well.

Abbrevs and skeletons make up a popular solution within Emacs default. Abbrevs are for simple expressions wherein the only input is the key, and the output is some Elisp function. They provide a lot of inbuilt functionality and are quite useful. Skeletons, on the other hand, are for higher level insertions

The popular external solution is Yasnippet. Yasnippet is a great package for snippets, which I use heavily in programming and org-mode. I setup here the global mode for yasnippet and a collection of snippets for ease of use.

Abbrevs

Just define a few abbrevs for various date-time operations. Also define a macro that will assume a function for the expansion, helping with abstracting a few things away.

(use-package abbrev
  :straight nil
  :hook
  (prog-mode-hook . abbrev-mode)
  (text-mode-hook . abbrev-mode)
  :init
  (defmacro +abbrev/define-abbrevs (abbrev-table &rest abbrevs)
    `(progn
       ,@(mapcar #'(lambda (abbrev)
                     `(define-abbrev
                        ,abbrev-table
                        ,(car abbrev)
                        ""
                        (proc (insert ,(cadr abbrev)))))
                 abbrevs)))
  (setq save-abbrevs nil)
  :config
  (+abbrev/define-abbrevs
   global-abbrev-table
   ("sdate"
    (format-time-string "%Y-%m-%d" (current-time)))
   ("stime"
    (format-time-string "%H:%M:%S" (current-time)))
   ("sday"
    (format-time-string "%A" (current-time)))
   ("smon"
    (format-time-string "%B" (current-time)))))

WAIT Skeletons

Defines a macro for generating a skeleton + abbrev for a given mode. Doesn't sanitise inputs because I assume callers are rational actors who would only use this for their top level Emacs config.

Honestly didn't find much use for this currently, so disabled.

(use-package skeleton
  :straight nil
  :after abbrev
  :config
  (defmacro +autotyping/gen-skeleton-abbrev (mode abbrev &rest skeleton)
    (let* ((table          (intern (concat (symbol-name mode) "-abbrev-table")))
           (skeleton-name  (intern (concat "+skeleton/" (symbol-name mode) "/" abbrev))))
      `(progn
         (define-skeleton
           ,skeleton-name
           ""
           ,@skeleton)
         (define-abbrev ,table
           ,abbrev
           ""
           ',skeleton-name)))))

Auto insert

Allows inserting text immediately upon creating a new buffer with a given name. Supports skeletons for inserting text. To make it easier for later systems to define their own auto inserts, I define a use-package keyword auto-insert which allows one to define an entry for auto-insert-alist.

(use-package autoinsert
  :straight nil
  :demand t
  :hook (after-init-hook . auto-insert-mode)
  :config
  (with-eval-after-load "use-package-core"
    (add-to-list 'use-package-keywords ':auto-insert)
    (defun use-package-normalize/:auto-insert (_name-symbol _keyword args)
      args)
    (defun use-package-handler/:auto-insert (name _keyword args rest state)
      (use-package-concat
       (use-package-process-keywords name rest state)
       (mapcar
        #'(lambda (arg)
            `(add-to-list
              'auto-insert-alist
              ',arg))
        args)))))

Yasnippet default

Look at the snippets folder for all snippets I've got.

(use-package yasnippet
  :defer t
  :hook
  (prog-mode-hook . yas-minor-mode)
  (text-mode-hook . yas-minor-mode)
  :general
  (insert-leader
    "i" #'yas-insert-snippet)
  :config
  (yas-load-directory (no-littering-expand-etc-file-name "yasnippet/snippets")))

WAIT Hydra

Hydra is a great package by abo-abo (yes the same guy who made ivy and swiper) and I hope to use it later on in the config. There are two use-package declarations here: one for hydra itself, and the other for use-package-hydra which provides the keyword :hydra in use-package declarations.

(use-package hydra
  :straight t)

(use-package use-package-hydra
  :straight t)