Skip to content
This repository has been archived by the owner on Jul 18, 2023. It is now read-only.

OldhamMade/emacs.d-27

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Emacs Configuration

Introduction

This file is a literate programming document written with org-mode and org-mode-babel which contains the configuration I use for development.

Contents

Use lexical binding

This buys a small improvement in performance, but every little bit helps.

;; -*- lexical-binding: t; -*-

early-init.el

The following configuration have been moved to early-init.el for performance reasons, and are therefore not tangled here.

Increase startup threshold

Emacs will run garbage collection after `gc-cons-threshold’ bytes of consing. The default value is 800,000 bytes, or ~ 0.7 MiB. By increasing to maximum we reduce the number of pauses due to garbage collection during setup.

(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes
      gc-cons-percentage 0.6)

Hide the toolbar

(ignore-errors
  (tool-bar-mode -1))

Hide scrollbars

(ignore-errors
  (scroll-bar-mode -1))

Disable alarm bells

(setq visible-bell nil)
(setq ring-bell-function 'ignore)

Disable instructions in the scratch buffer

(setq initial-scratch-message nil)

Reduce the message log to the last 512 entries

(setq message-log-max 512)

Enable debug mode when errors occur

(setq debug-on-error t)

Disable window resizing on font change

Emacs resizes the (GUI) frame when your newly set font is larger (or smaller) than the system default. This seems to add 0.4-1s to startup.

(setq frame-inhibit-implied-resize t)

Disable the cursor blink

This doesn’t seem to persist from early-init.el and seems to have a big impact on performance, especially in large Org files.

(blink-cursor-mode 0)
(setq visible-cursor nil)

Startup

Better GC handling

Define a sane value for general use

(defvar my--gc-cons-threshold 16777216)

Set sane value after startup

At the end of setup this value should be returned to the default, once startup has completed.

(add-hook 'emacs-startup-hook
  (lambda ()
  (setq gc-cons-threshold my--gc-cons-threshold
        gc-cons-percentage 0.1)))

Handle the minibuffer differently

I want to make sure any minibuffer operations won’t trigger gc, so tools like flx won’t pause.

(defun my/minibuffer-setup-hook ()
  (setq gc-cons-threshold most-positive-fixnum))

(defun my/minibuffer-exit-hook ()
  ;; Defer it so that commands launched immediately after will enjoy the
  ;; benefits.
  (run-at-time
    1 nil (lambda () (setq gc-cons-threshold my--gc-cons-threshold))))

(add-hook 'minibuffer-setup-hook #'my/minibuffer-setup-hook)
(add-hook 'minibuffer-exit-hook #'my/minibuffer-exit-hook)

Do a GC when emacs loses focus

(defun dotfiles--gc-on-last-frame-out-of-focus ()
  "GC if all frames are inactive."
  (if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
    (garbage-collect)))

(add-function :after after-focus-change-function
  #'dotfiles--gc-on-last-frame-out-of-focus)

Unset file-name-handler-alist temporarily

Emacs consults this variable every time a file is read or library loaded, or when certain functions in the file API are used (like expand-file-name or file-truename).

It does so to check if a special handler is needed to read the file, but none of these handlers are necessary for startup, so it is generally safe to disable it temporarily.

The disable step is being handled in early-init.el using the following:

;; copy to custom var
(defvar my--file-name-handler-alist file-name-handler-alist)
;; set original to nil
(setq file-name-handler-alist nil)

And here is the hook to re-set the value:

;; Restore after startup
(add-hook 'emacs-startup-hook
  (lambda ()
    (setq file-name-handler-alist my--file-name-handler-alist)))

Check for native JSON support

(if (functionp 'json-serialize)
  (message "Native JSON is available")
  (message "Native JSON is *not* available"))

Set native compilation if available

(if (and (fboundp 'native-comp-available-p)
   (native-comp-available-p))
   (setq comp-deferred-compilation t)
   (message "Native complation is *not* available"))

Add timestamps to messages

Adding timestamps to the messages so we can see whether anything is causing emacs to block for a significant amount of time.

;;; timestamps in *Messages*
(defun current-time-microseconds ()
  (let* ((nowtime (current-time))
         (now-ms (nth 2 nowtime)))
    (concat (format-time-string "[%Y-%m-%dT%T" nowtime) (format ".%d] " now-ms))))

(defadvice message (before test-symbol activate)
  (if (not (string-equal (ad-get-arg 0) "%s%s"))
      (let ((deactivate-mark nil)
            (inhibit-read-only t))
        (with-current-buffer "*Messages*"
          (goto-char (point-max))
          (if (not (bolp))
              (newline))
          (insert (current-time-microseconds))))))

Disable compiler warnings

Moved to early-init.el.

(setq byte-compile-warnings nil)

Allow answering ‘Yes or No’ prompts with Y or N

Moved to early-init.el.

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

Confirm exiting emacs

Moved to early-init.el.

(setq confirm-kill-emacs 'y-or-n-p)

Enable better interop with OS clipboard

Moved to early-init.el.

(setq save-interprogram-paste-before-kill t)

Enforce newline at end of files

(setq require-final-newline t)

Hide cursor in non-focussed windows

Moved to early-init.el.

(setq cursor-in-non-selected-windows nil)

Persist highlight in non-focussed windows

Moved to early-init.el.

(setq highlight-nonselected-windows t)

Disable using tabs as indents

(setq-default indent-tabs-mode nil)

Enable use of the clipboard

Moved to early-init.el.

(setq select-enable-clipboard t)

Allow overwriting selected text

Moved to early-init.el.

(delete-selection-mode 1)

Always focus the *Help* buffer when it opens

I prefer the *Help* buffer to gain focus when it opens so I can hit q to close it and go back to where I was.

Moved to early-init.el.

(setq help-window-select t)

Disable debug-on-error once we’ve loaded

This is useful during the start-up process, but becomes a nuisance once we’re in edit mode.

(add-hook 'emacs-startup-hook
  (lambda ()
    (setq debug-on-error nil)))

Increase the amount of data Emacs reads from processes

(setq read-process-output-max (* 1024 1024)) ;; 1mb

Basic settings

UTF-8 everywhere

Note: moved to early-init

(set-charset-priority 'unicode)
(setq locale-coding-system   'utf-8)   ; pretty
(set-terminal-coding-system  'utf-8-unix)   ; pretty
(set-keyboard-coding-system  'utf-8)   ; pretty
(set-selection-coding-system 'utf-8)   ; please
(prefer-coding-system        'utf-8)   ; with sugar on top
(setq default-process-coding-system '(utf-8-unix . utf-8-unix))

Environment

Flag that emacs is active (for use with tmux)

(setenv "INSIDE_EMACS" "1")

Pick up PATH from zsh

(defun my/configure-path ()
  (let ((path (shell-command-to-string ". ~/.zshrc; echo -n $PATH")))
    (setenv "PATH" path)
    (setq exec-path
          (append
           (split-string-and-unquote path ":")
           exec-path))))

(add-hook 'after-init-hook 'my/configure-path)

Enable direnv

direnv is a great tool for managing local environment during development. This package integrates direnv with Emacs so that programs started from within emacs, such as inferior shells, linters, compilers, and test runners, will be looked up in the correct $PATH, and will be started with the correct environment variables set.

(use-package direnv
  :delight
  :config
  (add-hook 'emacs-startup-hook (direnv-mode)))

exec-path-from-shell

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :delight
  :config
    (with-eval-after-load 'exec-path-from-shell-initialize))

Ensure system packages deps are available

(use-package use-package-ensure-system-package
  :delight)

Add asdf to exec path

(add-to-list 'exec-path (expand-file-name "~/.asdf/shims"))

Interface

macOS fixes

Use native full-screen

(setq ns-use-native-fullscreen t)

Don’t open new windows from terminal

This stops new windows (frames) opening when calling emacs from the terminal with a filename

(setq ns-pop-up-frames nil)

Try to fix colours

(setq ns-use-srgb-colorspace t)

Theme

My personal theme: https://github.com/OldhamMade/leiptr-them

(use-package leiptr-theme
  :straight (leiptr :type git :host github :repo "OldhamMade/leiptr-theme")
  :init (load-theme 'leiptr t))

Font: SanFranciscoMono

This has been set in early-init.el, but repeated here for completeness.

(set-face-attribute 'default nil :font "SFMono Nerd Font:pixelsize=10:weight=normal:slant=normal:width=normal:spacing=100:scalable=true:hinting=true")

Enable Emoji

(use-package unicode-fonts
  :ensure t
  :delight
  :config
    (when (member "Apple Color Emoji" (font-family-list))
      (set-fontset-font t 'symbol "Apple Color Emoji" nil 'prepend))
    (unicode-fonts-setup))

Show a visual bell

(use-package mode-line-bell
  :defer
  :delight
  :config
    (setq mode-line-bell-flash-time 0.4)
    (add-hook 'emacs-startup-hook 'mode-line-bell-mode))

Show a bell when using the cursors too much

I’d like to use more “jump” commands, but I rely on arrow keys too much. This should hopefully remove that reliance.

(use-package annoying-arrows-mode
  :defer
  :delight
  :config
    (add-hook 'emacs-startup-hook 'global-annoying-arrows-mode))

Install popup for packages that require it

(use-package popup
  :defer
  :delight)

Better help

(use-package helpful
  :defer
  :delight
  :bind (("C-h f" . helpful-callable)
         ("C-h v" . helpful-variable)
         ("C-h k" . helpful-key)))

Key bindings

macOS modifier keys

(setq mac-command-modifier 'alt
      mac-option-modifier 'meta
      mac-command-modifier 'hyper
      mac-right-option-modifier nil)

macOS standard keybindings

(bind-keys*
 ;; undo/redo handled by alternative package
 ; ("H-z" . undo)
 ; ("H-Z" . redo)
 ;; moving around
 ("<next>" . (lambda () (interactive)
               (condition-case nil (scroll-up)
                 (end-of-buffer (goto-char (point-max))))))
 ("<prior>" . (lambda () (interactive)
                (condition-case nil (scroll-down)
                  (beginning-of-buffer (goto-char (point-min))))))
 ;; Select all
 ("H-a" . mark-whole-buffer)
 ;; cut
 ("H-x" . kill-region)
 ;; copy
 ("H-c" . kill-ring-save)
 ;; paste
 ("H-v" . yank)
 ;; open
 ("H-o" . find-file)
 ;; save
 ("H-s" . save-buffer)
 ;;  close
 ("H-w" . (lambda ()
            (interactive)
            (my-kill-buffer
             (current-buffer))))
 ;; quit
 ("H-q" . save-buffers-kill-emacs)
 ;; minimise
 ("H-m" . iconify-frame)
 ;; hide
 ("H-h" . ns-do-hide-emacs)
 ;; jump to beginning of line
 ("H-<left>" . beginning-of-line)
 ;; jump to end of line
 ("H-<right>" . end-of-line)
 )

Add general for more convenient key definitions

(use-package general
  :delight)

Tools for finding free keys

(use-package free-keys
  :defer
  :delight)

Navigation

which-key

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup. For example, after enabling the minor mode if you enter C-x and wait for the default of 1 second the minibuffer will expand with all of the available key bindings that follow C-x (or as many as space allows given your settings).

I’m using which-key to try and remove my reliance on custom Hydras with H-<key> bindings.

(use-package which-key
  :delight
  :config
  (setq which-key-idle-delay .4
        which-key-side-window-location 'bottom
        which-key-side-window-max-height 0.25)
  (which-key-mode 1))

hercules

hercules.el lets us call any group of related command sequentially with no prefix keys, while showing a handy which-key-style popup to remember the bindings for those commands.

I’m using this to remove my reliance on custom Hydras from my previous config.

(use-package hercules
  :defer
  :delight)

amx

amx is an alternative interface for M-x in Emacs. It provides several enhancements over the ordinary execute-extended-command, such as prioritizing your most-used commands in the completion list and showing keyboard shortcuts, and it supports several completion systems for selecting commands, such as ido and ivy.

(use-package amx
  :defer
  :delight)

flx

Whenever I do searches I prefer the fuzzy-matching style, similar to fzf on the commandline. flx provides similar functionality in emacs.

(use-package flx
  :defer
  :delight)

Ivy/Counsel/Swiper

ivy is a generic completion mechanism for Emacs. While it operates similarly to other completion schemes such as icomplete-mode, Ivy aims to be more efficient, smaller, simpler, and smoother to use yet highly customizable.

Counsel takes this further, providing versions of common Emacs commands that are customised to make the best use of ivy.

And Swiper is an alternative to isearch that uses ivy to show an overview of all matches.

I’m trialing Ivy/Counsel/Swiper as a replacement for ido + smex and isearch.

ivy

(use-package ivy
  :delight
  :defer
  :init
    (ivy-mode 1)
  :bind
    (("C-x C-b" . ivy-switch-buffer)
     ("C-x b" . ivy-switch-buffer)
     ("C-c i r" . ivy-resume))
  :config
    (setq ivy-use-virtual-buffers t
          enable-recursive-minibuffers t
          ivy-height 10
          ivy-wrap t
	       ivy-extra-directories nil
          ;; disable ^ prefix
          ivy-initial-inputs-alist nil
          ;; enable fuzzy matches eveywhere
	       ivy-re-builders-alist
	         '((swiper . ivy--regex-plus)
          (t . ivy--regex-fuzzy))  ;; fuzzy-search everywhere
	       ivy-count-format "(%d/%d) ")
    ;; Use C-j for immediate termination with the current value, and RET
    ;; for continuing completion for that directory. This is the ido
    ;; behaviour.
    ;; TODO: Remove me, to get used to proper ivy usage
    (general-define-key
      :keymaps 'ivy-minibuffer-map
        "C-j" 'ivy-immediate-done
        "RET" 'ivy-alt-done))

counsel

(use-package counsel
  :delight
  :defer
  :after (ivy)
  :bind
    ((:map counsel-describe-map ("M-." . counsel-find-symbol))
     ("C-x C-f" . counsel-find-file)
     ("C-M-f" . counsel-rg)
     ("C-M-r" . counsel-recentf)
     ("C-x m" . counsel-mark-ring))
  :init
    (require 'amx)
    (counsel-mode)
  :config
    (setq counsel-find-file-ignore-regexp (regexp-opt '("./" "..")))
    (setq counsel-fzf-cmd "fd -H | fzf -f \"%s\"")
    (add-to-list 'ivy-re-builders-alist '(counsel-ag-function . ivy--regex))
    (add-to-list 'ivy-re-builders-alist '(counsel-fzf-function . ivy--regex))
    (add-to-list 'ivy-sort-functions-alist '(counsel-fzf-function . nil)))

swiper

(use-package swiper
  :delight
  :defer
  :general
    ("C-s" 'swiper)
  :init
    (setq ivy-display-style 'fancy))

ivy-rich

ivy-rich is a more friendly interface for ivy, providing inline help and other “rich” data.

(use-package ivy-rich
  :defer
  :delight
  :after (ivy counsel)
  :config
  (ivy-rich-mode 1))

Projectile

Automagically interact with “projects”; git, mercurial, bazaar, and darcs repos are seen as projects by default.

projectile

(use-package projectile
  :delight
  :custom
  (projectile-enable-caching t)
  :config
  (defun get-projectile-root ()
    "Return path `matcha-projectile' can print in heading."
    (if (projectile-project-p)
        (file-name-nondirectory
         (directory-file-name
          (file-name-directory (projectile-project-root))))
      "Not in Project"))
  )

  (add-hook 'emacs-startup-hook (lambda () (projectile-mode +1)))

Integrate projectile with counsel

(use-package counsel-projectile
  :delight
  :after
    (counsel projectile)
  :init
    (setq projectile-completion-system 'ivy
          projectile-switch-project-action 'projectile-vc)
  :config
    (general-define-key
      :keymaps 'projectile-mode-map
      "C-c p" 'projectile-command-map)
    (counsel-projectile-mode))

Custom keybindings

(general-def
  :keymaps 'projectile-command-map
  "A" 'projectile-add-known-project
  "K" 'projectile-remove-known-project
  "DEL" 'projectile-cleanup-known-projects)

Minibuffer

Disable ability to overwrite minibuffer prompt

This stops the cursor entering the prompt text in the minibuffer when using shortcuts such as CTRL-A.

(setq minibuffer-prompt-properties
      '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt))

Enable recursive editing

We can make the minibuffer much more useful by enabling recursive usage. This means that when the minibuffer is active we can still call commands that require the minibuffer.

(setq enable-recursive-minibuffers t)

With this setting enabled, it’s easy to lose track of whether we’re in a recursive minibuffer or not. We display the recursion level in the minibuffer to avoid confusion.

(minibuffer-depth-indicate-mode 1)

Minibuffer “shortcuts”

When selecting a file to visit, // in the path will mean / (root) and ~ will mean $HOME regardless of preceding text

(setq file-name-shadow-tty-properties '(invisible t))

Dim the part of the path that will be replaced.

(file-name-shadow-mode 1)

Modeline

Use mini-modeline

(use-package mini-modeline
  :init
    (setq mini-modeline-r-format
      (list
        ; Modified?
        '(:eval (when (buffer-modified-p)
          (propertize "*"
            'help-echo "Buffer has been modified"
            'face 'font-lock-warning-face)))
        ; Read only?
        '(:eval (when buffer-read-only
          (propertize "!"
            'help-echo "Buffer is read-only"
            'face 'font-lock-type-face)))
        ; Current filename
        '(:eval (propertize " %b" 'help-echo (buffer-file-name)))
        ; Current git branch
        ;'(:eval (propertize '(vc-mode vc-mode)
        ;  'face 'git-commit-comment-file-face))
        '(vc-mode vc-mode)
        '(:eval (propertize projectile--mode-line
          'help-echo "Current project"
          'face 'font-lock-keyword-face))
        " "
        ; Current line and column
        (propertize "%l:%c" 'help-echo "Line and column index")
        ; Total lines
        '(:eval (propertize (format "[%s]" (or my/mode-line-buffer-line-count "?"))
          'help-echo "Total lines"
          'face 'compilation-line-number))
        ))
  :config
    (mini-modeline-mode t)
  :custom
    (mini-modeline-echo-duration 3)
    (mini-modeline-right-padding 1)
    (mini-modeline-enhance-visual nil)
  :custom-face
    (mini-modeline-face-attr `(:background ,(face-attribute 'default :background)))
    ;(mini-modeline-mode-line ((t (:background "#FFFFFF" :box nil :height 0.1))))
    ;(mini-modeline-mode-line-inactive ((t (:background "#EEEEEE" :box nil :height 0.1))))
    )

Ensure buffer names are unique

(defun my/load-uniquify ()
  (require 'uniquify)
  (setq uniquify-buffer-name-style 'forward))

(add-hook 'emacs-startup-hook 'my/load-uniquify)

Display total lines in file

(defvar my/mode-line-buffer-line-count nil)
(make-variable-buffer-local 'my/mode-line-buffer-line-count)

(defun my/mode-line-count-lines ()
  (setq my/mode-line-buffer-line-count (int-to-string (count-lines (point-min) (point-max)))))

(add-hook 'after-init-hook 'my/mode-line-count-lines)
(add-hook 'find-file-hook 'my/mode-line-count-lines)
(add-hook 'after-save-hook 'my/mode-line-count-lines)
(add-hook 'after-revert-hook 'my/mode-line-count-lines)
(add-hook 'dired-after-readin-hook 'my/mode-line-count-lines)

Highlights

Indentation

Note: using :hooks keyword causes issues because this is a minor-mode.

(use-package highlight-indentation
  :defer
  :delight
  :hook ((prog-mode sass-mode yaml-mode) . highlight-indentation-mode)
  :config
    (set-face-background 'highlight-indentation-face "#222"))

Delimiters

Show paren pairs

I want to see the paren matches, but I don’t want to be too distracted by them.

(setq show-paren-delay 0
      show-paren-style 'parenthesis)
(set-face-background 'show-paren-match "#456")
(set-face-foreground 'show-paren-match "#cde")
(set-face-attribute 'show-paren-match nil :weight 'extra-bold)
(show-paren-mode t)

Show delimiters with differing, paired colours

Note: using :hooks keyword causes issues because this is a minor-mode.

(use-package rainbow-delimiters
  :defer
  :delight
  :hook ((org-mode prog-mode sass-mode) . rainbow-delimiters-mode))

Variables

Rainbow identifiers subtly changes the look of variables, to make them a little easier to visually search

(use-package rainbow-identifiers
  :delight
  :defer
  :config
    (add-hook 'prog-mode-hook (lambda ()
                                (unless (eq major-mode 'js2-mode)
                                  (rainbow-identifiers-mode)))))

Bad spaces

Show bad whitespace

(setq whitespace-style '(face lines-tail
                         trailing space-before-tab
                         indentation empty space-after-tab))

Make sure sneaky no-break spaces are displayed.

(setq nobreak-char-display 0)

Highlight trailing whitespace

(dolist (hook '(prog-mode-hook
                text-mode-hook))
  (add-hook hook
    (lambda () (setq show-trailing-whitespace t))))

Lines that go over 80 chars for prog/web modes

(defun my/load-whitespace ()
  (require 'whitespace)
  (setq whitespace-line-column 80) ;; limit line length
  (whitespace-mode +1))

(add-hook 'prog-mode-hook 'my/load-whitespace)
(add-hook 'web-mode-hook 'my/load-whitespace)

Colour references, displaying the colour referenced

(use-package rainbow-mode
  :delight
  :defer
  :hook (sass-mode css-mode emacs-lisp-mode))

Changes to the buffer caused by commands such as ‘undo’, ‘yank’/’yank-pop’, etc.

(use-package volatile-highlights
  :delight
  :defer
  :config (add-hook 'emacs-startup-hook (lambda ()(volatile-highlights-mode t))))

Keywords like TODO/FIXME/etc

(use-package hl-todo
  :defer
  :delight
  :hook (emacs-startup . global-hl-todo-mode))

Syntax highlighting by default

Wait until emacs has loaded, then enable syntax highlighting everywhere

(add-hook 'emacs-startup-hook
  (lambda () (global-font-lock-mode 1)))

Prettify symbols

(add-hook 'emacs-startup-hook
  (lambda () (global-prettify-symbols-mode +1)))

Change cursor dynamically, depending on the context

I was previously using cursor-chg to change the cursor color dynamically, but I found that it can cause some serious lag while typing, to the point where I would be waiting for a second or two for the sentence I’d just written to display at all.

This is a small snippet I found which works the same way but without the performance penalty.

Display the cursor as grey for read-only buffers, red when in overwrite mode, or white otherwise.

(setq my/set-cursor-color-color "")
(setq my/set-cursor-color-buffer "")
(defun my/set-cursor-color-according-to-mode ()
  "change cursor color according to some minor modes."
  ;; set-cursor-color is somewhat costly, so we only call it when needed:
  (let ((color
         (if buffer-read-only
             "#BBB"
           (if overwrite-mode
               "#C00"
             "#FFF"))))
    (unless (and
             (string= color my/set-cursor-color-color)
             (string= (buffer-name) my/set-cursor-color-buffer))
      (set-cursor-color (setq my/set-cursor-color-color color))
      (setq my/set-cursor-color-buffer (buffer-name)))))
(add-hook 'post-command-hook 'my/set-cursor-color-according-to-mode)

Files

Set a large recentf list, after startup

(add-hook 'emacs-startup-hook
  (lambda ()
    (setq recentf-max-menu-items 100
          recentf-max-saved-items 100)
    (recentf-mode 1)
    ))

Enable auto-save of files as they are edited, so that no changes are lost

(use-package super-save
  :delight
  :defer
  :hook (emacs-startup . super-save-mode)
  :init
    (setq super-save-auto-save-when-idle t  ;; autosave to the real file
          super-save-idle-duration 5  ;; autosave idle wait
          auto-save-default nil)  ;; disable autosave to backup file
  )

Backup files to a local directory.

(setq auto-save-file-name-transforms `((".*" ,"~/.emacs.d/auto-backup/" t))
      backup-directory-alist '(("." . "~/.emacs.d/auto-backup/"))    ; don't litter my fs tree
      backup-by-copying t      ; don't clobber symlinks
      delete-old-versions t
      kept-new-versions 6
      kept-old-versions 2
      version-control t)       ; use versioned backups

