cmacrae / .emacs.d

My literate Emacs configuration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

About

This is my literate configuration for Emacs. This document includes a collection of configuration snippets to express how I personally like to use Emacs, each accompanied by some reasoning. I think it’s important to include reasoning for each part so I can understand why I use it. Plus the added benefit of others being able to peruse and borrow parts, just as I have from others.

The combination of literacy and functionality is achieved using the amazing org-mode, with org-babel.

Throughout this document, you’ll notice heavy use of the brilliant use-package. For anyone who hasn’t tried out use-package; I emplore you to do so - it truly makes managing your configuration an absolute joy.

General

This section covers many different types of configuration for native Emacs capabilities

Start the server

Start the Emacs server so other clients can connect and use the same session. This is useful for when you may be oprating Emacs from the GUI usually, but want to use the same session from a TTY/terminal. Also handy for when you have your EDITOR set to emacsclient.

(server-start)

Personal stuff

Pretty self explanatory: just setting some personal details about who’s using Emacs.

(setq user-full-name "Calum MacRae"
      user-mail-address "hi@cmacr.ae")

Deactivation

Deactivation of functionality I don’t tend to use:

  • Backup files
  • Autosaving
  • Start-up message
  • Audible bell
(setq
  make-backup-files nil
  auto-save-default nil
  inhibit-startup-message t
  ring-bell-function 'ignore)

UTF-8

Configure Emacs for full UTF-8 compatability

(set-charset-priority 'unicode)
(setq locale-coding-system   'utf-8)
(set-terminal-coding-system  'utf-8)
(set-keyboard-coding-system  'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system        'utf-8)
(setq default-process-coding-system '(utf-8-unix . utf-8-unix))

Global :ensure for use-package statements

use-package has an :ensure keyword which dictates whether packages are installed or not. As most of my use-package configurations are for external packages, I set this to always ensure. Then, in cases where I don’t want this to be true, I simply set :ensure nil

(setq use-package-always-ensure t)

Discard customizations

Emacs has a comprehensive customization system that allows configuration changes interactively. Personally, I opt to ensure all the configuration I use for my environment is fully declarative. As such, the following configuration sets the custom-file to be a random temporary file created each time Emacs starts. This means any customizations made interactively are discarded entirely.

(setq custom-file (make-temp-file ""))

Just use ‘y’ or ‘n’ instead of ‘yes’ or ‘no’

You’ll find yes-or-no prompts coming up in Emacs a lot. I’d much rather just type y or n than yes or no every time…

(fset 'yes-or-no-p 'y-or-n-p)

Set meta for Darwin systems

(cond
  ((string-equal system-type "darwin")
    (setq mac-option-modifier 'meta)))

Set the scratch buffer string

Set the scratch buffer’s initial contents to include a comment with a timestamp of creation. Not really all that useful, but cleaner than the default comment, and I like having something there.

(setq initial-scratch-message (format ";; Scratch buffer - started on %s\n\n" (current-time-string)))

Confirm quit

This adds a confirmation prompt when quitting Emacs - because I’m only human.

(setq confirm-kill-emacs 'yes-or-no-p)

A few Darwin specific configurations

To make Emacs play a little nicer with window management, enable menu-bar-mode. Also, set the frame’s dimensions based on pixels - this makes Emacs play nicer with tiling window managers, where no title bar is displayed.

(cond
  ((string-equal system-type "darwin")
    (menu-bar-mode t)
    (setq frame-resize-pixelwise t)))

Follow symlinks in version control

If there are any symlinks in version controlled repositories, follow them

(setq vc-follow-symlinks t)

Use ‘root’ user by default for SSH connections using TRAMP

When connecting to a remote system over SSH via TRAMP, use the root user by default

(set-default 'tramp-default-proxies-alist (quote ((".*" "\\`root\\'" "/ssh:%h:"))))

Set TRAMP shell prompt pattern (fix for some fancy prompts)

When connecting to some remote systems over SSH via TRAMP, you may run into some shells which use some different encoding for their prompt. This can result in a malformed prompt on the client side. This little snippet fixes that

(setq shell-prompt-pattern "\\(?:^\\|\r\\)[^]#$%>\n]*#?[]#$%>].* *\\(^[\\[[0-9;]*[a-zA-Z] *\\)*")

Set explicit shell binary

Set the filepath to the binary to run when invoking term (or any of its siblings).

(setq explicit-shell-file-name "/run/current-system/sw/bin/zsh")

Use M-3 to insert an octothorp

I’m usually on a British keyboard, so when doing M-3: insert an octothorp, not a GBP sign

(global-set-key (kbd "M-3") '(lambda () (interactive) (insert "#")))

Configure FlySpell to use aspell

I use aspell, so this simply sets Flyspell to use it and passes a couple extra arguments

(setq ispell-program-name "aspell")
(setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_GB"))

Kill term buffers upon exit

If I’m using an interactive terminal, it’s nice to just ^D out of it and have the buffer disappear

(defadvice term-handle-exit
  (after term-kill-buffer-on-exit activate)
(kill-buffer))

Calendar/Diary

Set the start of the week for the calendar to be Monday. Sort entries when viewing diary items.

(setq calendar-week-start-day 1)
(setq diary-file "~/org/diary")
(add-hook 'diary-list-entries-hook 'diary-sort-entries t)

IRC

Emacs comes with a great builtin IRC client: ERC. These are some general settings that’re all pretty self explanatory: hide particular activity, autojoin channels for particular servers. For convenience, I’ve also defined a erc-conn function for my usual connection parameters.

(use-package erc
  :ensure nil
  :config
  (setq erc-hide-list '("PART" "QUIT" "JOIN"))
  (setq erc-autojoin-channels-alist '(("freenode.net"
    "#lobsters"
    "#nixos"
    "#nix-darwin"))
    erc-server "irc.freenode.net"
    erc-nick "cmacrae"))
(defun cm/erc-conn ()
  (interactive)
  (erc-tls :server "irc.freenode.net" :port 6697 :nick "cmacrae"))

Packages

This section covers external packages I use and their configuration, in no particular order

Ivy|Counsel|Swiper

Absolutely brilliant interactive interface and completion frameworks. These packages improve the Emacs experience so much. As you can see from the :bind sections, I use these to replace some of the most used actions.

Ivy

  • Suppress count visibility for ivy-read
  • Set initial input chars to nil
  • Provide insert and yank options for candidates
  • Display the candidate menu at the current point position with ivy-posframe
  • Add some graphical niceties with ivy-rich
(use-package ivy
  :hook (after-init . ivy-mode)
  :preface
  (defun ivy-yank-action (x)
    (kill-new x))

  (defun ivy-copy-to-buffer-action (x)
    (with-ivy-window
      (insert x)))

  :bind
  ("C-s"     . swiper)
  ("M-x"     . counsel-M-x)
  ("C-x C-f" . counsel-find-file)

  :config
  (setq ivy-count-format          ""
        ivy-initial-inputs-alist  nil)
  (ivy-set-actions t
   '(("i" ivy-copy-to-buffer-action "insert")
     ("y" ivy-yank-action "yank"))))

(use-package ivy-posframe
  :after ivy
  :config
  (set-face-background 'ivy-posframe-border   "#51afef")
  (setq ivy-posframe-border-width             1
        ivy-posframe-parameters               '((left-fringe . 8) (right-fringe . 8))
        ivy-posframe-display-functions-alist  '((t      . ivy-posframe-display-at-point)
                                                (swiper . nil)))
  (ivy-posframe-mode 1))

(use-package ivy-rich
  :after counsel
  :config (setq ivy-rich-path-style 'abbrev)
  :init (ivy-rich-mode 1))

Counsel

  • Set a prettier candidate delimiter for killring
  • Bind common functions
  • Bind common org functions
  • Ensure `smex` is installed for better candidate matching
(use-package counsel
  :init
  (setq counsel-yank-pop-separator
    (concat "\n\n"
      (concat (apply 'concat (make-list 50 "---")) "\n")))
  :bind (
  ("M-y" . counsel-yank-pop)
  ("C-h f" . counsel-describe-function)
  ("C-h v" . counsel-describe-variable)

  :map org-mode-map
  ("C-c  C-j" . counsel-org-goto)
  ("C-c  C-q" . counsel-org-tag))

  :config
  (use-package smex :ensure t))

Magit

The one true Git porcelain! Truely a joy to use - it surfaces the power of Git in such a fluent manner. Anyone using Git and Emacs needs Magit in their life!

(use-package magit
  :bind ("C-c m" . magit-status)
  :init
  (setq magit-completing-read-function 'ivy-completing-read))

git-link

Create & yank URLs for popular git forges based on current file/buffer location. Handy for collaborating.

(use-package git-link
  :bind
  ("C-c g l" . git-link))

Projectile

Project management based on version control repositories. Absolutely essential package for me. This makes hopping around and between various projects really easy. Not only that, but it allows project-wide actions. Like killing all buffers for a project, performing a project-wide find-and-replace, or a grep, etc.

Some configuration I use:

  • Setting the completion system to ivy
  • Adding an action to invoke neotree upon switching projects
(use-package projectile
  :init
  (setq projectile-completion-system 'ivy)
  (setq projectile-switch-project-action 'neotree-projectile-action)
  :config
  (projectile-global-mode))

counsel-projectile

Further integration of Counsel with Projectile than what’s provided natively. As I use counsel-projectile-on to remap a bunch of Projectile’s functions to their Counsel equivilents, but I want to use Perspective functionality, I remap projectile-switch-project, after counsel-projectile-on has been called, to projectile-persp-switch-project. This then masks counsel-projectile-switch-project and integrates Perspective when switching projects.

(use-package counsel-projectile
  :bind
  ("C-c p s r" . counsel-projectile-rg)
  (:map projectile-mode-map
    ("C-c p p" . projectile-persp-switch-project)
    ("C-c p f" . projectile-find-file))
  :init
  (counsel-projectile-mode))

Perspective

Workspaces! Indespensible if you work on a lot of projects. Perspective is like workspaces (virtual desktops) for Emacs. It’s a means of namespacing a group of tangible buffers. When combined with Projectile, this becomes a really nice combination as projects then seemlessly translate to workspaces.

Here, I’ve defined a cm/persp-neo function for use with persp-switch-hook. This makes NeoTree follow the perspective when switching. I’ve also added a hydra for various Perspective actions.

(use-package perspective
  :init (persp-mode)
  :config
  (defun cm/persp-neo ()
    "Make NeoTree follow the perspective"
    (interactive)
    (let ((cw (selected-window))
          (path (buffer-file-name))) ;; save current window and buffer
          (progn
            (when (and (fboundp 'projectile-project-p)
                       (projectile-project-p)
                       (fboundp 'projectile-project-root))
              (neotree-dir (projectile-project-root)))
            (neotree-find path))
          (select-window cw)))

  :hook
  (persp-switch . cm/persp-neo))

(use-package persp-projectile
  :after (perspective)
  :bind
  ("C-c x" . hydra-persp/body)
  :config
  (defhydra hydra-persp (:columns 4
                         :color blue)
    "Perspective"
    ("a" persp-add-buffer "Add Buffer")
    ("i" persp-import "Import")
    ("c" persp-kill "Close")
    ("n" persp-next "Next")
    ("p" persp-prev "Prev")
    ("k" persp-remove-buffer "Kill Buffer")
    ("r" persp-rename "Rename")
    ("A" persp-set-buffer "Set Buffer")
    ("s" persp-switch "Switch")
    ("C-x" persp-switch-last "Switch Last")
    ("b" persp-switch-to-buffer "Switch to Buffer")
    ("P" projectile-persp-switch-project "Switch Project")
    ("q" nil "Quit")))

NeoTree

Sidebar filebrowser, very handy. People seem to have accepted Treemacs as the new norm, but I like NeoTree :) Here, I’ve defined some key mappings that make it a little nicer to interact with - they should be quite self-explanatory.

(use-package neotree
  :bind
  ("C-;"     . neotree-show)
  ("C-c C-;" . neotree-toggle)
  (:map neotree-mode-map
   ("C-c C-h" . neotree-hidden-file-toggle)
   ("C-c C-y" . neotree-copy-filepath-to-yank-ring)
   ("C-;"     . (lambda () (interactive) (select-window (previous-window)))))
  :config
  (setq neo-theme (if window-system 'icons 'arrows)))

popwin

Some windows in Emacs can be quite obtrusive. popwin aims to manage this. By using popwin windows that could be deemed “temporary” only take up a small amount of realestate, which is reclaimed upon said window closing. This is handy for things like grep results, help/compile buffers, etc.

You can also define your own “pop-up” actions. As you can see here, I’ve defined a little “pop-up” terminal. This will spawn a little terminal buffer at the top of my Emacs frame. Then, when I’m done with it and I exit the process/kill the buffer, the space is automatically reclaimed.

(use-package popwin
  :defer 1
  :bind
  ("C-x t" . cm/popwin-term)
  :config
  (setq display-buffer-function 'popwin:display-buffer)
  (defun cm/popwin-term ()
  (interactive)
  (popwin:display-buffer-1
   (or (get-buffer "*terminal*")
       (save-window-excursion
         (call-interactively 'term)))
     :default-config-keywords '(:position :top))
     (provide 'popwin-term))

  ;; Go direx
  (push '("^\*go-direx:" :regexp t :position right :width 0.4 :dedicated t :stick t)
     popwin:special-display-config))

Flycheck

Have Flycheck turned on for everything - checking stuff is always good! And for convenience, add a posframe.

(use-package flycheck
  :hook
  (after-init . global-flycheck-mode))

(use-package flycheck-posframe
  :after flycheck
  :hook (flycheck-mode . flycheck-posframe-mode))

company-mode

Slick auto-complete framework

(use-package company
  :hook (prog-mode . company-mode))

ace-window

Jump around Emacs windows & frames using character prefixes. I use this constantly - it even works across multiple frames. Also added a hydra borrowed from here for some really convenient movement/manipulation!

(use-package ace-window
  :bind ("M-o" . hydra-window/body)
  :config
  (setq aw-dispatch-always t)
  (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (defhydra hydra-window (:color blue)
    "window"
    ("h" windmove-left "left")
    ("j" windmove-down "down")
    ("k" windmove-up "up")
    ("l" windmove-right "right")
    ("a" ace-window "ace")
    ("s" (lambda () (interactive) (ace-window 4)) "swap")
    ("d" (lambda () (interactive) (ace-window 16)) "delete")
    ("q" nil "Quit")))

Smartparens

Brilliant automatic balancing of pairs. Makes for a really nice experience when typing in any language - programming or not. Just check out some of the gifs in the project’s README.

(use-package smartparens
  :config
  (progn
    (smartparens-global-mode)
    (show-smartparens-global-mode t)))

erc-hl-nicks

Nickname highlighting for ERC (IRC in Emacs)

(use-package erc-hl-nicks)

GitGutter

Hints and actions in the buffer/fringe for bits being followed by Git. The configuration bellow gives little diff highlights in the fringe for changes.

(use-package git-gutter
  :init
  (setq
    git-gutter:modified-sign " "
    git-gutter:added-sign " "
    git-gutter:deleted-sign " ")
  (global-git-gutter-mode t)
  :hook
  (window-setup . (lambda ()
    (set-face-background 'git-gutter:modified "#da8548")
    (set-face-background 'git-gutter:added "#98be65")
    (set-face-background 'git-gutter:deleted "#ff6c6b"))))

YAML & Ansible

YAML’s great - so support is obviously nice to have. I also spend quite a bit of my time working with Ansible. ansible-doc is a handy little package to pull up Ansible module documentation within Emacs. I’ve bound C-c h a for the YAML mode keymap to spawn ansible-doc

(use-package ansible-doc)
(use-package yaml-mode
  :bind (:map yaml-mode-map
    ("C-c h a" . ansible-doc)))

TOML mode

Simply to support TOML configurations

(use-package toml-mode)

Set exec/man PATH from shell

When looking for executables/man-pages, Emacs will inherit these properties from the OS environment. This package provides the ability to do so from the user’s shell, where they may have some more complex logic to determine such paths.

(use-package exec-path-from-shell
  :config
  (setq exec-path-from-shell-check-startup-files nil)
  (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-env "SSH_AGENT_PID")
  (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))

Expand region

Select regions by semantic units. Really handy for selecting regions of data - just repeat keypress to expand selection further.

(use-package expand-region
  :bind ("C-=" . er/expand-region))

json-mode

No reasoning needed here! Everyone needs JSON

(use-package json-mode)

Aggressive indent

Keeps code indented when making disruptive changes

(use-package aggressive-indent
  :config
  (global-aggressive-indent-mode 1))

MoveText

Easily move text up and down. I’ve tied this into a little hydra for more natural repeated movement.

(use-package move-text
  :bind ("C-c t" . hydra-move-text/body)
  :config
  ;; Move Text
  (defhydra hydra-move-text ()
    "Move text"
    ("k" move-text-up "Up")
    ("j" move-text-down "Down")
    ("q" nil "Quit" :color blue)))

Docker Integration

Various docker integrations:

  • dockerfile-mode is pretty self explanatory
  • docker-tramp allows TRAMP connections into running containers
  • docker, with a hydra, allows for interaction with the Docker distribution
(use-package dockerfile-mode
  :mode "\\Dockerfile\\'")

(use-package docker-tramp)
(use-package docker
  :bind ("C-c d" . hydra-docker/body)
  :config
  (defhydra hydra-docker (:columns 5 :color blue)
    "Docker"
    ("c" docker-containers "Containers")
    ("v" docker-volumes "Volumes")
    ("i" docker-images "Images")
    ("n" docker-networks "Networks")
    ("b" dockerfile-build-buffer "Build Buffer")
    ("q" nil "Quit")))

Kubernetes Integration

Integrates general purpose Kubernetes operations as a porcelain

(use-package kubernetes
  :bind ("C-c k" . hydra-kube/body)
  :commands (kubernetes-overview)
  :config
  (defhydra hydra-kube (:columns 5 :color blue)
    "Kubernetes"
    ("o" kubernetes-overview "Overview")
    ("c" kubernetes-config-popup "Config")
    ("e" kubernetes-exec-popup "Exec")
    ("l" kubernetes-logs-popup "Logs")
    ("L" kubernetes-labels-popup "Labels")
    ("d" kubernetes-describe-popup "Describe")))

(use-package kubernetes-evil
  :after kubernetes)

Corral

Quickly surround text with delimiters, along with a hydra

(use-package corral
  :bind
  ("M-9" . corral-parentheses-backward)
  ("M-0" . corral-parentheses-forward)
  ("M-[" . corral-brackets-backward)
  ("M-]" . corral-brackets-forward)
  ("M-{" . corral-braces-backward)
  ("M-}" . corral-braces-forward)
  ("M-\"" . corral-double-quotes-backward)
  ("C-c v" . hydra-corral/body)
  :config
  (setq corral-preserve-point t)
  (defhydra hydra-corral (:columns 5)
    "Corral"
    ("(" corral-parentheses-backward "Back")
    (")" corral-parentheses-forward "Forward")
    ("[" corral-brackets-backward "Back")
    ("]" corral-brackets-forward "Forward")
    ("{" corral-braces-backward "Back")
    ("}" corral-braces-forward "Forward")
    ("\"" corral-double-quotes-backward "Back")
    ("'" corral-single-quotes-backward "Back")
    ("." hydra-repeat "Repeat")))

Focus

Makes the current function at the point the only syntax-highlighted construct in the buffer. All other buffer contents are “subdued” to look like comments.

(use-package focus)

Dumb Jump

Jump to definitions

(use-package dumb-jump
  :bind
  ("C-c j" . hydra-dumb-jump/body)
  :config
  (setq dumb-jump-selector 'ivy)
  (defhydra hydra-dumb-jump (:color blue)
  "Dumb Jump"
  ("g" dumb-jump-go "Jump to def")
  ("p" dumb-jump-back "Jump back")
  ("q" dumb-jump-quick-look "Quick look")
  ("o" dumb-jump-go-other-window "Jump in other window")
  ("q" nil "Quit")))

undo-tree

Powerful undo actions formulated in a tree structure

(use-package undo-tree
  :config
  (global-undo-tree-mode))

ivy-pass & auth-password-store

I use pass to manage my passwords. This is a handy little package for interfacing with it.

(use-package ivy-pass
  :init (setq password-store-password-length 30)
  :bind ("C-c M-p" . ivy-pass))

And this package allows it to act as an auth-source

(use-package auth-source-pass
  :config (auth-source-pass-enable))

Nix

Various packages for working with Nix

Turn off aggressive-indent-mode as it doesn’t play nice.

(use-package nix-mode
  :init (setenv "NIX_REMOTE" "daemon")
  :hook
  (nix-mode . (lambda ()
                (when (and (stringp buffer-file-name)
                  (string-match "\\.nix\\'" buffer-file-name))
                    (aggressive-indent-mode 0)))))

Configure company-mode completions for NixOS options.

(use-package nixos-options)
(use-package company-nixos-options
  :hook
  (nix-mode . (lambda ()
                (set (make-local-variable 'company-backends) '(company-nixos-options))
                  (company-mode))))

restclient

REST client for Emacs! Really cool package. Kinda like Postman/Insomnia.

(use-package restclient
  :mode ("\\.http\\'" . restclient-mode))

Note/TODO highlighting

It’s nice to have some note/todo highlighting :)

(use-package hl-todo
  :config
  (global-hl-todo-mode)
  :hook
  (yaml-mode . hl-todo-mode))

ivy-lobsters

That’s right, I’m a crustacean 🦀

(use-package ivy-lobsters)

discover-my-major

A great little package to help discover more about the current major mode.

(use-package discover-my-major
  :bind ("C-h C-m" . hydra-discover/body)
  :config
  (defhydra hydra-discover(:color blue)
    "Discover"
    ("m" discover-my-major "Major")
    ("M" discover-my-mode "Mode")
    ("q" nil "Quit" :color blue)))

define-word

Display the definition of word at the point, nice!

(use-package define-word)

vterm

Fully-fledged terminal emulator based on libvterm! I manage the module and elisp as a Nix overlay in my system configuration, so no need to install it. Set it up to play nice with Evil.

(use-package vterm
  :ensure nil
  :after evil
  :hook
  (vterm-mode . (lambda ()
                  (setq-local evil-insert-state-cursor 'hbar)
                  (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 'insert vterm-mode-map (kbd "C-y")      #'vterm-yank)
  (evil-define-key 'normal vterm-mode-map (kbd "C-d")      #'vterm--self-insert)
  (evil-define-key 'normal vterm-mode-map (kbd "p")        #'vterm-yank)
  (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))

centaur-tabs

Fancy buffer tabs ✨

  • Activate after init
  • Disable for a few modes
  • Set the header face to fit better
  • Set tab grouping to work with Projectile
  • Bottom bar style tab indicator
  • Turn on icons
  • Set the cycle scope to current project tabs
  • Bind “K”/”J” in normal Evil state to move forward/backward
(use-package centaur-tabs
  :hook
  (after-init . centaur-tabs-mode)
  (vterm-mode . centaur-tabs-local-mode)
  (calendar-mode . centaur-tabs-local-mode)
  (org-agenda-mode . centaur-tabs-local-mode)
  (helpful . centaur-tabs-local-mode)
  :config
  (centaur-tabs-headline-match)
  (centaur-tabs-group-by-projectile-project)
  :custom
  (centaur-tabs-style "bar")
  (centaur-tabs-set-bar 'under)
  (centaur-tabs-set-icons t)
  (centaur-tabs-cycle-scope 'tabs)
  :bind
  (:map evil-normal-state-map
     ("K" . centaur-tabs-forward)
     ("J" . centaur-tabs-backward)))

Hydras

Great package to tie tangible actions together into convenient keybinding landscapes. Here, you’ll find some “general” hydras - other hydras that are centric around packages will be found with that package’s configuration.

General hydras:

  • Zoom: increase/decrease current buffer text size
  • Transpose: transpose various constructs of text
  • Toggle mode: turn frequently “toggled” modes on and off

Enhancement packages:

  • hydra-posframe: use posframe to display hydra buffers at custom positions NOTE: This package is not currently available on MELPA. There’s an open issue to get it added: Ladicle/hydra-posframe#3
(use-package hydra
  :bind
  ("C-c z" . hydra-zoom/body)
  ("C-c T" . hydra-transpose/body)
  ("C-c M" . hydra-toggle-mode/body)

  :config
  ;; Zoom
  (defhydra hydra-zoom ()
    "Zoom"
    ("i" text-scale-increase "In")
    ("o" text-scale-decrease "Out")
    ("q" nil "Quit" :color blue))

  ;; Transpose
  (defhydra hydra-transpose (:color red)
    "Transpose"
    ("c" transpose-chars "Characters")
    ("w" transpose-words "Words")
    ("l" transpose-lines "Lines")
    ("s" transpose-sentences "Sentences")
    ("p" transpose-paragraphs "Paragraphs")
    ("q" nil "Quit" :color blue))

  ;; Toggle mode
  (defhydra hydra-toggle-mode (:color blue)
    "Toggle"
    ("c" centered-window-mode "Centered Buffer")
    ("w" whitespace-mode "Whitespace")
    ("f" focus-mode "Focus")
    ("i" aggressive-indent-mode "Aggressive indent")
    ("s" flyspell-mode "FlySpell")
    ("S" flyspell-prog-mode "FlySpell Prog")
    ("q" nil "Quit")))

;; TODO: [hydra/posframe] Waiting for MELPA package
;;       https://github.com/Ladicle/hydra-posframe/issues/3
;; (use-package hydra-posframe
;;   :hook (after-init . hydra-posframe-enable))

Evil

Vim emulation in Emacs. Because: yes, you can have the best of both worlds!

Below you’ll find various extensions to my Evil layer that generally improve the quality of life. This first configuration block is simply to turn Evil on at start and add some NeoTree bindings for compatability.

(use-package evil
  :init
  (setq evil-want-C-u-scroll t)
  (evil-mode)
  :config
  (evil-define-key 'normal neotree-mode-map (kbd "TAB") 'neotree-enter)
  (evil-define-key 'normal neotree-mode-map (kbd "SPC") 'neotree-quick-look)
  (evil-define-key 'normal neotree-mode-map (kbd "q") 'neotree-hide)
  (evil-define-key 'normal neotree-mode-map (kbd "RET") 'neotree-enter))

Compatibility

Make some things play nicer with Evil

Magit

(use-package evil-magit)

smartparens

(use-package evil-smartparens
  :hook
  (smartparens-enabled . evil-smartparens-mode))

Org

(use-package evil-org
  :after (org)
  :hook
  ((org-mode . evil-org-mode)
   (evil-org-mode . (lambda ()
              (evil-org-set-key-theme)))))

Surround

Easily surround, emulating surround.vim

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

Goggles

Visual hints when performing Evil operations (dd, yy, cw, p, etc.)

(use-package evil-goggles
  :config
  (evil-goggles-mode)
  (evil-goggles-use-diff-faces))

Lion

Align operators (gl & gL), emulating lion.vim

(use-package evil-lion
  :config
  (evil-lion-mode))

Traversal

EasyMotion

Buffer traversal made easy! Emulates easymotion.vim

(use-package evil-easymotion
  :config
  (evilem-default-keybindings "SPC"))

Snipe

2-char searching with f, F, t, T operators. Like seek.vim/sneak.vim

(use-package evil-snipe
  :after (evil-quickscope)
  :config
  (evil-snipe-mode 1)
  (evil-snipe-override-mode 1))

Quickscope

Highlight targets for f, F, t, T operators. Emulates quick_scope.vim

(use-package evil-quickscope
  :config
  (global-evil-quickscope-mode 1))

Commentary

Easily comment lines/blocks. Emulates commentary.vim

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

Exchange

Exchange operator for exchanging constructs of text. Emulates exchange.vim

(use-package evil-exchange
  :config
  (evil-exchange-install))

Multiple Cursors

Having multiple cursors can be very powerful. This allows you to perform simultaneous actions at multiple positions within the buffer.

(use-package evil-multiedit
  :config
  (evil-multiedit-default-keybinds)
  (evil-ex-define-cmd "ie[dit]" 'evil-multiedit-ex-match))

Custom functions

Useful functions gathered that don’t quite require an entire package.

Sort words

Taken from here; just a handy little function to sort words in a region alphabetically

(defun cm/sort-words (reverse beg end)
  "Sort words in region alphabetically, in REVERSE if negative.
    Prefixed with negative \\[universal-argument], sorts in reverse.

    The variable `sort-fold-case' determines whether alphabetic case
    affects the sort order.

    See `sort-regexp-fields'."
  (interactive "*P\nr")
  (sort-regexp-fields reverse "\\w+" "\\&" beg end))

Sensible beginning of line

Taken from here, I use this to replace move-beginning-of-line (C-a). It will take your point back to the first column of the line you’re on, as per the indentation. A second press will then take your point back to the very beginning of the line. Pressing again will take you back to the indented column.

(defun cm/sensible-move-beginning-of-line (arg)
  "Move point back to indentation of beginning of line.

  Move point to the first non-whitespace character on this line.
  If point is already there, move to the beginning of the line.
  Effectively toggle between the first non-whitespace character and
  the beginning of the line.

  If ARG is not nil or 1, move forward ARG - 1 lines first.  If
  point reaches the beginning or end of the buffer, stop there."
  (interactive "^p")
  (setq arg (or arg 1))

  ;; Move lines first
  (when (/= arg 1)
    (let ((line-move-visual nil))
      (forward-line (1- arg))))

  (let ((orig-point (point)))
    (back-to-indentation)
    (when (= orig-point (point))
      (move-beginning-of-line 1))))

(global-set-key [remap move-beginning-of-line]
                'cm/sensible-move-beginning-of-line)

Yank filename

Simple little function to copy the current filename to the clipboard.

(defun cm/yank-filename ()
  "Copy the current buffer file name to the clipboard."
  (interactive)
  (let ((filename (if (equal major-mode 'dired-mode)
                     default-directory
                   (buffer-file-name))))
    (when filename
      (kill-new filename)
      (message "Copied buffer file name '%s' to the clipboard." filename))))

Load theme

Fully unloads the current theme and loads the new one of choice. This is handy to have as loading a theme over another can leave behind bad faces.

(defun cm/switch-theme (theme)
  "Disable active themes and load THEME."
  (interactive (list (intern (completing-read "Theme: "
                               (->> (custom-available-themes)
                                 (-map #'symbol-name))))))
  (mapc #'disable-theme custom-enabled-themes)
  (load-theme theme 'no-confirm))

New blog post

A convenience function to create a new Org file suited to blogging with Hugo.

(defvar cm/blog-location)
(defvar cm/blog-project)
(setq cm/blog-location (concat (getenv "HOME") "/dev/cmacr.ae/content/post"))
(setq cm/blog-project "~/dev/cmacr.ae")
(defun cm/new-blog-post  ()
  "Set up a new blog post file & buffer."
  (interactive)
  (setq today (format-time-string "%Y-%m-%d")
        title (read-string "Title: ")
        filename (replace-regexp-in-string " " "-" (downcase title)))
  (write-region
    (format "#+date: %s\n#+title: %s\n#+tags[]: %s\n\n"
      today
      title
      (read-string "Tags: "))
     nil
     (format "%s/%s"
       cm/blog-location
        (concat
          (format "%s-" today)
          (format "%s.org" filename)))
    ;; Don't automatically overwrite existing file
    nil nil nil t)
  (projectile-persp-switch-project cm/blog-project)
  (find-file (format "%s/%s-%s.org" cm/blog-location today filename))
  (cm/persp-neo)
  (company-mode)
  (company-emoji-init)
  (emojify-mode)
  (end-of-buffer)
  (centered-window-mode)
					(delete-other-windows))

Appearance

Configuration related to the appearance of Emacs

Hide stuff

Hide various elements of the Emacs GUI:

  • toolbar
  • tooltips
  • scrollbar
  • menubar
  • blinking cursor
  • macOS titlebar (transparent)
  • frame title
(dolist (mode
  '(tool-bar-mode
    tooltip-mode
    scroll-bar-mode
    blink-cursor-mode))
  (funcall mode 0))

(cond
  ((string-equal system-type "darwin")
      (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))))
(setq frame-title-format '(""))

Fringes

Fringes always looked too fat to me by default, and take up too much space. This just makes them a bit thinner and turns the fringe off completely where I don’t feel it’s necessary.

(fringe-mode '(4 . 0))

(defun cm/hide-fringes ()
  (set-window-fringes (selected-window) 0 0))

(add-hook 'eshell-mode 'cm/hide-fringes)

Centered buffers

A really simple package that will centre your buffer contents in the frame. Purely cosmetic, but I do find it helps with focus from time to time. If I’m working on something that only needs one buffer, I’ll usually centre it. I have this bound to a key in my toggle-mode hydra so I can switch it on/off easily.

(use-package centered-window)

Current line highlighting

Highlights the current line of the point. Just helps to visualise where you are in the buffer. I turn it on globally, but explicitly turn it off where I don’t deem it necessary.

(global-hl-line-mode t)

(make-variable-buffer-local 'global-hl-line-mode)
(defvar my-ghd-modes '(
                       shell-mode-hook
                       git-commit-mode-hook
                       term-mode-hook
                      )
  "Modes to ensure global-hl-line-mode is disabled for.")
  (dolist (m my-ghd-modes)
    (add-hook m (lambda () (setq global-hl-line-mode nil))))

Indent guides

Cool little package to provide indentation guides. This will display a line of | characters with a comment face to indicate the indentation of the current block.

(use-package highlight-indent-guides
  :hook
  (prog-mode . highlight-indent-guides-mode)
  :config
  (setq highlight-indent-guides-auto-odd-face-perc        15
        highlight-indent-guides-auto-even-face-perc       15
        highlight-indent-guides-auto-character-face-perc  20
	   highlight-indent-guides-responsive                'stack
        highlight-indent-guides-method                    'character))

Rainbow Delimiters

So handy! This will colourize delimiters differently based on their depth. Really helps you not get burried when you’re in deep.

(use-package rainbow-delimiters
  :hook
  (prog-mode . rainbow-delimiters-mode)
  (yaml-mode . rainbow-delimiters-mode))

All the icons

Fancy! Just a bit of extra prettiness. This places little glyphs around to better convey some things where text may be a bit cluttered. That, and it makes things look nice! We’re visual creatures, after-all.

In this first block, I’ve added a conditional call to the downloading of the all-the-icons font, based on the OS environment.

(use-package all-the-icons
  :init
  (cond
   ((string-equal system-type "darwin")
     (if (not
      (file-exists-p (concat (getenv "HOME") "/Library/Fonts/all-the-icons.ttf")))
      (all-the-icons-install-fonts "t")))))

Dired

Makes dired buffers a little more easy on the eyes. Actually very helpful when trying to pick some files out manually.

(use-package all-the-icons-dired
  :hook
  (dired-mode . all-the-icons-dired-mode))

Ivy

Icons in some ivy operations (file icons in counsel-find-file, etc.)

(use-package all-the-icons-ivy
  :hook (after-init . all-the-icons-ivy-setup)
  :init
  (setq all-the-icons-ivy-buffer-commands '())
  (setq all-the-icons-ivy-file-commands
    '(counsel-find-file
      counsel-file-jump
      counsel-recentf
      counsel-projectile-find-file
      counsel-projectile-find-dir)))

(use-package all-the-icons-ivy-rich
  :init (all-the-icons-ivy-rich-mode 1))

Theme

Fashion First!

Right now, I’m using the beautiful doom-one & doom-solarized-light themes from hlissner’s doom-themes. They’re high contrast, and easy on the eyes, and right enough to easily distinguish between different constructs, but not sickening.

(use-package doom-themes
  :init
  (setq doom-themes-enable-bold        t
        doom-themes-enable-italic      t
	   doom-themes-neotree-file-icons t
        doom-one-brighter-comments     t)
  (load-theme 'doom-solarized-light t)
  (doom-themes-neotree-config))

Modeline

The ever important modeline! Making your modeline look good and express useful information is vital, in my opinion. There’s a lot of info you can cram in there - but to do so tastefully and efficiently is key.

(use-package doom-modeline
  :hook (after-init . doom-modeline-mode)
  :config
  (setq doom-modeline-persp-name              nil
        doom-modeline-buffer-encoding         nil
        doom-modeline-icon                    t
        doom-modeline-buffer-file-name-style  'truncate-with-project))

Make focussed & file visiting buffers stand out

The following expression adds a little flair to focussed buffers and those visiting files. I have it activate upon visiting files and after switching perspectives.

(use-package solaire-mode
  :init
  (advice-add #'persp-load-state-from-file :after #'solaire-mode-restore-persp-mode-buffers)
  :hook
  (after-change-major-mode . turn-on-solaire-mode)
  :config
  (solaire-mode-swap-bg))

(use-package dimmer
  :hook (after-init . dimmer-mode)
  :config
  (dimmer-configure-hydra)
  (dimmer-configure-magit)
  (dimmer-configure-org)
  (dimmer-configure-posframe))

Font

Some configuration for fonts

Emoji

Because this is the world we live in: don’t hate, appreciate! Emojis can be fun in READMEs (and maybe Git commits where machine readability doesn’t matter all that much)

(use-package company-emoji
  :hook
  ((markdown-mode . company-mode)
   (git-commit-mode . company-mode))
  :config
  (add-to-list 'company-backends 'company-emoji))

(use-package emojify
  :hook
  ((markdown-mode . emojify-mode)
   (git-commit-mode . emojify-mode)
   (magit-status-mode . emojify-mode)
   (magit-log-mode . emojify-mode)))

Language Config

Configuration specific to languages I tend to use

Language Server Protocol

Serious “IDEness”…

(use-package lsp-mode
  :ensure t
  :commands (lsp lsp-deferred)
  :hook     (go-mode . lsp-deferred)
  :config
  ;; Performance tweaks
  (setq gc-cons-threshold 100000000)
  (setq read-process-output-max (* 1024 1024))

  ;; Set up before-save hooks to format buffer and add/delete imports.
  (defun lsp-go-install-save-hooks ()
    (add-hook 'before-save-hook #'lsp-format-buffer t t)
    (add-hook 'before-save-hook #'lsp-organize-imports t t))
  (add-hook 'go-mode-hook #'lsp-go-install-save-hooks))

(use-package lsp-ui :commands lsp-ui-mode)
(use-package company
  :config
  (setq company-idle-delay 0)
  (setq company-minimum-prefix-length 1))
(use-package company-lsp :commands company-lsp)
(use-package lsp-ivy :commands lsp-ivy-workspace-symbol)

Rego

(use-package rego-mode)

Markdown

Markdown compatability. Activate markdown-mode for .md files and turn on flyspell

(use-package markdown-mode
  :mode "\\.md\\'"
  :hook
  (markdown-mode . flyspell-mode))

Jinja2

Jinja2 compatability. Activate jinja2-mode for .j2 files

(use-package jinja2-mode
  :mode "\\.j2\\'")

JavaScript

JavaScript compatability. Activate js2-mode for .js files

(use-package js2-mode
  :mode "\\.js\\'")

HashiCorp

Compatability with HCL and Terraform syntax. Activate hcl-mode for .nomad files.

(use-package hcl-mode
  :mode "\\.nomad\\'")

(use-package terraform-mode
  :hook
  (terraform-mode . company-mode)
  (terraform-mode . (lambda ()
                      (when (and (stringp buffer-file-name)
                        (string-match "\\.tf\\(vars\\)?\\'" buffer-file-name))
                          (aggressive-indent-mode 0))))

  (before-save . terraform-format-buffer))

Org Config

Configuration for the brilliant Org mode!

General

  • A few global keybindings for captures, agenda, etc.
  • Turn on flyspell mode
  • Follow filesystem links for Org files
  • Agenda files directory
  • Custom capture templates
(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-cc" 'org-capture)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cb" 'org-iswitchb)
(use-package org-mode
  :ensure nil
  :hook (org-mode . flyspell-mode)
  :config
    (setq org-return-follows-link t
          org-src-fontify-natively t
          org-agenda-files '("~/org")
          org-capture-templates
          '(("t" "Todo" entry (file+headline "~/org/inbox.org" "Tasks")
             "* TODO %^{Brief Description} %^g\n%?\tAdded: %U")
            ("r" "ToRead" entry (file+headline "~/org/inbox.org" "Tasks")
             "* TOREAD %^{Title} %^g\n%?\tLink: %c")
            ("p" "Project" entry (file+headline "~/org/inbox.org" "Projects")
             "* %^{Brief Description} %^g\n%?\tAdded: %U")
            ("m" "Maybe" entry (file+headline "~/org/inbox.org" "Maybe/Some Day")
             "* %^{Brief Description} %^g\n%?\tAdded: %U"))))

org-bullets

Make Org headings look a bit fancier

(use-package org-bullets
  :hook
  (org-mode . (lambda () (org-bullets-mode 1))))

About

My literate Emacs configuration


Languages

Language:Emacs Lisp 100.0%