Disable annoying lockfiles

(setq create-lockfiles nil)

Always append a new line to the file

(setq require-final-newline t)

Clean whitespace intelligently on key-press

(use-package shrink-whitespace
  :delight
  :defer
  :general
    ("<S-backspace>" #'shrink-whitespace))

Copy Filename to Clipboard

(defun copy-file-name-to-clipboard ()
  "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))))

Reveal in Finder

(use-package reveal-in-osx-finder
  :delight
  :defer
  :general
    ("H-O" #'reveal-in-osx-finder))

Buffers

Initial buffer major mode: text

Switch to text-mode once startup has completed.

(setq initial-major-mode 'fundamental-mode)
(add-hook 'emacs-startup-hook
  (lambda ()
    (setq initial-major-mode 'text-mode)))

New Empty Buffer

(defun new-empty-buffer ()
  "Create a new buffer called untitled(<n>)"
  (interactive)
  (let ((newbuf (generate-new-buffer-name "untitled")))
    (switch-to-buffer newbuf)))

(general-define-key "H-n" 'new-empty-buffer)

Make the *scratch* buffer persistent across sessions

(use-package persistent-scratch
  :delight
  :config
    (setq persistent-scratch-save-file (expand-file-name "~/Dropbox/.emacs.persist/.scratch"))
  :hook (emacs-startup . persistent-scratch-setup-default))

(defun my/set-scratch-as-text ()
  (with-current-buffer (get-buffer "*scratch*")
    (let ((mode "text-mode"))
      (message "Setting scratch to text-mode")
      (funcall (intern mode)))))

(defadvice persistent-scratch-restore (after advice-persistent-scratch-restore activate)
  (my/set-scratch-as-text))

  ;; yas-reload-all unfortunately triggers `persistent-scratch-setup-default`
  ;; again, resetting the scratch to fundamental-mode, so advising here too.
  ;; (defadvice yas-reload-all (after advice-yas-reload-all activate)
  ;;  (my/set-scratch-as-text))

Bury special buffers instead of killing

(setq bury-buffer-names '("*scratch*" "*Messages*" "*dashboard*"))

(defun kill-buffer-query-functions-maybe-bury ()
  "Bury certain buffers instead of killing them."
  (if (member (buffer-name (current-buffer)) bury-buffer-names)
      (progn
        (kill-region (point-min) (point-max))
        (bury-buffer)
        nil)
    t))

(add-hook 'kill-buffer-query-functions 'kill-buffer-query-functions-maybe-bury)

(defun my-kill-buffer (buffer)
  "Protect some special buffers from getting killed."
  (interactive (list (current-buffer)))
  (if (member (buffer-name buffer) bury-buffer-names)
      (call-interactively 'bury-buffer buffer)
    (kill-buffer buffer)))

Kill all buffers except current

(defun my/kill-all-buffers-except-current ()
  "Kill all buffers except current buffer."
  (interactive)
  (let ((current-buf (current-buffer)))
    (dolist (buffer (buffer-list))
      (set-buffer buffer)
      (unless (eq current-buf buffer)
        (kill-buffer buffer)))))
(general-define-key (kbd "C-x K") 'my/kill-all-buffers-except-current)

Copy buffer path to kill ring

(defun copy-full-path-to-kill-ring ()
  "copy buffer's full path to kill ring"
  (interactive)
  (when buffer-file-name
    (kill-new (file-truename buffer-file-name))))

Echo buffer path

(defun describe-variable-short (var)
  (interactive "vVariable: ")
  (message (format "%s: %s" (symbol-name var) (symbol-value var))) )

(defun get-buffer-path ()
  "print the buffer path in the mini buffer"
  (interactive)
  (when buffer-file-name
    (kill-new (file-truename buffer-file-name))
    (message (format "Path: %s (copied to kill-ring)" (file-truename buffer-file-name)))
    ))

Better ibuffer listing

;; fix syntax highlighting ((
(use-package ibuffer-vc
  :hook
    (ibuffer-hook . (lambda ()
      (ibuffer-vc-set-filter-groups-by-vc-root)
      (unless (eq ibuffer-sorting-mode 'filename/process)
        (ibuffer-do-sort-by-filename/process))))
  :config
    (define-ibuffer-column size-h
      (:name "Size" :inline t)
      (cond
       ((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0)))
       ((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0)))
       (t (format "%8d" (buffer-size)))))
    (setq ibuffer-formats
      '((mark modified read-only vc-status-mini " "
          (name 18 18 :left :elide)
          " "
          (size-h 9 -1 :right)
          " "
          (mode 16 16 :left :elide)
          " "
          filename-and-process)
        (mark modified read-only vc-status-mini " "
          (name 18 18 :left :elide)
          " "
          (size-h 9 -1 :right)
          " "
          (mode 16 16 :left :elide)
          " "
          (vc-status 16 16 :left)
          " "
          filename-and-process)))
          )

Switch buffers

(use-package buffer-move
  :defer
  :delight
  :general
    (:keymaps 'global
      "<H-M-up>" 'buf-move-up
      "<H-M-down>" 'buf-move-down
      "<H-M-left>" 'buf-move-left
      "<H-M-right>" 'buf-move-right))

Moving around

Enable subword mode

(global-subword-mode 1)

Make the goto-map keymap better

M-g, the goto-map, is somewhat limited. Since we have avy and it’s friends, let’s add further options.

(general-define-key
  :keymaps 'goto-map
  "<up>" 'beginning-of-buffer
  "<down>" 'end-of-buffer
  "<left>" '("previous points" . pop-global-mark)
  "." '("previous M-. tag" . pop-tag-mark)
  )

And now let’s bind C-;, a more comfortable key combo which is generally unused, to goto-map.

(general-define-key
  "C-;" (general-simulate-key "M-g"))

Move Where I Mean

C-a and C-e normally moves the cursor to the beginning/end of the line unconditionally.

mwim is more useful, as it moves to the first non-whitespace character if we’re already at the beginning of the line. Repeated use of C-a toggles between these two positions.

C-e will toggle to the end of the line ignoring comments, or to the true end of the line.

(use-package mwim
  :commands (mwim-beginning mwim-end)
  :delight
  :general
    (:keymaps 'override
      "C-a" #'mwim-beginning
      "C-e" #'mwim-end
      "H-<left>" #'mwim-beginning
      "H-<right>" #'mwim-end))

Jumping around with avy and friends

avy

(use-package avy
  :defer
  :delight
  :general
    ("C-." #'avy-goto-char)
    (:keymaps 'goto-map
     ";" 'avy-goto-char
     "C-;" 'avy-goto-char-2
     "c" 'avy-goto-char-timer
     "w" 'avy-goto-word-1
     "l" 'avy-goto-line
     )
  :config (setq avy-all-windows nil))

Jump back to the last edit

(use-package goto-last-change
  :delight
  :defer
  :general
    (:keymaps 'goto-map
     "-" 'goto-last-change))

Quickly jump between other symbols found at point

Smart Scan will try to infer the symbol your point is on and let you jump to other, identical, symbols elsewhere in your current buffer with a single key stroke.

Use M-n and M-p move between symbols, and M-' to replace all symbols in the buffer matching the one under point.

(use-package smartscan
  :defer
  :delight
  :hook (emacs-startup . (lambda () (smartscan-mode 1))))

Editing

Undo/redo

Add undo-fu for better undo behaviour

(use-package undo-fu
  :straight (undo-fu :type git :host gitlab :repo "ideasman42/emacs-undo-fu")
  :defer
  :delight
  :init
    (global-unset-key (kbd "H-z"))
    (global-unset-key (kbd "H-Z"))
  :general
    ("H-z" #'undo-fu-only-undo)
    ("H-Z" #'undo-fu-only-redo))

Add undo-fu-session for history

(use-package undo-fu-session
  :straight (undo-fu-session :type git :host gitlab :repo "ideasman42/emacs-undo-fu-session")
  :delight
  :after undo-fu
  :init
    (setq undo-fu-session-directory (expand-file-name "~/Dropbox/.emacs.persist/.undohist")
          undo-fu-session-incompatible-files
          '("COMMIT_EDITMSG"
            "NOTES_EDITMSG"
            "MERGE_MSG"
            "TAG_EDITMSG"
            "\\.gpg\\'"
            "/tmp"
            file-remote-p)))

Unfill

Unfill adds the inverse of fill-paragraph/-region.

(use-package unfill
  :delight
  :defer)

Indents

Enable automatic indenting

(electric-indent-mode +1)

Set tab width to 4 for all buffers

(setq-default tab-width 4)

Cursors

Multiple cursors

Allows editing with multiple points on the screen.

Base package
(use-package multiple-cursors
  :demand
  :delight
  :general
    (:prefix-map 'my/mc-map
      "n" #'mc/mark-next-like-this
      "p" #'mc/mark-previous-like-this
      "j" #'mc/skip-to-next-like-this
      "-" #'mc/skip-to-previous-like-this
      "a" #'mc/mark-all-like-this
      "N" #'mc/mark-next-symbol-like-this
      "P" #'mc/mark-previous-symbol-like-this
      "A" #'mc/mark-all-symbols-like-this
      "." #'mc/mark-all-dwim
      "1" #'mc/insert-numbers
      "L" #'mc/insert-letters
      "l" #'mc/edit-lines
      "s" #'mc/sort-regions
      "r" #'mc/reverse-regions
      )
  :config
  (hercules-def
    :toggle-funs #'my/mc-mode
    :keymap 'my/mc-map
    :transient t)
  (general-define-key
    :keymaps 'mc/keymap
    "<return>" nil)
  (general-define-key
    (kbd "C-c m") #'my/mc-mode))
FIXME ace-mc

ace-mc makes it really easy to add and remove multiple cursors using ace jump mode.

(use-package ace-mc
  :after (multiple-cursors)
  :delight
  :general ("C-c C-m" #'ace-mc-add-multiple-cursors))

Smart regions

Smart region guesses what you want to select by one command:

  • If you call this command multiple times at the same position, it expands the selected region (with `er/expand-region’).
  • Else, if you move from the mark and call this command, it selects the region rectangular (with `rectangle-mark-mode’).
  • Else, if you move from the mark and call this command at the same column as mark, it adds a cursor to each line (with `mc/edit-lines’).
(use-package smart-region
  :delight
  :after (multiple-cursors)
  :general ("C-\\" #'smart-region)
  :hook (emacs-startup . smart-region-on))

Remember cursor position when reopening files

(save-place-mode 1)
(setq save-place-forget-unreadable-files nil)

Expanding regions

I use expand region a lot. M-[ feels like a good binding, with the mental connection of “open” (expand) and conversely M-] as “close” (contract).

(use-package expand-region
  :defer
  :delight
  :general
    (:keymaps 'global
      "M-[" #'er/expand-region
      "M-]" #'er/contract-region))

FIXME: elixir mode should have expansions similar to ruby-mode ;:config ;(er/enable-mode-expansions ‘elixir-mode ‘er/add-ruby-mode-expansions)

Braces

Auto-pair braces

Emacs 24.4+ comes with electric-pair-mode which matches autopair in terms of functionality.

I disable it in the minibuffer as it usually just gets in the way there.

(electric-pair-mode t)
(add-hook 'minibuffer-setup-hook (lambda () (electric-pair-mode -1)))
(add-hook 'minibuffer-exit-hook (lambda () (electric-pair-mode t)))

Embrace

Add/Change/Delete pairs based on expand-region

(use-package embrace
  :delight
  :defer
  :general
    ("C-'" #'embrace-commander)
  :hook
    (ruby-mode . embrace-ruby-mode))

Move text with M-<up> and M-<down>

(use-package move-text
  :delight
  :defer
  :hook (emacs-startup . move-text-default-bindings))

Crux: A Collection of Ridiculously Useful eXtensions

crux bundles a few useful interactive commands to enhance your overall Emacs experience.

(use-package crux
  :delight
  :defer
  :commands
    (crux-duplicate-current-line-or-region
     crux-smart-kill-line
     crux-rename-file-and-buffer
     crux-kill-other-buffers
     crux-capitalize-region
     crux-upcase-region
     crux-downcase-region)
  :general
    ("M-D" #'crux-duplicate-current-line-or-region
     "C-k" #'crux-smart-kill-line
     "C-c R" #'crux-rename-file-and-buffer
     "C-c K" #'crux-kill-other-buffers
     "C-c c c" #'crux-capitalize-region
     "C-c c u" #'crux-upcase-region
     "C-c c l" #'crux-downcase-region
     )
  :config
    (crux-reopen-as-root-mode))

Set title for whichkey prefix entry

(define-key mode-specific-map "c" '("change case"))

Whitespace

Delete trailing whitespace

(general-define-key (kbd "C-c DEL") 'delete-trailing-whitespace)

Allow inserting into whitespace-separated blocks

(use-package dynamic-spaces
  :delight
  :defer
  :hook (emacs-startup . dynamic-spaces-global-mode))

Inflection (camel/kebab/snake case)

(use-package string-inflection
  :defer
  :delight
  :general ("M-C" #'string-inflection-all-cycle))

Up/Down-case DWIM

(general-define-key (kbd "M-c") 'capitalize-dwim)
(general-define-key (kbd "M-u") 'upcase-dwim)
(general-define-key (kbd "M-l") 'downcase-dwim)

Replace zap-to-char with avy-zap

(use-package avy-zap
  :delight
  :defer
  :after (avy)
  :general
    ("M-z" #'avy-zap-to-char-dwim)
    ("M-Z" #'avy-zap-up-to-char-dwim)
  )

Spelling

flyspell-correct-ivy

flyspell-correct is a package for distraction-free words correction with flyspell via a selected interface.

(use-package flyspell-correct-ivy
  :delight
  :defer
  :general
    ("C-M-;" #'flyspell-correct-wrapper)
  :init
    (setq flyspell-correct-interface #'flyspell-correct-ivy))

ace-flyspell

Jump to and correct spelling errors using avy and flyspell.

(use-package ace-flyspell
  :delight
  :defer
  :hook (emacs-startup-hook . ace-flyspell-setup))

Search/replace

Highlight matches in query-replace mode

(setq query-replace-highlight t)

Add deadgrep for searching

Ripgrep is faster than grep, and deadgrep provides a great UI. It also allows inline editing through deadgrep-edit-mode, which is great for refactoring in combination with visual-regexp.

(use-package deadgrep
  :defer
  :delight
  :init
    (defun config-editing--on-enter-deadgrep-edit-mode (&rest _)
      (message "Entering edit mode. Changes will be made to underlying files as you edit."))
    (defun config-editing--on-exit-deadgrep-edit-mode (&rest _)
      (when (derived-mode-p 'deadgrep-edit-mode)
        (message "Exiting edit mode.")))
  :general
    ("C-c d" 'deadgrep)
  :config
    (advice-add #'deadgrep-edit-mode :after #'config-editing--on-enter-deadgrep-edit-mode)
    (advice-add #'deadgrep-mode :before #'config-editing--on-exit-deadgrep-edit-mode)
    (defun deadgrep--format-command-patch (rg-command)
      "Add --hidden to rg-command."
      (replace-regexp-in-string "^rg " "rg --hidden " rg-command)))

(general-define-key
  :keymaps 'deadgrep-mode-map
  "e" 'deadgrep-edit-mode
  "t" '(lambda () (interactive) (deadgrep--search-term nil))
  "r" '(lambda () (interactive) (setq deadgrep--search-type 'regexp) (deadgrep-restart))
  "s" '(lambda () (interactive) (setq deadgrep--search-type 'string) (deadgrep-restart))
  "d" '(lambda () (interactive) (deadgrep--directory nil))
  )

(general-define-key
  :keymaps 'deadgrep-edit-mode-map
  "<escape>" 'deadgrep-mode)

Synonym injection

(use-package synosaurus
  :defer
  :delight
  ; doesn't work with emacs-plus
  ;:ensure-system-package
  ;  (wn . wordnet)
  :commands (synosaurus-mode
             synosaurus-lookup
             synosaurus-choose-and-replace)
  :general
    ("C-c S" #'synosaurus-choose-and-replace)
  :init
  (setq synosaurus-backend 'synosaurus-backend-wordnet
        synosaurus-choose-method 'popup))

Visual regexp

(use-package visual-regexp
  :delight
  :defer
  :general
    (:keymaps 'global
      "C-c r" 'vr/replace
      "C-c q" 'vr/query-replace)
  :config
    (general-define-key
      :keymaps 'my/mc-map
      "q" #'vr/mc-mark))

Windows

Automatically balance windows when created

(use-package balanced-windows
  :delight
  :defer
  :hook
    (emacs-startup . balanced-windows-mode))

Use ace-window to move around

(use-package ace-window
  :delight
  :defer
  :general
    ("M-o" 'ace-window)
  :config
    (ace-window-display-mode t)
    (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
          aw-dispatch-always t))

Allow “zooming” a buffer to full-screen

(use-package zoom-window
  :delight
  :defer
  :init (custom-set-variables
         '(zoom-window-mode-line-color "DarkGreen"))
  :general
    ("C-x C-z" 'zoom-window-zoom))

Perspectives (workspaces)

(use-package persp-projectile
  :defer
  :delight
  :after (projectile)
  :init (use-package perspective)
  :hook (emacs-startup . persp-mode)
  :general
    (:keymaps 'perspective-map
     "x" 'projectile-persp-switch-project)
    ;; override projectile-switch-project since
    ;; we always want to be in a perspective
    (:keymaps 'projectile-command-map
     "p" 'projectile-persp-switch-project))

Recover window split using C-c <left> with winner-mode

(defun my/load-winner-mode ()
  (winner-mode 1))
(add-hook 'emacs-startup-hook 'my/load-winner-mode)

Better splitting defaults

Try to reuse existing windows where possible

(setq display-buffer-alist
  '((".*" (display-buffer-reuse-window display-buffer-same-window))))

Try to reuse windows in other frames

(setq display-buffer-reuse-frames t)

Avoid resizing the display-buffer if possible

(setq even-window-sizes nil)

Better split functionality

Function which prefers a “horizontal” split

(defun split-window-sensibly-prefer-horizontal (&optional window)
"Based on split-window-sensibly, but designed to prefer a horizontal split,
i.e. windows tiled side-by-side."
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window t)
         ;; Split window horizontally
         (with-selected-window window
           (split-window-right)))
    (and (window-splittable-p window)
         ;; Split window vertically
         (with-selected-window window
           (split-window-below)))
    (and
         ;; If WINDOW is the only usable window on its frame (it is
         ;; the only one or, not being the only one, all the other
         ;; ones are dedicated) and is not the minibuffer window, try
         ;; to split it horizontally disregarding the value of
         ;; `split-height-threshold'.
         (let ((frame (window-frame window)))
           (or
            (eq window (frame-root-window frame))
            (catch 'done
              (walk-window-tree (lambda (w)
                                  (unless (or (eq w window)
                                              (window-dedicated-p w))
                                    (throw 'done nil)))
                                frame)
              t)))
     (not (window-minibuffer-p window))
     (let ((split-width-threshold 0))
       (when (window-splittable-p window t)
         (with-selected-window window
          (split-window-right))))))))

Function which provides a new “sensible” split

(defun split-window-really-sensibly (&optional window)
  (let ((window (or window (selected-window))))
    (if (> (window-total-width window) (* 2 (window-total-height window)))
        (with-selected-window window (split-window-sensibly-prefer-horizontal window))
        (with-selected-window window (split-window-sensibly window)))))

Configure preferences

(setq
  split-height-threshold 4
  split-width-threshold 40
  split-window-preferred-function 'split-window-really-sensibly)

Add ability to “rotate” layouts and windows

(use-package rotate
  :delight
  :defer
  :general
    ("C-c C-SPC" 'rotate-layout))

Version Control

Highlight diff in fringe

(use-package diff-hl
  :delight
  :defer
  :general
    (:keymaps 'goto-map
     "n" 'diff-hl-next-hunk
     "p" 'diff-hl-previous-hunk)
  :hook (emacs-startup . global-diff-hl-mode))

Browse-at-remote

browse-at-remote opens the current buffer at github/gitlab/bitbucket/stash/git.savannah.gnu.org/sourcehut from Emacs.

It supports various kind of emacs buffer, like:

  • file buffer
  • dired buffer
  • magit-mode buffers representing code
  • vc-annotate mode (use get there by pressing C-x v g by default)
(use-package browse-at-remote
  :defer
  :delight
  :general ("C-c g g" 'browse-at-remote))

Use magit and forge with git repositories

(use-package magit-todos
  :delight
  :after (projectile magit))

(use-package magit-gitflow
  :delight
  :after (projectile magit))

(use-package magit-diff-flycheck
  :delight
  :after (projectile magit))

(use-package magit
  :delight
  :defer
  :after projectile
  :general ("C-x g" 'magit-status)
  :commands (magit-status
             magit-log
             magit-commit
             magit-stage-file)
  :hook (magit-mode . turn-on-magit-gitflow)
  :config
  (setq magit-branch-arguments nil
        magit-branch-read-upstream-first 'fallback
        magit-diff-paint-whitespace t
        magit-diff-highlight-indentation (quote (("" . tabs)))
        magit-fetch-arguments (quote ("--prune"))
        magit-pull-arguments (quote ("--rebase"))
        magit-push-arguments (quote ("--set-upstream"))
        magit-log-arguments (quote ("--graph" "--color" "--decorate" "-n256")))
  (magit-todos-mode t)
  (transient-append-suffix 'magit-pull "C"
    '("A" "Autostash" "--autostash"))
  )

(defun magit-set-repo-dirs-from-projectile ()
  "Set `magit-repository-directories' with known Projectile projects."
  (setq magit-repository-directories
        (mapcar (lambda (dir)
                  (cons dir 0))
                (seq-filter (lambda (dir)
                              (file-exists-p (expand-file-name ".git" dir)))
                            projectile-known-projects))))

(with-eval-after-load 'projectile
  (magit-set-repo-dirs-from-projectile))

(add-hook 'projectile-switch-project-hook
          #'magit-set-repo-dirs-from-projectile)
(setq smerge-command-prefix "\C-c m")

reintroduce smerge keybindings

I originally had the following magit hook, but without hydra it raises an error. Needs to be replaced with a General setup.

(magit-diff-visit-file . (lambda ()
                                   (when smerge-mode
                                     (my/smerge-hydra/body))))

smerge keybindings

(general-define-key
  :keymaps 'smerge-mode-map
  :prefix "C-c -"
  "" '(nil :which-key "smerge")
  "n" '(smerge-next :which-key "next")
  "p" '(smerge-prev :which-key "previous")
  "o" '(smerge-keep-lower :which-key "other (upper)")
  "m" '(smerge-keep-upper :which-key "mine (lower)")
  "u" '(smerge-keep-upper :which-key "upper (other)")
  "l" '(smerge-keep-lower :which-key "lower (mine)")
  )

Add advice around branch naming to convert spaces to dashes

(advice-add 'magit-whitespace-disallowed :around
  (lambda (orig-fun &rest args) (interactive) (insert "-")))

magit-log date headers

This tweak uses the package ov, short for Overlay. Overlay is capable of manipulating text appearance, cursor behavior, etc. It doesn’t affect font-lock or text-properties.

;;; fix syntax highlighting: <(
(use-package ov
  :after (magit)
  :delight
  :config
    (defun unpackaged/magit-log--add-date-headers (&rest _ignore)
      "Add date headers to Magit log buffers."
      (when (derived-mode-p 'magit-log-mode)
        (save-excursion
          (ov-clear 'date-header t)
          (goto-char (point-min))
          (cl-loop with last-age
                   for this-age = (-some--> (ov-in 'before-string 'any (line-beginning-position) (line-end-position))
                                            car
                                            (overlay-get it 'before-string)
                                            (get-text-property 0 'display it)
                                            cadr
                                            (s-match (rx (group (1+ digit) ; number
                                                                " "
                                                                (1+ (not blank))) ; unit
                                                         (1+ blank) eos)
                                                     it)
                                            cadr)
                   do (when (and this-age
                                 (not (equal this-age last-age)))
                        (ov (line-beginning-position) (line-beginning-position)
                            'after-string (propertize (concat " " this-age "\n")
                                                      'face 'magit-section-heading)
                            'date-header t)
                        (setq last-age this-age))
                   do (forward-line 1)
                   until (eobp)))))

    (define-minor-mode unpackaged/magit-log-date-headers-mode
      "Display date/time headers in `magit-log' buffers."
      :global t
      (if unpackaged/magit-log-date-headers-mode
          (progn
            ;; Enable mode
            (add-hook 'magit-post-refresh-hook #'unpackaged/magit-log--add-date-headers)
            (advice-add #'magit-setup-buffer-internal :after #'unpackaged/magit-log--add-date-headers))
        ;; Disable mode
        (remove-hook 'magit-post-refresh-hook #'unpackaged/magit-log--add-date-headers)
        (advice-remove #'magit-setup-buffer-internal #'unpackaged/magit-log--add-date-headers)))
)

Dired

Set listing switches

(setq dired-listing-switches "-alh")

Collapse paths like Github does

(use-package dired-collapse
  :delight
  :defer
  :commands (dired-collapse-mode)
  :hook (dired-mode . dired-collapse-mode))

Allow filename editing directly in the buffer

(use-package dired-efap
  :delight
  :defer
  :general
    (:keymaps 'dired-mode-map
     "r" 'dired-efap))

Add archive browsing

(use-package dired-avfs
  :delight
  :defer)

Provide better listing similar to k and ll

(use-package dired-k
  :delight
  :defer
  :init
    (setq dired-k-human-readable 1
          dired-k-padding 1)
  :hook
    ((dired-mode-hook . dired-k)
     (dired-after-readin-hook . dired-k-no-revert)))

Colorise the files based on type/extensions

(use-package dired-rainbow
  :delight
  :defer
  :after (dired)
  :config
  (progn
    (dired-rainbow-define-chmod directory "#6cb2eb" "d.*")
    (dired-rainbow-define html "#eb5286" ("css" "less" "sass" "scss" "htm" "html" "jhtm" "mht" "eml" "mustache" "xhtml"))
    (dired-rainbow-define xml "#f2d024" ("xml" "xsd" "xsl" "xslt" "wsdl" "bib" "json" "msg" "pgn" "rss" "yaml" "yml" "rdata"))
    (dired-rainbow-define document "#9561e2" ("docm" "doc" "docx" "odb" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub" "odp" "ppt" "pptx"))
    (dired-rainbow-define markdown "#ffed4a" ("org" "etx" "info" "markdown" "md" "mkd" "nfo" "pod" "rst" "tex" "textfile" "txt"))
    (dired-rainbow-define database "#6574cd" ("xlsx" "xls" "csv" "accdb" "db" "mdb" "sqlite" "nc"))
    (dired-rainbow-define media "#de751f" ("mp3" "mp4" "MP3" "MP4" "avi" "mpeg" "mpg" "flv" "ogg" "mov" "mid" "midi" "wav" "aiff" "flac"))
    (dired-rainbow-define image "#f66d9b" ("tiff" "tif" "cdr" "gif" "ico" "jpeg" "jpg" "png" "psd" "eps" "svg"))
    (dired-rainbow-define log "#c17d11" ("log"))
    (dired-rainbow-define shell "#f6993f" ("awk" "bash" "bat" "sed" "sh" "zsh" "vim"))
    (dired-rainbow-define interpreted "#38c172" ("py" "ipynb" "rb" "pl" "t" "msql" "mysql" "pgsql" "sql" "r" "clj" "cljs" "scala" "js"))
    (dired-rainbow-define compiled "#4dc0b5" ("asm" "cl" "lisp" "el" "c" "h" "c++" "h++" "hpp" "hxx" "m" "cc" "cs" "cp" "cpp" "go" "f" "for" "ftn" "f90" "f95" "f03" "f08" "s" "rs" "hi" "hs" "pyc" ".java"))
    (dired-rainbow-define executable "#8cc4ff" ("exe" "msi"))
    (dired-rainbow-define compressed "#51d88a" ("7z" "zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar"))
    (dired-rainbow-define packaged "#faad63" ("deb" "rpm" "apk" "jad" "jar" "cab" "pak" "pk3" "vdf" "vpk" "bsp"))
    (dired-rainbow-define encrypted "#ffed4a" ("gpg" "pgp" "asc" "bfe" "enc" "signature" "sig" "p12" "pem"))
    (dired-rainbow-define fonts "#6cb2eb" ("afm" "fon" "fnt" "pfb" "pfm" "ttf" "otf"))
    (dired-rainbow-define partition "#e3342f" ("dmg" "iso" "bin" "nrg" "qcow" "toast" "vcd" "vmdk" "bak"))
    (dired-rainbow-define vc "#0074d9" ("git" "gitignore" "gitattributes" "gitmodules"))
    (dired-rainbow-define-chmod executable-unix "#38c172" "-.*x.*")
))

Org-mode

Tweaks

These are tweaks to the built-in org-mode

Ensure UTF-8

(setq org-export-coding-system 'utf-8)

Hide emphasis markers

Wit this option enabled, markers like =, /, * are hidden which makes for a neater view.

(setq org-hide-emphasis-markers t)

Enable shift-selection

Standard Emacs S-<cursor> commands conflict with Org’s use of S-<cursor> to change timestamps, TODO keywords, priorities, and item bullet types, etc. Since S-<cursor> commands outside of specific contexts do not do anything, Org offers the variable org-support-shift-select for customization. Org mode accommodates shift selection by:

  1. making it available outside of the special contexts where special commands apply, and
  2. extending an existing active region even if point moves across a special context.
(setq org-support-shift-select t)

Add font styles to DONE lines

It’s useful to have titles like TODO and DONE hilight differently.

(setq org-fontify-done-headline t)

SRC blocks

Style src blocks natively
(setq org-src-fontify-natively t)
Allow indenting natively within source blocks.
(setq org-src-tab-acts-natively nil)

Add “easy templates” using org-tempo

Use C-c C-, to trigger.

(require 'org-tempo)
Additional templates

Org-mode has “Easy Templates”, here are some additions:

(add-to-list 'org-structure-template-alist
  '("sl" . "src emacs-lisp :tangle yes"))
(add-to-list 'org-structure-template-alist
  '("se" . "src elixir"))
(add-to-list 'org-structure-template-alist
  '("sc" . "src crystal"))
(add-to-list 'org-structure-template-alist
  '("sn" . "src nim"))
(add-to-list 'org-structure-template-alist
  '("sp" . "src python"))
(add-to-list 'org-structure-template-alist
  '("sz" . "src zsh"))

Org-Capture

(general-define-key
  "C-c o" 'org-capture)
(setq org-default-notes-file "~/Dropbox/notes.org")
(defvar my-org-capture-directory
  (expand-file-name "~/Dropbox/org")
  "Location for all org-mode capture files.")
(defvar my-org-blog-directory
  (expand-file-name "~/Dropbox/blog/content-org")
  "Location for all org-mode capture files.")
doct: Declarative Org Capture Templates
(use-package doct
  :defer
  :delight
  :after org
  :init 
    (setq org-capture-templates '())
    (setq til-categories
      '(
         ("Ack" "a")
         ("ASDF" "A")
         ("Elixir" "x")
         ("Emacs" "e")
         ("Git" "g")
         ("Magit" "M")
         ("Makefiles" "m")
         ("Phoenix" "p")
         ("Python" "P")
         ("Shell" "s")
         ("Tmux" "t")
         ))
    (setq idea-categories
      '(
        ("Open-Source Library" "o")
        ("Physical Product" "p")
        ("Web App" "w")
        ("iOS App" "i")
        ("macOS App" "m")
        ))
  :hook (emacs-startup . (lambda ()
    (setq org-capture-templates
      (doct `(:group
       :empty-lines 1
       :children
       (("Tasks"
         :keys "t"
         :file ,(expand-file-name "tasks.org"  my-org-capture-directory)
         :clock-in t
         :clock-resume t
         :children
         (("Today" :keys "t" :type entry :headline "Uncategorized"
           :datetree t :tree-type week :template "* TODO %?\n %i\n %a\n")
          ("Reading" :keys "r" :type entry :headline "Reading"
           :template "* TODO %^{name}\n %a\n")
          ("Work" :keys "w" :type entry :headline "Work"
           :template "* TODO %^{taskname}\n %a\n")))
        ("Blog"
         :keys "b"
         :file ,(expand-file-name "posts.org"  my-org-blog-directory)
         :type entry
         :template "* TODO %^{name}   :%^{tags}:\n  :PROPERTIES:\n  :EXPORT_DATE: %u\n  :EXPORT_FILE_NAME: %^{slug}.md\n  :END:\n  \n  %i%?"
        )
        ("TIL"
         :keys "l"
         :file ,(expand-file-name "til.org"  my-org-blog-directory)
         :type entry
         :template "* TODO %^{name}   :%^{tags}:\n  :PROPERTIES:\n  :EXPORT_DATE: %u\n  :EXPORT_FILE_NAME: %^{slug}.md\n  :END:\n  \n  %i%?"
         :children ,(cl-loop for (key value) in (sort til-categories (lambda (a b) (string< (car a) (car b))))
             collect (list key :keys value :headline key))
        )
        ("Idea"
         :keys "i"
         :file ,(expand-file-name "project-ideas.org"  my-org-capture-directory)
         :type entry
         :template "* TODO %^{name}\n  %i%?\n  "
         :children ,(cl-loop for (key value) in (sort idea-categories (lambda (a b) (string< (car a) (car b))))
             collect (list key :keys value :headline key))
        )
        ("Project"
         :keys "p"
         :file ,(defun my/project-todo-file ()
                  (let ((file (expand-file-name "TODO.org"
                                                (when (functionp 'projectile-project-root)
                                                  (projectile-project-root)))))
                    (with-current-buffer (find-file-noselect file)
                      (org-mode)
                      ;; Set to UTF-8 because we may be visiting raw file
                      (setq buffer-file-coding-system 'utf-8-unix)
                      (when-let* ((headline (doct-get :headline)))
                        (unless (org-find-exact-headline-in-buffer headline)
                          (goto-char (point-max))
                          (insert "* " headline)
                          (org-set-tags (downcase headline))))
                      file)))
         :template (lambda () (concat "* %{todo-state} " (when (y-or-n-p "Link? ") "%A\n") "%?"))
         :todo-state "TODO"
         :children (("bug"           :keys "b" :headline "Bugs")
                    ("documentation" :keys "d" :headline "Documentation")
                    ("enhancement"   :keys "e" :headline "Enhancements")
                    ("feature"       :keys "f" :headline "Features")
                    ("optimization"  :keys "o" :headline "Optimizations")
                    ("miscellaneous" :keys "m" :headline "Miscellaneous")
                    ("security"      :keys "s" :headline "Security")))))
    )))))

Packages

Replace Org’s bullets with something less noisy

(use-package org-bullets
  :after org
  :delight
  :init
    (setq org-bullets-bullet-list '("" "" "" "" "" "" "" "⦿" "" ""))
  :hook (org-mode . org-bullets-mode))

Blogging

(use-package ox-hugo
  :after (:all org ox))

Utility functions

This section contains generally useful functions.

Paths & Dirs

(defun parent-directory (dir)
  (unless (equal "/" dir)
    (file-name-directory (directory-file-name dir))))

(defun find-file-in-hierarchy (current-dir fname)
  "Search for a file named FNAME upwards through the directory hierarchy, starting from CURRENT-DIR"
  (let ((file (concat current-dir fname))
        (parent (parent-directory (expand-file-name current-dir))))
    (if (file-exists-p file)
        file
      (when parent
        (find-file-in-hierarchy parent fname)))))

(defun find-dir-in-hierarchy (current-dir dname)
  "Search for a dir named DNAME upwards through the directory hierarchy, starting from CURRENT-DIR"
  (let ((dir (concat current-dir dname))
        (parent (parent-directory (expand-file-name current-dir))))
    (if (file-directory-p dir)
        dir
      (when parent
        (find-dir-in-hierarchy parent dname)))))

(defun find-include-dir ()
  "Search for the next available include dir from START."
  (let ((idir (find-dir-in-hierarchy (buffer-file-name) "include")))
    (if idir (concat "-I" idir) "")))

Programming

General enhancements

Jumping around

dumb-jump is an Emacs “jump to definition” package for 40+ languages that I find works really well.

Here I add certain functions to my custom jump keymap.

(use-package dumb-jump
  :defer
  :delight
  ; doesn't work with emacs-plus
  ;:ensure-system-package
  ;  (rg . ripgrep)
  :general
    (:keymaps 'goto-map
     "j j" 'dumb-jump-go
     "j b" 'dumb-jump-back
     "j o" 'dumb-jump-go-other-window)
  :config
   (setq dumb-jump-selector 'swiper
         dumb-jump-prefer-searcher 'rg
         dumb-jump-default-project "~/Projects"))

DWIM with comments

(use-package comment-dwim-2
  :defer
  :delight
  :general ("M-;" 'comment-dwim-2))

URL encode/decode functions

(defun func-region (start end func)
  "run a function over the region between START and END in current buffer."
  (save-excursion
    (let ((text (delete-and-extract-region start end)))
      (insert (funcall func text)))))

(defun url-encode (start end)
  "urlencode the region between START and END in current buffer."
  (interactive "r")
  (func-region start end #'url-hexify-string))

(defun url-decode (start end)
  "de-urlencode the region between START and END in current buffer."
  (interactive "r")
  (func-region start end #'url-unhex-string))

Language Server Protocol

lsp-mode

(use-package lsp-mode
  :commands lsp
  :delight
  :defer
  :bind ("C-c h" . lsp-describe-thing-at-point)
  :hook
    (elixir-mode . lsp-deferred)
    (python-mode . lsp-deferred)
  :init
    (add-to-list 'exec-path "~/Projects/elixir/elixir-ls/release")
    (setq lsp-keymap-prefix "C-c l")
  :config
    (dolist (dir '("build$" "deps$"))
            (push (concat "[/\\\\]" dir) lsp-file-watch-ignored))
    (setq
      lsp-auto-configure t
      lsp-auto-guess-root t
      lsp-eldoc-enable-hover nil
      lsp-enable-completion-at-point t
      lsp-enable-file-watchers t
      lsp-file-watch-threshold 10000
      lsp-keep-workspace-alive nil
      lsp-log-io t
      lsp-prefer-flymake nil
      )

    (add-hook 'before-save-hook
      (lambda () (when (eq major-mode 'elixir-mode)
                  (ignore-errors 'lsp-format-buffer))))
    (lsp-register-custom-settings
       '(("pyls.plugins.pyls_mypy.enabled" t t)
         ("pyls.plugins.pyls_mypy.live_mode" nil t)
         ;("pyls.plugins.pyls_black.enabled" t t)
         ;("pyls.plugins.pyls_isort.enabled" t t)
         ))
  )

lsp-eldoc-hook ‘(lsp-hover)

lsp-ui

(use-package lsp-ui
  :delight
  :commands lsp-ui-mode
  :after lsp-mode
  :config
    (setq
      lsp-ui-doc-enable nil
      lsp-ui-sideline-enable nil
      lsp-ui-sideline-show-hover nil
      lsp-ui-flycheck-enable t
      lsp-ui-sideline-ignore-duplicate t
      ))

lsp-ui-doc-enable t lsp-ui-doc-delay 1.0 lsp-ui-doc-position ‘bottom

lsp-ivy

(use-package lsp-ivy
  :delight
  :after (lsp ivy)
  :commands lsp-ivy-workspace-symbol)

eglot: Client for Language Server Protocol (LSP) servers

(use-package eglot
  :defer
  :delight
  :commands (eglot)
  :hook ((python-mode . eglot-ensure))
  :bind (:map eglot-mode-map
          ("C-c n" . eglot-rename) ; rename identifier
          ("C-c f" . eglot-format)))

dap-mode

Disabled since I don’t actively use it at the moment.

(use-package dap-mode)

Company mode

(use-package company
  :defer
  :delight
  :config
    (setq company-idle-delay 0.1) ; Make Company open a little faster
    (define-key company-active-map (kbd "C-f") 'company-filter-candidates) ; allow filtering
    (define-key company-active-map (kbd "C-/") 'counsel-company) ; move to minibuffer
  :hook (emacs-startup . global-company-mode) ; Enable company-mode globally
    )

Add lsp backend

(use-package company-lsp
  :delight
  :after (company lsp-mode)
  :commands company-lsp
  :config
    (setq company-lsp-cache-candidates 'auto)
    (push 'company-lsp company-backends))

Flycheck

(use-package flycheck
  :defer
  :delight ""
  :hook (emacs-startup . global-flycheck-mode))

flycheck-color-mode-line

An Emacs minor-mode for Flycheck which colors the mode line according to the Flycheck state of the current buffer.

(use-package flycheck-color-mode-line
  :defer
  :delight
  :after (flycheck)
  :hook (flycheck-mode . flycheck-color-mode-line-mode))

Code Folding

Disabled for now as I don’t use it

(use-package lsp-origami
  :defer
  :delight
  :general
    ("C-c TAB" 'origami-recursively-toggle-node)
  :hook (lsp-mode . lsp-origami-mode))

Erlang

I have to use the http://zotonic.com framework at my day job, so let’s add erlang and some zotonic helpers

(defun find-zotonic-include-dir ()
  "Search for the next available zotonic include dir from START."
  (let ((zdir (find-dir-in-hierarchy
    (file-name-directory buffer-file-name)
    (concat (file-name-as-directory "zotonic") "include"))))
      (if zdir (concat "-I" zdir) "")))

(defun my/define-erlang-flychecker ()
  (flycheck-define-checker erlang-otp
    "An Erlang syntax checker using the Erlang interpreter."
    :command ("~/.asdf/shims/erlc" "-o" temporary-directory "-Wall"
              (option-list "-I" flycheck-erlang-include-path)
              (eval (find-zotonic-include-dir))
              source)
    :error-patterns
    ((warning line-start (file-name) ":" line ": Warning:" (message) line-end)
     (error line-start (file-name) ":" line ": " (message) line-end))
    :modes erlang-mode))

(defun erlang-mode-flycheck-hook ()
  (flycheck-select-checker 'erlang-otp)
  (flycheck-mode))

(defun erlang-mode-compile-hook ()
  (require 'erlang-eunit)
  (when (projectile-project-p)
    (add-to-list 'erlang-compile-extra-opts (cons 'i  (projectile-project-p)))
    (add-to-list 'erlang-eunit-src-candidate-dirs (projectile-project-p))
    (add-to-list 'erlang-eunit-test-candidate-dirs (projectile-project-p))))

(defun erlang-mode-prettify-symbols-hook ()
  (setq-local
   prettify-symbols-alist
   (append
    '(("->" . ?→)
      ("=>" . ?⇒)
      ("<-" . ?←)
      ("<=" . ?⇐)
      (">=" . ?≥)
      ("=<" . ?≤)
      ("=/=" . ?≠)
      ("fun" . ))
    prettify-symbols-alist)))

(use-package erlang
  :defer
  :delight
  :after (flycheck)
  :init (my/define-erlang-flychecker)
  :mode (("\\.[eh]rl\\'" . erlang-mode)
         ("\\.yaws?\\'" . erlang-mode)
         ("\\.escript?\\'" . erlang-mode))
  :hook ((erlang-mode . erlang-mode-flycheck-hook)
         (erlang-mode . erlang-mode-prettify-symbols-hook)
         (erlang-mode . company-mode)
         (erlang-mode . erlang-mode-compile-hook)))

Add zotonic-tpl support

I don’t use Zotonic currently, so disabled for now

(use-package zotonic-tpl-mode
  :straight (zotonic-tpl-mode :type git :host github :repo "OldhamMade/zotonic-tpl-mode")
  :config
    (add-to-list 'auto-mode-alist '("\\.tpl\\'" . zotonic-tpl-mode)))

Elixir

Elixir is fast becoming my primary programming language, so there’s lots of tweaks and focus here

Add ruby-end to support the end keyword

(use-package ruby-end
  :defer
  :delight)

Add elixir-mode

(use-package elixir-mode
  :after (ruby-end elgot)
  :init
    (add-to-list
      'eglot-server-programs
        '(elixir-mode . ("sh" "~/Projects/elixir/elixir-ls/release/language-server.sh")))
    (add-to-list 'eglot-server-programs '(python-mode "pyls"))
  :delight
    (elixir-mode "[ex]")
    (ruby-end-mode "")
  :mode ("\\.exs?\\'" . elixir-mode)
  )
  ;(:config
  ;  (add-to-list 'eglot-server-programs
  ;    `(elixir-mode "~/Projects/elixir/elixir-ls/release/language-server.sh")))

:hook (company-mode lsp)

Add exunit.el

(use-package exunit
  :straight (exunit :type git :host github :repo "ananthakumaran/exunit.el")
  :delight
  :defer)

Add inf-elixir

Pop open and interact with iEX

(use-package inf-elixir
 :straight (inf-elixir :host github :repo "J3RN/inf-elixir")
 :functions
   (inf-elixir
    run-elixir
    inf-elixir-project
    inf-elixir-send-line
    inf-elixir-send-region
    inf-elixir-send-buffer))

Add flycheck-credo

(use-package flycheck-credo
  :delight
  :after (flycheck)
  :config
  (flycheck-credo-setup)
  (setq flycheck-elixir-credo-strict t))

Define custom functions

elixir-find-definition
(defun elixir-find-definition (var)
  (interactive "vDefinition: ")
  (lsp-find-definition var)
  )

Define custom elixir keymaps

Elixir keys start with C-c e.

(general-define-key
  :prefix "C-c e"
  "" '(nil :which-key "elixir")
  "t" '(exunit-verify-all :which-key "test project")
  "b" '(exunit-verify :which-key "test buffer")
  "u" '(exunit-verify-all-in-umbrella :which-key "test umbrella")
  "." '(exunit-verify-single :which-key "test at point")
  "r" '(exunit-rerun :which-key "rerun last")
  "F" '(xref-find-definitions :which-key "defs (here)")
  "f" '(xref-find-definitions-other-window :which-key "defs (other window)")
  "d" '(elixir-find-definition :which-key "jump to def")
  )

Prettify elixir symbols

(defun elixir-mode-prettify-symbols-hook ()
  (setq-local
   prettify-symbols-alist
   (append
    '(("->" . ?→)
      ("=>" . ?⇒)
      ("<-" . ?←)
      ("<=" . ?⇐)
      (">=" . ?≥)
      ("=<" . ?≤)
      ("!=" . ?≠)
      ("fn" . ))
    prettify-symbols-alist)))

Add hooks

(add-hook 'elixir-mode-hook 'elixir-mode-prettify-symbols-hook)
(add-hook 'elixir-mode-hook 'ruby-block-mode)
(add-hook 'elixir-mode-hook
          (lambda ()
            (set (make-variable-buffer-local 'ruby-end-expand-keywords-before-re)
                 "\\(?:^\\|\\s-+\\)\\(?:do\\)")
            (set (make-variable-buffer-local 'ruby-end-check-statement-modifiers)
                 nil)
            (ruby-end-mode 1)
            ))
(eval-after-load 'elixir-mode '(require 'ruby-mode-expansions))

Add support for .eex files

Here we’ll switch on web-mode so that we can edit HTML properly.

(add-to-list 'auto-mode-alist '("\\.l?eex\\'" . web-mode))
(setq web-mode-engines-alist
  '(("elixir" . "\\.l?eex\\'")))

Add support for ExActor keywords

(font-lock-add-keywords 'elixir-mode
  '(("\\<\\(defabcast\\|defabcastp\\|defcall\\|defcallp\\|defcast\\|defcastp\\|defhandlecall\\|defhandlecast\\|defhandleinfo\\|definit\\|defmulticall\\|defmulticallp\\|defstart\\|defstartp\\)\\>" 1 font-lock-keyword-face)))

Configure code folding

(add-to-list 'hs-special-modes-alist
  '(elixir-mode
    ("\\(cond\\|quote\\|defmacro\\|defmacrop\\|defp\\|def\\|if\\) .*\\(do\\)" 2) "\\(end\\)" "#"
      nil nil))

Reformat on save

(defun my/elixir-on-save-hook ()
  (add-hook 'before-save-hook
    (lambda ()
      (if (equal major-mode 'elixir-mode)
        (ignore-errors (elixir-format nil t))))))
(add-hook 'elixir-mode-hook 'my/elixir-on-save-hook)

Python

(use-package python-mode
  :defer
  :delight
  :config
    (setq lsp-pyls-plugins-pylint-enabled nil)
    (setq lsp-pyls-plugins-pycodestyle-enabled nil)
  :hook
    (python-mode . subword-mode)
    ;(python-mode .
    ;  (lambda ()
    ;    (defun py-describe-symbol nil)
    ;    (defun py-help-at-point nil)
    ;    ))
  :mode ("\\.py\\'" . python-mode))

Add docstring support

(use-package python-docstring
  :after python
  :bind
  (:map python-mode-map
    ([remap fill-paragraph] . python-docstring-fill)))

Better flycheck setup

Allows multiple syntax checkers to run in parallel on Python code Ideal use-case: pyflakes for syntax combined with mypy for typing

(use-package flycheck-pycheckers
  :after flycheck
  :ensure t
  :init
  (with-eval-after-load 'flycheck
    (add-hook 'flycheck-mode-hook #'flycheck-pycheckers-setup)
    )
  (setq flycheck-pycheckers-checkers
    '(
      mypy3
      pyflakes
      )
    )
  )

Manage python projects with poetry

(use-package poetry
  :delight
  :defer
  :init (setq poetry-tracking-strategy 'projectile)
  :hook (python-mode . poetry-tracking-mode))

Nim

(use-package nim-mode
  :defer
  :delight
  :mode ("\\.nim\\'" . nim-mode))

Crystal

(use-package crystal-mode
  :after (ruby-end)
  :defer
  :delight
  :mode ("\\.cr\\'" . crystal-mode)
  :hook
    ((crystal-mode . ruby-block-mode)
     (crystal-mode . ruby-end-mode)))

Add flycheck

(use-package flycheck-crystal
  :after (flycheck crystal-mode)
  :defer)

Pony

Disabled for now as I don’t use this language often.

(use-package ponylang-mode
  :defer
  :mode ("\\.pony\\'" . ponylang-mode)
  :config (setq tab-width 2))

Go-lang

Disabled for now as I don’t use this language often.

(use-package go-mode
  :defer
  :mode ("\\.go\\'" . go-mode)
  :config
    (setq tab-width 4))

Add flycheck mode

(use-package flycheck-pony
  :defer)

HTML et al

(use-package web-mode
  :defer
  :delight
  :config
  ;; use eslint with web-mode for jsx files
  (with-eval-after-load 'flycheck
    (flycheck-add-mode 'javascript-eslint 'web-mode))

  ;; adjust indents for web-mode to 2 spaces
  (defun my-web-mode-hook ()
    "Hooks for Web mode. Adjust indents"
    ;;; http://web-mode.org/
    (setq web-mode-markup-indent-offset 2)
    (setq web-mode-css-indent-offset 2)
    (setq web-mode-code-indent-offset 2))

  (add-hook 'web-mode-hook  'my-web-mode-hook))

(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
;(add-to-list 'auto-mode-alist '("\\.jsx\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.ecr\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))

JavaScript

(use-package js2-mode
  :defer
  :delight
  :config
  (progn
    (setq-default js-indent-level 4)
    (setq-default js2-basic-offset 4)
    (setq tab-width 4)
    (setq js-switch-indent-offset 4)
    ))

(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
(add-hook 'js2-mode-hook #'js2-imenu-extras-mode)
(add-hook 'js2-mode-hook 'company-mode)

TypeScript

(use-package typescript-mode
  :defer
  :delight
  :mode "\\.ts\\'"
  :config
    (setq typescript-indent-level 2)
    (setq tab-width 2)
    )

Sass/Scss

(use-package sass-mode
  :delight
  :defer
  :mode ("\\.s(c|a)ss\\'" . sass-mode))

Yaml

(use-package yaml-mode
  :defer
  :delight
  :mode ("\\.ya?ml\\'" . yaml-mode))

Markdown

(use-package markdown-mode
  :delight
  :defer
  :mode ("\\.md\\'" . markdown-mode))

Docker

(use-package dockerfile-mode
  :defer
  :delight
  :mode ("\\Dockerfile\\'" . dockerfile-mode))

RAML

Disabled for now as I don’t use this language often.

(use-package raml-mode
  :straight (raml-mode :type git :host github :repo "victorquinn/raml-mode")
  :defer
  :delight
  :init (setq raml-indent-offset 2)
  :mode "\\.raml\\'")

Gherkin/FDD

(use-package feature-mode
  :defer
  :delight
  :mode ("\\.feature$" . feature-mode))

Integrations

Harvest (time tracking)

Bound to C-c t for “time”.

Use c to start a new timer, then s to stop it, and finally t to edit the time entry at that point.

g: refresh the list n/p: next/previous entry f/b: Go to next/previous day q: bury buffer d: change date (Y-M-D or Y.M.D, or use +/-D)

Expects that ~/.authinfo will contain an entry for Harvest.

(use-package reaper
  :defer
  :delight
  :init (require 'auth-source)
  :general ("C-c t" #'reaper)
  :config
    (setq reaper-account-id (my-username "id.getharvest.com"))
    (setq reaper-api-key  (my-password "id.getharvest.com"))
    )

Define the password functions

(defun my-username (host)
  (nth 0 (auth-source-user-and-password host)))
(defun my-password (host)
  (nth 1 (auth-source-user-and-password host)))

Terminal

Multi-term

(use-package multi-term
  :delight
  :defer
  :config
  (setq multi-term-program "/bin/zsh"))

Misc tweaks

(setq term-scroll-show-maximum-output 1)
(setq system-uses-terminfo nil)
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
(add-to-list 'comint-output-filter-functions 'ansi-color-process-output)

Better terminal colours

(use-package eterm-256color
  :demand
  :delight
  :hook (term-mode . eterm-256color-mode))

Finalizing

Add profiler to diagnose start-up issues

(use-package esup
  :delight
  :defer)

Finally, display how long it took to start up

(add-hook 'emacs-startup-hook
  (lambda ()
    (message "Emacs ready in %s with %d garbage collections."
      (format "%.2f seconds"
        (float-time
          (time-subtract after-init-time before-init-time)))
      gcs-done)))

TODOs

Move early-init.el lines back into README.org

Tangle the early-init lines to a separate file.

Introduce matcha

Matcha may fix a lot of my annoyances about discoverability https://github.com/jojojames/matcha

Display menu for ace-window

I really want to see a which-key menu for the extra commands available for ace-window as I constantly forget them.

Unfortunately ace-window doesn’t have it’s own keymap, and I don’t want to create a hydra for this.

Custom keymap

Current approach would be to create a custom sparse-keymap, iterate through aw-dispatch-alist and populate the new keymap from it, then add some advice to one of the which-key functions to trigger which-key-show-minor-mode-keymap.

After a time-boxed attempt I have the following, but it isn’t working yet.

(use-package ace-window
  :delight
  :bind ("M-o" . ace-window)
  :config
    (ace-window-display-mode t)
    (setq aw-dispatch-always t)
    (progn
      (setq ace-window-map (make-sparse-keymap))
      (cl-loop for (key . value) in aw-dispatch-alist
               do (define-key ace-window-map key
                   (if (car-safe value)
                     (quote (car-safe value))
                       (quote value))))))

Show available commands using hercules

Another alternative, but the menu triggers after ace-window has completed, not during.

(general-def
  :prefix-map 'my-ace-window-map
  "x" #'aw-delete-window
  "m" #'aw-swap-window
  "M" #'aw-move-window
  "c" #'aw-copy-window
  "j" #'aw-switch-buffer-in-window
  "n" #'aw-flip-window
  "u" #'aw-switch-buffer-other-window
  "e" #'aw-execute-command-other-window
  "F" #'aw-split-window-fair
  "v" #'aw-split-window-vert
  "b" #'aw-split-window-horz
  "o" #'delete-other-windows
  "T" #'aw-transpose-frame
  "?" #'aw-show-dispatch-help
  )

(hercules-def
  :toggle-funs #'ace-window
  :keymap 'my-ace-window-map
  :transient t
  :flatten t)

Further reading

ace-mc should use home-row keys like avy

Investigate using ycmd for code completion

https://github.com/abingham/emacs-ycmd#company-ycmd

Add no-littering

https://github.com/emacscollective/no-littering

Better alchemist menu

Move from projectile to something lighter

I don’t use 90% of projectile functionality, and can’t imagine I ever would. Primarily I just want to have a perspective per project, and limit searches to that perspective.

Maybe moving to project.el would be better.

https://old.reddit.com/r/emacs/comments/b0jzy4/emacscast_8_writing_in_emacs_and_org_mode_part_1/eiiywwm/ https://old.reddit.com/r/emacs/comments/88v344/workspace_with_isolated_buffers_eyebrowseperspmode/

dap-mode for elixir

https://github.com/emacs-lsp/dap-mode#elixir

Capture command frequency

to see whether anything can be removed or should be focused on https://github.com/dacap/keyfreq

emacs-refactor for elixir

https://github.com/Wilfred/emacs-refactor

Improve refactor using deadgrep

Don’t edit paths in global search/replace

“Read only” text should stay that way

Remove M-` binding

M-` brings up tmm which is completely useless to me, so might as well be remapped to something handy. Maybe iterate through frames/windows?

Window (pane) management

Buffer navigation

https://github.com/joostkremers/nswbuff

Scroll on jump

For better https://github.com/emacsmirror/scroll-on-jump

Or alternatively, just keep the cursor centered

https://github.com/andre-r/centered-cursor-mode.el

Coverage

Display coverage inline https://github.com/trezona-lecomte/coverage

FIXME:

multiple cursors

About

My latest emacs config for v27.x

Topics

Resources

Stars

Watchers

Forks