Skip to content

Latest commit

 

History

History
2509 lines (2162 loc) · 72.5 KB

configuration.org

File metadata and controls

2509 lines (2162 loc) · 72.5 KB

Literate Programming init.el

Introduction

This is an literate programming init.el. Read Harry’s brief tutorial on how this works and how to do it yourself.

Helper for loading optional elisp files

  (defun winny/load-file-when-present (file)
    "Load FILE when it's present.

This suppresses an error caused by missing file.  It only prints
out an error when the file throws an error."
    (condition-case err
        (load file nil t t)
      (file-missing nil)                  ; Ignore missing optional file
      (error (message "Failed to load %s: %s" file (error-message-string err))
             nil)))

Utility stuff

Full ISO 8601

From https://wilkesley.org/~ian/xah/emacs/elisp_datetime.html

(defun winny/iso-8601 ()
  (concat
   (format-time-string "%Y-%m-%dT%T")
   ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5)))
    (format-time-string "%z"))))

(defun winny/insert-iso-8601 ()
  (interactive)
  (insert (winny/iso-8601)))

Load host-pre-init.el

(winny/load-file-when-present "~/.emacs.d/host-pre-init.el")

Basic settings

Hardcode some settings that I wish to never change in any Emacs setup.

(setq mode-require-final-newline t
      auto-revert-verbose nil
      async-shell-command-buffer 'new-buffer
      Man-notify-method 'friendly
      help-window-select 'other
      show-paren-delay 0.0
      custom-file "/dev/null"           ; XXX Windows
      custom-safe-themes t
      warning-suppress-types '((comp)))
(tool-bar-mode -1)
(tooltip-mode -1)
(when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(menu-bar-mode -1)
(column-number-mode 1)
(blink-cursor-mode 1)
(show-paren-mode 1)

Set up load path

This way packages can be installed from submodules or tracked directly in my git repository.

(add-to-list 'load-path "~/.emacs.d/site-lisp/")

Package Setup

Set up packaging sources.

(require 'package)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.org/packages/") t)
(when (< emacs-major-version 24)
  ;; For important compatibility libraries like cl-lib
  (add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)
(defconst winny/initialized-cookie-path
  (concat user-emacs-directory "/.w-firstboot-initialized"))
(unless (file-exists-p winny/initialized-cookie-path)
  (message "Detected first boot.  Doing initialization steps...")
  (package-refresh-contents)
  (with-temp-buffer
    (write-file winny/initialized-cookie-path))
  (message "First boot-only initialization complete."))
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)

use-package-always-ensure

Set use-package-always-ensure to t… if one can figure out how to load stuff from site-lisp and site-packages with this setting.

Detect WSL

Load the locally maintained is-wsl package which is used later in this file.

(use-package is-wsl)

Better frame title

Give the desktop window title a nicer look.

(setq-default frame-title-format '("Emacs "
                                   (:eval (if buffer-file-name
                                            (replace-regexp-in-string (regexp-quote (or (getenv "HOME") "")) "~" buffer-file-name)
                                            (buffer-name)))
                                   " [%m] { "
                                   (:eval (string-join (mapcar #'(lambda (w) (buffer-name (window-buffer w))) (window-list)) ", "))
                                   " }"))

Backup files

This section is inspired by https://github.com/yanghaoxie/emacs.d#setting-related-in-build-in-features

Store backup files in ~/.emacs.d/var/backup/.

(defvar winny/backup-directory
  (concat (file-name-as-directory user-emacs-directory) "var/backup"))
(unless (file-exists-p winny/backup-directory)
  (make-directory winny/backup-directory t))
(setq backup-directory-alist
      `(("." . ,winny/backup-directory)))

Store auto-save files in ~/.emacs.d/var/auto-save/

(defvar winny/emacs-autosave-directory (concat (file-name-as-directory user-emacs-directory) "var/auto-save/"))
(unless (file-exists-p winny/emacs-autosave-directory)
  (make-directory winny/emacs-autosave-directory t))
(setq auto-save-file-name-transforms
      `((".*" ,winny/emacs-autosave-directory t)))

Initialization helpers

Toggle showing matching parenthesis for the current buffer only

(defun show-paren-local-mode (&optional arg)
  "Toggle visibility of matching parenthesis for the current buffer.
When ARG is positive or not a number, enable function
`show-paren-mode', else disable it."
  (interactive)
  (setq-local show-paren-mode
              (cond
               ((numberp arg) (> arg 0))
               ((not arg) (not show-paren-mode))
               (t t)))
  (when (called-interactively-p 'interactive)
    (message "show-paren-mode %s in current buffer." (if show-paren-mode "enabled" "disabled"))))

Easy way to add file extensions to a major mode

(defun add-to-auto-mode-alist (mm extension &rest extensions)
  "Add major mode MM for EXTENSION and EXTENSIONS to the `auto-mode-alist'.
EXTENSION may also be a list."
  (let ((ls (if (listp extension)
              (append extension extensions)
              (cons extension extensions))))
    (dolist (ext ls)
      (add-to-list 'auto-mode-alist (cons (concat "\\." ext "\\'") mm)))
    auto-mode-alist))

Language support

This section also includes file-format support, as file-formats can be thought of as languages :)

Racket

The core racket-mode.

(use-package racket-mode
  :ensure t
  :config
  (setq racket-show-functions 'racket-show-echo-area)
  :init
  (add-hook
   'racket-mode-hook
   (defun winny/racket-mode-hook ()
     (put 'bit-string-case 'racket-indent-function 'defun)
     ;; Defer to Emacs default value.  Note to self, if I modify this variable
     ;; I need to use (set-default-value ...)
     (setq-local comment-column (default-value 'comment-column))))
  (add-hook 'racket-mode-hook
            'racket-xp-mode))

And a locally installed scribble.el for scribble markup.

(use-package scribble)

Fennel

(use-package fennel-mode
  :ensure t
  :init
  (add-to-list 'auto-mode-alist '("\\.fnl\\'" . fennel-mode)))

Common Lisp

(use-package slime
  :ensure t
  :init
  (setq inferior-lisp-program "sbcl"))

LaTeX/auctex

I install this via my distro’s package manager, so no use-package here.

(add-hook 'TeX-mode-hook (lambda ()
                           (setq word-wrap t)))

Lua

(use-package lua-mode
  :ensure t
  :custom
  ((lua-indent-level 2)))

Python

I’ve given up on jedi. I am not a fan. It doesn’t work like you expect it to, and is very moody. TODO: look into setting up Python LSP.

(use-package python-mode
  :ensure t)

Ruby

The default ruby mode is not very nice. So use enh-ruby-mode.

(use-package enh-ruby-mode
  :ensure t
  :init
  ;; Not sure if any if this is needed.  So commenting it out.
  ;; (autoload 'enh-ruby-mode "enh-ruby-mode" "Major mode for ruby files" t)
  ;; (add-to-auto-mode-alist 'enh-ruby-mode "rb")
  ;; (add-to-list 'interpreter-mode-alist '("ruby" . enh-ruby-mode))
  )

C# (.Net support)

C# support

Syntax highlighting major mode.

(use-package csharp-mode
  :ensure t)

csproj support

Major mode for csproj and other msbuild project files.

(use-package csproj-mode
  :ensure t)

dotnet cli helper

This makes it possible to run some dotnet commands via emacs.

(use-package dotnet
  :ensure t
  :after csharp-mode
  :init
  (add-hook 'csharp-mode-hook 'dotnet-mode))

omnisharp (code completion, linting, intellisense)

This is the secret sauce for dotnet core support in emacs. It gives code completion, suggestions, errors, and so on. It is the same stuff that VSCode uses internally.

(use-package omnisharp
  :ensure t
  :after csharp-mode
  :after company
  :init
  (add-hook 'csharp-mode-hook 'omnisharp-mode)
  (add-to-list 'company-backends 'company-omnisharp))

dotnet core

Mark the dotnet core .DotSettings files as xml.

(add-to-list 'auto-mode-alist '("\\.DotSettings\\'" . xml-mode))

Powershell

(use-package powershell
  :ensure t
  :hook (powershell-mode
         .
         (lambda ()
           ;; No don't override a standard emacs key, really what were they thinking?
           (local-unset-key (kbd "M-`"))
           ;; TODO: bind `powershell-escape-selection' to something else...
           )))

Web stuff

Coffee script

(use-package coffee-mode
  :ensure t
  :init
  (setq coffee-tab-width 2))

HTML/template support

web-mode is pretty great. It supports all the cool template types.

(use-package web-mode
  :ensure t
  :config
  ;; web-mode
  (add-to-auto-mode-alist 'web-mode "php" "phtml" "tpl" "[agj]sp" "as[cp]x"
                          "erb" "mustache" "d?html")
  (defadvice web-mode-highlight-part (around tweak-jsx activate)
    (if (equal web-mode-content-type "jsx")
      (let ((web-mode-enable-part-face nil))
        ad-do-it)
      ad-do-it))
  (setq web-mode-auto-close-style 2
        web-mode-enable-auto-closing t
        web-mode-code-indent-offset 2
        web-mode-css-indent-offset 2
        web-mode-markup-indent-offset 2
        )
;; (add-hook 'web-mode-hook (lambda ()
;;                            (setq web-mode-markup-indent-offset 2)
;;                            (setq web-mode-css-indent-offset 2)
;;                            (setq web-mode-code-indent-offset 2)))
  )

TSX/JSX

(straight-use-package '(tsi :type git :host github :repo "orzechowskid/tsi.el"))

(straight-use-package '(tsx-mode :type git :host github :repo "orzechowskid/tsx-mode.el" :branch "emacs28"))

Svelte

A pretty cool framework for modern component web applications.

(use-package svelte-mode
  :ensure t)

Jade HTML templates

Maybe I should remove this. Haven’t used a Jade template for a long time.

(use-package jade-mode
  :ensure t)

Javascript

(setq js-indent-level 2)

Typescrypt

(use-package typescript-mode
  :ensure t)

(use-package tide
  :ensure t
  :after typescript-mode
  :after flycheck
  :after company
  :after editorconfig
  :init
  (defun setup-tide-mode ()
    (interactive)
    (tide-setup)
    (flycheck-mode +1)
    (setq flycheck-check-syntax-automatically '(save mode-enabled))
    (eldoc-mode +1)
    (tide-hl-identifier-mode +1)
    ;; company is an optional dependency. You have to
    ;; install it separately via package-install
    ;; `M-x package-install [ret] company`
    (company-mode +1))
  ;; formats the buffer before saving
  (add-hook 'before-save-hook 'tide-format-before-save)

  (add-hook 'typescript-mode-hook #'setup-tide-mode)
  (add-hook 'editorconfig-after-apply-functions (defun winny/fix-tide-indentation (props)
                                                  (when (and (boundp 'tide-mode) tide-mode)
                                                    (tide-command:configure)))))

Prisma (DSL for schemas)

(use-package prisma-mode
  :after (lsp)
  :load-path "~/.emacs.d/site-packages/emacs-prisma-mode")

Scala

scalpp was a file extension I used for cpp prerocessed code. cool was a file extension for a compilers course I took. It was a subset of Scala, so I used this major mode. coop is cpp preprocessed code.

(use-package scala-mode
  :ensure t
  :mode "\\.coo[lp]\\'"
  :mode "\\.scalpp\\'")

Golang

It turns out golang mode is not strict about indentation despite the toolchain being pretty strict about that sort of thing. So that’s what the hook does.

(use-package go-mode
  :ensure t
  :after (lsp-mode)
  :init
  (add-hook 'go-mode-hook #'lsp-deferred)
  (add-hook 'go-mode-hook
            (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
            (defun winny/go-hook ()
              "My Go hook"
              (setq-local tab-width 4))))

Haskell

(use-package haskell-mode
  :ensure t
  :init
  (setq haskell-interactive-popup-errors nil))

Ocaml

(use-package tuareg
  :ensure t)

Zig

(use-package zig-mode
  :ensure t)

LSP

(use-package lsp-mode
  :ensure t
  :bind-keymap (("C-'" . lsp-command-map)))

Packaging language modes

nix

The nix package language and configuration language.

(use-package nix-mode
  :ensure t)

PKGBUILD

The bash-based packaging format used for archlinux.

(use-package pkgbuild-mode
  :ensure t)

ebuild

(This is installed via the package manager.)

eix app-emacs/ebuild-mode

Graphviz .dot files

See https://www.graphviz.org/doc/info/lang.html

(use-package graphviz-dot-mode
  :ensure t)

Java ecosystem

Kotlin

A Java replacement by Google.

(use-package kotlin-mode
  :ensure t)

Gradle (build tool)

See also groovy-mode for syntax highlighting.

(use-package gradle-mode
  :ensure t)

Groovy

(And Gradle syntax highlighting)

(use-package groovy-mode
  :ensure t)

Java LSP

(use-package lsp-java
  :after (lsp)
  :ensure t
  :init (add-hook 'java-mode-hook 'lsp))

Ledger

For plain text accounting.

(use-package ledger-mode
  :ensure t
  :after company-mode
  :hook
  ((ledger-mode-hook
    .
    (lambda ()
      (company-mode 1)))))

CSV

Always useful to have better CSV tooling.

(use-package csv-mode
  :ensure t
  :mode "\\.[Cc][Ss][Vv]\\'")

Rust

Nice and simple. Just install rust-mode.

(use-package rust-mode
  :ensure t)

Swift

(use-package swift-mode
  :ensure t)

JSON

While one could use javascript-mode, json-mode restricts the syntax to just the JSON stuff.

(use-package json-mode
  :ensure t)

YAML

Yet another silly markup language.

(use-package yaml-mode
  :ensure t)

XML extensions

Format XML documents. Not perfect as it depends an xmllint and that tends to clean up dirty XML documents (e.g. add DTDs).

(fset 'winny/xml-format
     (kmacro-lambda-form [?\C-x ?h ?\C-u ?\M-| ?x ?m ?l ?l ?i ?n ?t ?  ?- ?- ?f ?o ?r ?m ?a ?t ?  ?- return] 0 "%d"))

Add some other known extensions to xml-mode.

(add-to-list 'auto-mode-alist '("\\.xsd\\'" . xml-mode)) ; XML Schema Definition
(add-to-list 'auto-mode-alist '("\\.wsdl\\'" . xml-mode)) ; Web Services Description Language
(add-to-list 'auto-mode-alist '("\\.jca\\'" . xml-mode)) ; Java Connector Architecture Adapter files

TOML

Tom’s obvious minimal language.

(use-package toml-mode
  :ensure t)

Sed

For sed(1) scripts.

(use-package sed-mode
  :ensure t)

ssh configuration files

This adds syntax highlighting for ssh_config, sshd_config, known_hosts, and authorized_keys.

(use-package ssh-config-mode
  :ensure t)

.gitignore

Also adds major modes for git attributes and git config files. gitignore-mode Helps with making sure globs make sense.

(use-package git-modes
  :ensure t)

Markdown

The markdown markup language.

(use-package edit-indirect
  :ensure t
  :init
  (require 'edit-indirect)
  (define-key edit-indirect-mode-map (kbd "C-c C-'") 'edit-indirect-commit))

(use-package markdown-mode
  :after (edit-indirect)
  :ensure t
  :init
  (require 'markdown-mode)
  (setq markdown-asymmetric-header t)
  (define-key markdown-mode-map (kbd "C-c C-'") 'markdown-edit-code-block))

Unison profiles

The unison synchronization tool has a somewhat weird syntax, so I wrote a major mode to highlight it more accurately.

(use-package unison)

C mode

All C-like

I can’t remember what this does.

(add-hook 'c-mode-common-hook
          (lambda ()
            (c-set-offset 'substatement-open 0)
            (if (assoc 'inexpr-class c-offsets-alist)
              (c-set-offset 'inexpr-class 0))))

C language specifically

Set default style and use tabs in C files by default.

(add-hook 'c-mode-hook (lambda ()
                         (setq indent-tabs-mode t)
                         (c-set-style "bsd")))

Meson

(use-package meson-mode
  :ensure t)

Qlik

(add-to-auto-mode-alist 'sql-mode "qvs")

Perl

perldoc support

(use-package helm
  :ensure t)

(use-package helm-perldoc
  :ensure t
  :after (helm)
  :init
  (helm-perldoc:setup))

Erlang

(use-package erlang
  :ensure t
  :init
  (require 'erlang-start))

Dockerfile

(use-package dockerfile-mode
  :ensure t)

SQL

(use-package sql-indent
  :ensure t
  :hook ((sql-mode . sqlind-minor-mode)))

CMake

(use-package cmake-mode
  :ensure t)

Bison (Lex/Yacc)

(use-package bison-mode
  :ensure t)

BASIC

(use-package basic-mode
  :ensure t
  :init
  (setq basic-auto-number 20
        basic-line-number-cols 4))

Clojure

(use-package clojure-mode
  :ensure t)

Linux Firewall (nft)

(use-package nftables-mode
  :ensure t)

Hex editing

(use-package nhexl-mode
  :ensure t
  :init
  ;; defalias needs symbols: it's a function, not a macro.
  (defalias 'hex-edit 'nhexl-mode)
  (add-hook 'nhexl-mode-hook #'(lambda ()
                                 (setq-local so-long-action 'longlines-mode))))

Whitespace

Show trailing whitespace on some major modes by default.

(mapc (lambda (m) (add-hook (intern (concat (symbol-name m) "-mode-hook"))
                            (defun whitespace-hook ()
                              "Hook to make trailing whitespace visible."
                              (setq-local show-trailing-whitespace t))))
      '(c csv c++ python ruby enh-ruby js lisp web racket org TeX haskell makefile))

Add command and bind key to toggle trailing whitespace

(defun show-trailing-whitespace (n)
  "Toggle the highlight of trailing whitespace for the current buffer.

  When N is nil, toggle the highlight setting.
  When N is non-negative, enable the highlight setting.
  When N is negative, disable the highlight setting."
  (interactive "P")
  (setq-local show-trailing-whitespace
              (cond
               ((eq n nil) (not show-trailing-whitespace))
               ((< n 0) nil)
               (t t)))
  (force-window-update)
  (message (if show-trailing-whitespace
             "Showing trailing whitespace."
             "Hiding trailing whitespace.")))

(global-set-key (kbd "C-x M-w") 'show-trailing-whitespace)

File manager stuff

dired

Automatically update directory listings

Except on Windows where Disk IO seems to be prohibitively slow. Could just be work Anti Virus ¯\_(ツ)_/¯. On Windows, typing g in a dired buffer causes an excessively long delay (tens of seconds) in a directory with 4000 entries.

(unless (or (member system-type '(ms-dos windows-nt cygwin)) is-wsl)
  (add-hook 'dired-mode-hook 'auto-revert-mode))

Add C-c n to create an empty file

(eval-after-load 'dired
  '(progn
     (define-key dired-mode-map (kbd "C-c n") 'dired-create-file)
     (defun dired-create-file (file)
       "Create a file called FILE.
If FILE already exists, signal an error."
       (interactive
        (list (read-file-name "Create file: " (dired-current-directory))))
       (let* ((expanded (expand-file-name file))
              (try expanded)
              (dir (directory-file-name (file-name-directory expanded)))
              new)
         (if (file-exists-p expanded)
           (error "Cannot create file %s: file exists" expanded))
         ;; Find the topmost nonexistent parent dir (variable `new')
         (while (and try (not (file-exists-p try)) (not (equal new try)))
           (setq new try
                 try (directory-file-name (file-name-directory try))))
         (when (not (file-exists-p dir))
           (make-directory dir t))
         (write-region "" nil expanded t)
         (when new
           (dired-add-file new)
           (dired-move-to-filename))))))

dired+

A better dired.

(use-package dired+
  :disabled)

sunrise commander

A OFM (like midnight commander) for emacs.

;; (use-package sunrise
;;   :disabled
;;   :load-path "~/.emacs.d/site-packages/sunrise-commander")

neotree side pane

This is a handy side pane with a navigable tree of folders and files. This also configures neotree to sort by file extension.

(defun string</extension (x y)
  "Using the file extension, indicate if X is less than Y."
  (let ((x-ext (f-ext x))
        (y-ext (f-ext y)))
    (cond
     ((string= x-ext y-ext) (string< x y))
     ((not x-ext) t)
     ((not y-ext) nil)
     (t (string< x-ext y-ext)))))
(use-package neotree
  :ensure t
  :bind (([f8] . neotree-toggle))
  :bind (:map neotree-mode-map
              ("^" . neotree-select-up-node)
              ("v" . neotree-select-down-node))
  :config (setq neo-filepath-sort-function 'string</extension))

Feature reloading

This should be moved to its own emacs lisp file. winny/reload-major-mode attempts to reload a major mode. This helps when making certain kinds of changes to el files. No need to restart emacs. Or partially re-evaluate, only to realize it didn’t work as you expected.

(defun winny/reload-feature (feature &optional force) ; Why the HECK is this
                                                      ; not standard?
  "Reload FEATURE optionally FORCE the `unload-feature' call."
  (interactive
   (list
    (read-feature "Unload feature: " t)
    current-prefix-arg))
  (let ((f (feature-file feature)))
    (unload-feature feature force)
    (load f)))

(require 'loadhist)                     ; For `file-provides'
(defun winny/reload-major-mode ()
  "Reload the current major mode.

TODO: This should be generalized to any feature, and will
re-enable any minor or major modes present in the feature's
file."
  (interactive)
  (letrec ((mode major-mode)
           (f (cdr (find-function-library mode)))
           (buffers (loop for b in (buffer-list)
                          when (eq (buffer-local-value 'major-mode b) mode)
                          collect b)))
    (loop for feature in (file-provides f)
          do (unload-feature feature t))
    (load f)
    (loop for b in buffers
          do (with-current-buffer b
               (funcall mode)))))

custom-mode helpers

Add the following keys to help with navigating custom-mode:

KeyCommandDescription
^Custom-goto-parentGo to parent node.
M-nwinny/forward-child-widgetGo to next configurable option.
M-pwinny/backward-child-widgetGo to previous configurable option.
M-RETCustom-newlineLazy bind so one doesn’t have to release meta key when wishing to expand/contract a widget.

The ^ aligns with dired’s usage of ^ to go up one directory.

(require 'cus-edit)
(defconst winny/child-widget-regex "^\\(Hide\\|Show Value\\|Show\\)")
(defun winny/forward-child-widget (&optional arg)
  "Navigate to next child widget by ARG.
Use a Negative ARG to navigate backwards."
  (interactive "p")
  (when (and (looking-at winny/child-widget-regex) (> arg 0))
    (setq arg (+ 1 arg)))
  (condition-case nil
      (progn
        (re-search-forward winny/child-widget-regex nil nil arg)
        ;; Ensure point is at the beginning of the line.
        (move-beginning-of-line nil))
    (error (ding))))
(defun winny/backward-child-widget (&optional arg)
  "Navigate to previous child widget by ARG.
Use a Negative ARG to navigate forwards."
  (interactive "p")
  (winny/forward-child-widget (- arg)))

(define-key custom-mode-map "^" 'Custom-goto-parent)
(define-key custom-mode-map (kbd "M-n") 'winny/forward-child-widget)
(define-key custom-mode-map (kbd "M-p") 'winny/backward-child-widget)
(define-key custom-mode-map (kbd "M-RET") 'Custom-newline)

Theme-ing

The themes

cyberpunk

My goto theme.

(use-package cyberpunk-theme
  :ensure t)

tao

(use-package tao-theme
  :ensure t)

Others for the moods

(use-package uwu-theme
  :ensure t)

(use-package abyss-theme
  :ensure t)

Modeline

(use-package nerd-icons
  :ensure t)
(use-package doom-modeline
  :ensure t
  :after (nerd-icons)
  :init (doom-modeline-mode 1))

A facility to streamline theme selection

(load "switch-theme.el" t t)
(setq winny/default-theme 'cyberpunk)
(use-package smart-mode-line
  :ensure t
  :disabled
  :init
  (require smart-mode-line)
  (setq sml/col-number-format "%3c"
        sml/line-number-format "%4l"
        sml/mode-width 'right
        sml/numbers-separator ","
        sml/replacer-regexp-list '(("^~/\\.emacs\\.d/elpa/" ":ELPA:")
                                   ("^~/\\.emacs\\.d/" ":ED:"))
        sml/theme 'respectful)
  (add-hook 'winny/after-theme-switch-hook 'sml/setup t t))

Helper to describe theme

(defun describe-current-theme ()
  "Describe the current theme, ignoring smart-mode-line themes."
  (interactive)
  (describe-theme
   (car
    (cl-remove-if (lambda (x)
                    (string-prefix-p "smart-mode-line" (symbol-name x)))
                  custom-enabled-themes))))

Emacs Performance and debugging

Profiler

Bind the emacs profiler to some keys under the C-x M-p map.

(require 'profiler)
(global-set-key (kbd "C-x M-p s") 'profiler-start)
(global-set-key (kbd "C-x M-p q") 'profiler-stop)
(global-set-key (kbd "C-x M-p r") 'profiler-report)

ESUP - Emacs Start Up Profiler

(use-package esup
  :ensure t
  ;; To use MELPA Stable use ":pin mepla-stable",
  :pin melpa
  :commands (esup))

Debug on error or quit

Function toggle-debug-on-error is always available, but if there is an error that prevents M-x toggle-debug-on-error RET from completing, you won’t be able to enable this functionality, thereby be unable to get an error trace (sad). The work around is to make a helper function, then bind it to a key on the global keymap. In this case C-x \ will toggle debug on error. C-u C-x \ will toggle debug on quit.

(defun winny/toggle-debug-on-error-or-quit (&optional on-quit)
  "Toggle debug on error, or quit with non-nil prefix argument.
When ON-QUIT is non-nil toggle debug on quit instead."
  (interactive "P")
  (if on-quit
    (toggle-debug-on-quit)
    (toggle-debug-on-error)))

(global-set-key (kbd "C-x \\") 'winny/toggle-debug-on-error-or-quit)

org-mode

Ye ole fabulous productivity tool.

Note to self about blocks

In recent org-mode <sTAB no longer works. One can restore this functionality using (require 'org-tempo) — this reimplements the old behavior. On the other hand the new behavior using C-c C-, s is much cleaner, allowing the user to dispatch to any known block type from a menu. It is one extra keystroke, but I think I’ll live.

Package setup

See:

  • org#Headlines
  • org#Catching invisible edits
(use-package org
  :pin gnu
  :ensure t
  :init
  (setq org-special-ctrl-a t
        org-special-ctrl-k t
        org-special-ctrl-k 'error
        org-catch-invisible-edits t
        org-startup-folded 'fold
        ;; https://emacs.stackexchange.com/questions/64222/insert-link-to-a-heading-with-id
        org-id-link-to-org-use-id 'create-if-interactive
        user-full-name "Winston Weinert (winny)"
        user-mail-address "hello@winny.tech"
        org-capture-templates
        '(("a" "Anything" entry
           (file+datetree "~/files/notes/unsorted.org")
           (file "~/.emacs.d/org-capture-templates/unsorted.org"))
          ("j" "Journal Entry" entry
           (file+datetree "~/files/writings/journal/journal.org")
           (file "~/.emacs.d/org-capture-templates/journal-item.org"))
          ("t" "Todo list item" entry
           (file+headline "~/files/notes/todo.org" "Inbox")
           (file "~/.emacs.d/org-capture-templates/todo-item.org")))
        org-default-notes-file "~/files/notes/unsorted.org"))
(use-package org-contrib
  :ensure t
  :after org)

org-cliplink

C-c C-y is bound to something that I’ve never used related to timekeeping.

(use-package org-cliplink
  :ensure t
  :after org
  :init
  (define-key org-mode-map (kbd "C-c C-y") 'org-cliplink))

Main hook

(add-hook 'org-mode-hook (defun winny/org-hook ()
                           (setq word-wrap t)
                           (turn-on-auto-fill)
                           (org-indent-mode 1)))

Global org-mode keys

(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cc" 'org-capture)
(global-set-key "\C-cb" 'org-switchb)

Org-mode specific keys

(define-key org-mode-map (kbd "M-n") 'org-next-visible-heading)
(define-key org-mode-map (kbd "M-p") 'org-previous-visible-heading)

(define-key org-mode-map (kbd "<C-M-return>")
  (defun winny/org-goto-content ()
    "Go to content for heading or create a newline for content."
    (interactive)
    (org-end-of-meta-data)
    (org-show-hidden-entry)
    (when (org-at-heading-p)
      (open-line 1))))

;; Make it easier to enter/leave org block editing without lifting the Control
;; key.
(define-key org-mode-map (kbd "C-c C-'") 'org-edit-special)
(define-key org-src-mode-map (kbd "C-c C-'") 'org-edit-src-exit)

(define-key org-mode-map (kbd "C-M-u") 'org-up-element)

Insert created timestamp

(defvar winny/org-auto-insert-expiry-pattern-list '()
  "A list of regexes like the first element in `auto-mode-alist'
cons cells.")
(defun winny/org-insert-created ()
  "Insert created expiry information.
Only insert when the variable the target filing file name matches
a regex in `winny/org-auto-insert-expiry-pattern-list'."
  (when (let* ((base-buffer-file-name
                (buffer-file-name (buffer-base-buffer (current-buffer))))
               (case-fold-search
                (file-name-case-insensitive-p base-buffer-file-name)))
          (assoc-default base-buffer-file-name
                         (mapcar #'(lambda (el) (cons el t))
                                 winny/org-auto-insert-expiry-pattern-list)
                         'string-match))
    (save-excursion
      (org-back-to-heading)
      (org-expiry-insert-created))))
(add-hook 'org-capture-before-finalize-hook 'winny/org-insert-created)
(add-hook 'org-insert-heading-hook 'winny/org-insert-created)

Some helper functions/macros for org stuff

convert a table to a definition list

(defun winny/org-table-line-to-definition-list (&optional arg)
  "Keyboard macro."
  (interactive "p")
  (kmacro-exec-ring-item (quote ([4 45 19 124 return 2 2 134217760 4 58 58 5 2 134217760 4 backspace return 11] 0 "%d")) arg))

Silly helper to increment footnotes

(defun winny/increment-footnotes (count)
  "Increment all footnote numbers in buffer by `COUNT'."
  (interactive "p")
  (unless count
    (setq count 1))
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "\\[fn:\\([0-9]+\\)\\]" nil t)
      (message "m")
      (replace-match (number-to-string (+ count (string-to-number (match-string 1))))
                     nil nil nil 1))))

idk what this does but it was in my init.el

(defun afs/org-replace-link-by-link-description ()
  "Replace an org link by its description or if empty its address."
  (interactive)
  (if (org-in-regexp org-bracket-link-regexp 1)
    (save-excursion
      (let ((remove (list (match-beginning 0) (match-end 0)))
            (description (if (match-end 3)
                           (org-match-string-no-properties 3)
                           (org-match-string-no-properties 1))))
        (apply 'delete-region remove)
        (insert description)))))

Export stuff

ox-latex

(require 'ox-latex)
(add-to-list 'org-latex-classes
             '("beamer"
               "\\documentclass\[presentation\]\{beamer\}"
               ("\\section\{%s\}" . "\\section*\{%s\}")
               ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
               ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}")))

ox-twbs

Pretty bootstrap based HTML export.

(use-package ox-twbs
  :ensure t
  :after ox)

ox-hugo

Export to hugo markdown. Great for blogging.

(use-package ox-hugo
  :ensure t
  :after ox)

Helper commands to get productive

(defun winny/new-blog-post ()
  (interactive)
  (find-file "~/pro/winny.tech/blog.winny.tech/content-org/all-posts.org")
  (goto-char (point-max))
  (org-previous-visible-heading 1)
  (org-meta-return))

Modern org

Cleaner org-mode.

(use-package org-modern
  :ensure t
  :init
  (with-eval-after-load 'org (global-org-modern-mode)))

Babel (code evaluation)

Async execution

(use-package ob-async
  :ensure t
  :after org
  :init
  (require 'ob-async))

Bash support

(org-babel-do-load-languages 'org-babel-load-languages '((shell . t)))

Code folding

Use M-g f to fold the region. Use M-g d to delete the fold under point. Use M-g t to toggle the fold at point.

(use-package vimish-fold
  :ensure t
  :after expand-region
  :init
  (defun winny/vimish-fold-defun ()
    "Fold the defun around point."
    (interactive)
    (lexical-let ((r (save-excursion (er/mark-defun) (list (region-beginning) (region-end)))))
      (vimish-fold (car r) (cadr r))))
  (defun winny/vimish-fold-delete (entire-buffer)
    "Fold region or entire buffer when ENTIRE-BUFFER is not nil."
    (interactive "P")
    (if entire-buffer
      (vimish-fold-delete-all)
      (vimish-fold-delete)))
  (global-set-key (kbd "M-g f") #'vimish-fold)
  (global-set-key (kbd "M-g M-f") #'vimish-fold)
  (global-set-key (kbd "M-g u") #'vimish-fold-unfold)
  (global-set-key (kbd "M-g M-u") #'vimish-fold-unfold)
  (global-set-key (kbd "M-g t") #'vimish-fold-toggle)
  (global-set-key (kbd "M-g M-t") #'vimish-fold-toggle)
  (global-set-key (kbd "M-g d") #'vimish-fold-delete)
  (global-set-key (kbd "M-g M-d") #'vimish-fold-delete))

VCS/Git support

When following a symlink into a git repo, display a warning, but don’t prompt if it is okay.

(setq vc-follow-symlinks nil)

Magit

The best way to use git. As long as you know C-x g to open the magit menu, you are good to go.

(use-package magit
  :ensure t
  :bind (("C-x g" . magit-status)
         ("C-x M-g" . magit-dispatch)
         ("C-x M-c" . magit-clone)
         :map magit-revision-mode-map
         ("C-c u" . magit-rev-parent))
  :init
  (fset 'magit-rev-parent
   (kmacro-lambda-form [?\M-< ?\C-s ?p ?a ?r ?e ?n ?t ?: return return] 0 "%d")))

Git LFS

(use-package magit-lfs
  :ensure t
  :after magit)

Forge

Work with github and gitlab efficiently.

As of 2022-07-13 there’s an issue with my forge setup, so disable temporarily until a workaround can be determined.

(use-package forge
  :disabled
  :ensure t)

Documentation/help browsers

info

Sometimes I put texinfo files into ~/docs/info. Most distros do not package mysql’s texinfo, for example. It sure beats firing up a web browser!

(add-to-list 'Info-directory-list "~/docs/info" t)

Add a key to easily copy the current info node name. This can be used to share with others how to find docuemantion.

(bind-key "y" #'Info-copy-current-node-name Info-mode-map)

RFC reader (irfc)

In this repository.

(use-package irfc
  :hook (irfc-mode
         . (lambda ()
             (read-only-mode)         ; Make read only.
             (show-paren-local-mode -1))))

Better describe-*

The helpful package takes over C-h v, C-h k, C-h f providing more descriptive output and nicer formatting.

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

Show keys in the current mode-map

Say you start typing C-x. After a brief delay this mode will show all available keys at the bottom of the screen. This can help with forgetting keyboard shortcuts, as one tends to do with octopus-hand tools like Emacs.

(use-package which-key
  :ensure t
  :init
  (which-key-mode 1)
  :config
  ;; Address issue with tao-yin.  This is a hack.
  (set-face-attribute 'which-key-command-description-face nil :inherit nil))

Show unbound keyboard shortcuts

No more guessing if a key is available. This will show a list of all keys available in a given mode map. Use C-h Y.

(use-package free-keys
  :ensure t
  :bind (("C-h Y" . free-keys)))

Describe a face

(defun what-face (pos)
  "Describe the face under point.

Prefix argument POS should be a location it the buffer."
  (interactive "d")
  (let ((face (or (get-char-property (pos) 'read-face-name)
                  (get-char-property (pos) 'face))))
    (if face (message "Face: %s" face) (message "No face at %d" pos))))

Describe a theme

See here.

Direnv

(use-package direnv
  :ensure t
  :config
  (direnv-mode))

Pull in ssh-agent via keychain

See https://www.funtoo.org/Keychain

(use-package keychain-environment
  :ensure t
  :init
  (keychain-refresh-environment))

Completion

Vertico

(use-package vertico
  :ensure t
  :init
  (setq read-file-name-completion-ignore-case t
        read-buffer-completion-ignore-case t
        completion-ignore-case t)
  (vertico-mode))

(use-package savehist
  :init
  (savehist-mode))

;; Enable rich annotations using the Marginalia package
(use-package marginalia
  :ensure t
  ;; Either bind `marginalia-cycle' globally or only in the minibuffer
  :bind (("M-A" . marginalia-cycle)
         :map minibuffer-local-map
         ("M-A" . marginalia-cycle))

  ;; The :init configuration is always executed (Not lazy!)
  :init

  ;; Must be in the :init section of use-package such that the mode gets
  ;; enabled right away. Note that this forces loading the package.
  (marginalia-mode))

(use-package all-the-icons-completion
  :ensure t
  :after (marginalia all-the-icons)
  :hook (marginalia-mode . all-the-icons-completion-marginalia-setup)
  :init
  (all-the-icons-completion-mode))

Ensure XDG_RUNTIME_DIR is set

(add-hook 'after-init-hook
          (defun winny/ensure-XDG_RUNTIME_DIR ()
            "Ensure XDG_RUNTIME_DIR is set.
Used by qutebrowser and other utilities."
            (let ((rd (getenv "XDG_RUNTIME_DIR")))
              (when (or (not rd) (string-empty-p rd))
                (setenv "XDG_RUNTIME_DIR" (format "/run/user/%d" (user-uid)))))))

eww

Create multiple eww buffers

This allows for C-u M-x eww RET to create a new buffer. This is from https://emacs.stackexchange.com/a/24477/9163 .

(defun modi/force-new-eww-buffer (orig-fun &rest args)
  "When prefix argument is used, a new eww buffer will be created,
regardless of whether the current buffer is in `eww-mode'."
  (if current-prefix-arg
    (with-temp-buffer
      (apply orig-fun args))
    (apply orig-fun args)))
(advice-add 'eww :around #'modi/force-new-eww-buffer)

It appears the above does not work :(. This is a convenient work around. Just use M-x eww-new RET

(defun eww-new ()
  (interactive)
  (let ((url (read-from-minibuffer "Enter URL or keywords: ")))
    (switch-to-buffer (generate-new-buffer "*eww*"))
    (eww-mode)
    (eww url)))

Better eww appearance

Using writeroom-mode, one can center the text in eww-mode, reduce the paragraph width, and increase line height.

(add-hook 'eww-mode-hook 'writeroom-mode)

Code searching

ripgrep

(use-package rg
  :ensure t
  :init
  ;; Move over the default rg search to `rg/files'.
  (rg-define-search rg/files :confirm prefix)
  ;; Don't prompt for file types.  Note: "all" will only search the files known
  ;; to ripgrep to be interesting.  This won't work if working with
  ;; non-standard file extensions.  Instead use "everything", which appears to
  ;; be what ripgrep does by default anyways.
  ;;
  ;; Created https://github.com/dajva/rg.el/issues/131 to memorialize this
  ;; surprising behavior.
  (rg-define-search rg :confirm prefix :files "everything"))

Ergonomic search key

Use f3 as an ergonomic search key.

(define-key global-map (kbd "<f3>") 'isearch-forward)
(define-key global-map (kbd "<S-f3>") 'isearch-backward)
(define-key isearch-mode-map (kbd "<f3>") 'isearch-repeat-forward)
(define-key isearch-mode-map (kbd "<S-f3>") 'isearch-repeat-backward)

Occur

Occcur is pretty cool, but not sure why n and p do not move the cursor down and up?

(define-key occur-mode-map (kbd "p") 'previous-line)
(define-key occur-mode-map (kbd "n") 'next-line)

Swiper

A rather nice incremental search.

(use-package swiper
  :ensure t
  :bind (("C-x M-s" . swiper)))

Flyspell/flycheck/etc

Flyspell

Check spelling of prose in writing modes.

(add-hook 'text-mode-hook 'flyspell-mode)

And make it less distracting because jeeeeeeeeeeeeeeeez!

(require 'flyspell)
(setq flyspell-persistent-highlight nil)

;; https://emacs.stackexchange.com/questions/450/intelligent-spell-checking-in-org-mode
(add-to-list 'ispell-skip-region-alist '(":\\(properties\\|LOGBOOK\\|PROPERTIES\\|LOGBOOK\\):" . ":END:"))
(add-to-list 'ispell-skip-region-alist '("#\\+\\(BEGIN_SRC\\|begin_src\\)" . "#\\+\\(END_SRC\\|end_src\\)"))

flycheck

Enable it globally.

(use-package flycheck
  :ensure t
  :init
  ;; Disable the Elisp checkdoc checker.  I'm not sure why this is enabled by
  ;; default as most elisp users write is ad-hoc and
  ;; undocumented... https://emacs.stackexchange.com/a/10854/9163
  (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))
  (global-flycheck-mode 1))

Code completion

company

(use-package company
  :ensure t
  :init
  (global-set-key (kbd "<C-tab>") 'company-complete)
  ;; Temporarily disable this hook until implications are understood.  Add the
  ;; line to host.el instead.
  ;; (add-hook 'after-init-hook 'global-company-mode)
  )

Navigation

Reverse cycle windows

C-x o goes to the next window. But what about going to the previous window? One can do C-u -1 C-x o but we can do better than that.

This adds C-x O to cycle backwards.

(defun other-window-reverse (offset &optional all-frames)
  "`other-window' but in reverse."
  (interactive "p")
  (other-window (- (if (numberp offset) offset 1)) all-frames))

(global-set-key (kbd "C-x O") 'other-window-reverse)

Ergonomic cycle key

(global-set-key (kbd "<f4>") 'other-window)
(global-set-key (kbd "S-<f4>") 'other-window)

Slightly adjusting the viewport

This scrolls the viewport up and down. It keeps the cursor at the same line except if the line the cursor is presently on scrolls off the screen. Then the cursor moves to the line closest to the previous line that is still on the screen. It is bound to M-N and M-P.

(defun scroll-up-1 ()
  "Scroll up by 1 line."
  (interactive)
  (scroll-up 1))
(defun scroll-down-1 ()
  "Scroll down by 1 line."
  (interactive)
  (scroll-down 1))

(global-set-key (kbd "M-N") 'scroll-up-1)
(global-set-key (kbd "M-P") 'scroll-down-1)

Move buffers between windows

(use-package buffer-move
  :ensure t
  :bind (("C-x w p" . buf-move-up)
         ("C-x w n" . buf-move-down)
         ("C-x w b" . buf-move-left)
         ("C-x w f" . buf-move-right)))

Recenter on page navigation

(defun traverse-page--recenter-top (&optional count)
  "Recenter top, ignoring COUNT."
  (when (get-buffer-window)
    (recenter-top-bottom 0)))

(advice-add 'forward-page :after #'traverse-page--recenter-top)
(advice-add 'backward-page :after #'traverse-page--recenter-top)

Easier to type keys for page navigation

(global-set-key (kbd "<C-M-next>") 'forward-page)
(global-set-key (kbd "<C-M-prior>") 'backward-page)

Winner

Navigate history of window/buffer/frame layout. Use C-c <left> to go to previous layout, and C-c <right> to go to next layout.

(winner-mode 1)

Speed up scrolling

This works by disabling font locking (syntax highlighting) when rendering is taking too long, then restores font locking when scrolling stops.

(use-package fast-scroll
  :ensure t
  :config
  ;; Keep `mode-line-format' the same. This addresses a problem with
  ;; disappearing winum mode-line indicies.
  (defun fast-scroll-default-mode-line ()
    mode-line-format)
  :init
  (fast-scroll-mode 1))

Speed up rendering of very long lines

New with Emacs 27.1. See M-x so-long-commentary RET.

(global-so-long-mode 1)

Alternate keys to traverse between delimited phrases

One can use C-M-B and C-M-f to go backward and forward between s-expressions, but sometimes that is a bit awkward. So add keys C-x ,= and =C-x . to do the same thing.

(global-set-key "\C-x," 'backward-sexp)
(global-set-key "\C-x." 'forward-sexp)

Globally enable/disable line numbers in prog-mode

This feature allows for the quick toggle of line numbers. I personally don’t find line numbers very handy, but they help pair programmers communicate which particular code fragment they are talking about.

Type C-x M-l to toggle line numbers.

(defun enable-line-numbers ()
  "Enable line numbers in prog-mode."
  (interactive)
  (cl-loop for buf in (buffer-list)
           collect (with-current-buffer buf
                     (when (derived-mode-p 'prog-mode)
                       (display-line-numbers-mode 1))))
  (add-hook 'prog-mode-hook 'winny--enable-line-numbers)
  (when (called-interactively-p 'interactive)
    (message "Line numbers ENABLED in prog-modes."))
  t)

(defun disable-line-numbers ()
  "Disable line numbers in prog-mode."
  (interactive)
  (cl-loop for buf in (buffer-list)
           collect (with-current-buffer buf
                     (when (derived-mode-p 'prog-mode)
                       (display-line-numbers-mode -1))))
  (remove-hook 'prog-mode-hook 'winny--enable-line-numbers)
  (when (called-interactively-p 'interactive)
    (message "Line numbers DISABLED in prog-modes."))
  nil)

(defun winny--enable-line-numbers ()
  "Internal hook function."
    (display-line-numbers-mode 1))

(defun toggle-line-numbers ()
  "Toggle visibility of line numbers in prog-mode."
  (interactive)
  (if (member 'winny--enable-line-numbers prog-mode-hook)
    (call-interactively 'disable-line-numbers)
    (call-interactively 'enable-line-numbers)))

(global-set-key (kbd "C-x M-l") 'toggle-line-numbers)

Ace Jump

Type C-c C-SPC or C-c SPC then type the character you wish to navigate to. Type the subsequent highlighted character when prompted. Viola!

(use-package ace-jump-mode
  :ensure t
  :config
  (define-key global-map (kbd "C-c SPC") 'ace-jump-mode)
  (define-key global-map (kbd "C-c C-SPC") 'ace-jump-mode))

Editing

Lisp editing

Edit s-expressions efficiently with Paredit

Paredit is the best.

(use-package paredit
  :ensure t
  :init
  (dolist (m '(emacs-lisp-mode-hook
               racket-mode-hook
               lisp-mode-hook
               scheme-mode-hook
               clojure-mode-hook))
    (add-hook m #'paredit-mode))
  (add-hook 'paredit-mode-hook
            (defun winny/add-paredit-keystrokes ()
              "Ensure custom keys are enabled in paredit."
              (bind-keys :map paredit-mode-map
                         ("{"   . paredit-open-curly)
                         ("}"   . paredit-close-curly))
              (unless terminal-frame
                (bind-keys :map paredit-mode-map
                           ("M-[" . paredit-wrap-square)
                           ("M-{" . paredit-wrap-curly))))))

Make parenthesis stand out less in lisp modes

(use-package paren-face
  :ensure t
  :config
  (setq paren-face-regexp (rx (any "()[]{}")))
  (add-to-list 'paren-face-modes 'racket-mode)
  (add-to-list 'paren-face-modes 'racket-reply-mode)
  (add-to-list 'paren-face-modes 'emacs-lisp-mode)
  (add-to-list 'paren-face-modes 'lisp-mode))

Tweak if to not indent weird in elisp

(put 'if 'lisp-indent-function 'defun)

Expand region

Use C-= to select things around the point such as words, balanced delimiters, paragraphs, functions, incrementally.

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

editorconfig support

Configure the editor via .editorconfig files.

(use-package editorconfig
  :ensure t
  :config
  (setq editorconfig-mode-lighter " EdC")
  :init
  (editorconfig-mode 1))

Electric parens

Insert matching parenthesis.

(electric-pair-mode 1)

Wider fill column

Use 79 chars in each line for filling.

(setq-default fill-column 79)

No tabs by default

(setq-default indent-tabs-mode nil)

Use closest indent column for margin comments

(setq-default comment-column 0)

Zap

One can use M-z to character. This will delete all text including the first occurrence of the prompted character. Sometimes this is not ideal, so one can use C-M-z to zap up to (but keep) the prompted character.

(global-set-key (kbd "C-M-z") 'zap-up-to-char)

Some helper macros/commands

(defun winny/maybe-query-replace-bad-comma (no-prompt)
  "Replace occurrences of , followed by a non-space.  if `NO-PROMPT' then do don't do a query replace."
  (interactive "P")
  (funcall
   (if no-prompt
     'replace-regexp
     'query-replace-regexp)
   ",\\(\\S \\)"
   ", \\1"))

Collapse a wrapped paragraph to a single line.

This unwraps a paragraph into one line.

(defun unfill-region (beg end)
  "Unfill the region, joining text paragraphs into a single
    logical line.  This is useful, e.g., for use with
    `visual-line-mode'."
  (interactive "*r")
  (let ((fill-column (point-max)))
    (fill-region beg end)))

Delete whitespace right of point

(defun winny/kill-whitespace-right ()
  "Kill whitespace to right of point."
  (interactive)
  (delete-region (point) (save-excursion (skip-chars-forward " \t") (point))))

Alternate function to mark functions

(defun winny/mark-defun ()
  (interactive)
  (mark-defun)
  (when (or (comment-only-p (region-beginning) (region-end))
            (looking-at-p "[[:space:]]*$"))
    (forward-line 1)))

Quell Shellcheck at point

Create a line similar to # shellcheck disable=SC1234 above the current line in order to calm down Shellcheck.

(fset 'winny/shellcheck-disable
   (kmacro-lambda-form [?\C-  ?\C-  ?\M-x ?f ?l ?y ?c ?h ?e ?c ?k ?- ?d ?i ?s ?p ?l ?a ?y ?- ?e ?r ?r ?o ?r ?- ?a ?t ?- ?p ?o ?i ?n ?t ?\C-m ?\C-h ?e ?\C-x ?o ?\M-> ?\C-p ?\C-s ?\[ ?\C-m ?\C-  ?\M-f ?\M-w ?q ?\C-a ?\C-o ?\C-i ?# ?  ?s ?h ?e ?l ?l ?c ?h ?e ?c ?k ?  ?d ?i ?s ?a ?b ?l ?e ?= ?\C-y ?\C-u ?\C-  ?\C-u ?\C- ] 0 "%d"))
(add-hook 'sh-mode-hook
          (defun winny--bind-shellcheck-disable ()
            (global-set-key (kbd "C-c ! k") 'winny/shellcheck-disable)))

Snippets

Using Yasnippets. See the documentation.

Yasnippets (core)

(use-package yasnippet
  :ensure t
  :hook
  (snippet-mode . (lambda ()
                    ;; Do not force a newline in snippets.
                    (setq-local require-final-newline nil)))
  :init
  (make-directory (concat user-emacs-directory "/snippets") :parents)
  (yas-global-mode 1))

Upstream Snippets

(use-package yasnippet-snippets
  :ensure t)

Kill line or region

Type C-w without a region (selection) to kill the current line. Found this in Mastering Emacs, a fantastic book that you should also read :).

(use-package whole-line-or-region
  :ensure t
  :init
  (whole-line-or-region-global-mode))

Open file to previous position

(save-place-mode 1)

RSS Feed Reader

(use-package elfeed
  :ensure t
  ;; :after writeroom-mode
  ;; :hook (elfeed-show-mode . (lambda ()
  ;;               (writeroom-mode 1)
  ;;               (setq-local shr-width (writeroom--calculate-width))))
  )

Manage RSS feeds in elfeed.org.

(use-package elfeed-org
  :ensure t
  :init
  (elfeed-org))

Transmission

(use-package transmission
  :disabled
  :init
  (defun winny/transmission-add-magnet-uri ()
    "Add a magnet URI"
    (interactive)
    (transmission-add (read-string "Magnet URI: ")))
  :bind (:map transmission-mode-map
              ("A" . winny/transmission-add-magnet-uri)))

Shebang improvements

Make shebanged files executable on save

(add-hook 'after-save-hook
          (defun winny/make-shebanged-file-executable ()
            "Make sure scripts with shebang are saved with expected permissions."
            (interactive)
            (when (and (save-excursion (goto-char (point-min)) (looking-at "#!"))
                       (not (file-executable-p buffer-file-name)))
              (message "Making `%s' executable..." buffer-file-name)
              (executable-chmod))))

Detect shebang change and change major mode

(use-package shebang-change
  :init
  ;;(winny/add-shebang-change-hooks)
  )

Mode line tweaks

(Besides smart-mode-line)

Show battery when a battery is present

(display-battery-mode
 ;; Show battery status only if the system can use a battery.
 (if (and (fboundp 'battery-status-function)
          (lexical-let ((ac-line-status
                         (alist-get ?L (funcall battery-status-function))))
            (and ac-line-status (not (equal "N/A" ac-line-status)))))
   1
   -1))

Bell

(use-package mode-line-bell
  :ensure t
  :init
  (mode-line-bell-mode 1))

(setq visible-bell t)

Buffer management

Revert all buffers

(defun revert-all-buffers ()
  "Refreshes all open buffers from their respective files."
  (interactive)
  (dolist (buffer (buffer-list) (message "Refreshed open files"))
    (let ((fn (buffer-file-name buffer)))
      (when (and fn (not (buffer-modified-p buffer)))
        (if (file-exists-p fn)
          (progn
            (set-buffer buffer)
            (revert-buffer t t t))
          (message "Backing file `%s' no longer exists! Skipping." fn))))))

Kill buffers missing their files

(defun kill-all-missing-buffers (no-ask)
  "Kill all buffers with missing files.

When prefix argument NO-ASK is non-nil, do not ask before killing
each buffer"
  (interactive "P")
  (dolist (buffer (buffer-list))
    (let ((fn (buffer-file-name buffer)))
      (when (and fn (not (file-exists-p fn)))
        (if no-ask
          (kill-buffer buffer)
          (kill-buffer-ask buffer))))))

Copy the buffer filename

(defun yank-file-name (choice)
  "Copy the the buffer path to the `kill-ring'.
CHOICE can be `?f', `?d', or `?n' for full path, directory path,
or filename respectively.  Via
https://stackoverflow.com/a/18814469/2720026"
  (interactive "cCopy Buffer Name (F) Full, (D) Directory, (N) Name, (P) Project Path")
  (let* ((name (if (eq major-mode 'dired-mode)
                 (dired-get-filename)
                 (buffer-file-name)))
         (s
          (cl-case choice
            (?f name)
            (?d (file-name-directory name))
            (?n (file-name-nondirectory name))
            (?p (replace-regexp-in-string (regexp-quote (projectile-project-root)) "" name)))))
    (cond
     (s
      (message "%s copied" s)
      (kill-new s))
     (t
      (message "(No name to copy.)")))))

Show buffer filename is minibuffer

(defun show-file-name ()
  "Show the full path file name in the minibuffer."
  (interactive)
  (message (buffer-file-name)))

Use ibuffer

(defalias 'list-buffers 'ibuffer)

Highlight text

Better highlight. Don’t believe I use this?

(use-package highlight
  :ensure t)

Highlight Todo’s and XXX.

(use-package hl-todo
  :ensure t
  :init
  (global-hl-todo-mode 1))

Highlight color codes.

(use-package rainbow-mode
  :ensure t)

Dashboard

Show a nice screen when emacs starts up or creates a new fram.

(use-package dashboard
  :ensure t
  :bind (:map dashboard-mode-map
              ("p" . dashboard-previous-line)
              ("n" . dashboard-next-line))
  :init
  (setq ;;initial-buffer-choice (lambda () (get-buffer "*dashboard*"))
        dashboard-items '((projects . 5)
                          (recents . 5)
                          (bookmarks . 5))
        dashboard-item-shortcuts '((projects . "j")
                                   (recents . "r")
                                   (bookmarks . "m")
                                   (agenda . "a")
                                   (registers . "e"))
        dashboard-image-banner-max-height 50
        dashboard-image-banner-max-width 50)

  ;; Add the hook to startup, but... See second line.
  (dashboard-setup-startup-hook)
  ;; Ensure scratch is hidden
  (add-hook 'emacs-startup-hook 'delete-other-windows)

  (defun dashboard ()
    "Switch to or create the dashboard. "
    (interactive)
    (let ((buffer "*dashboard*"))
      (when (not (get-buffer buffer))
        (dashboard-insert-startupify-lists))
      (switch-to-buffer buffer))))

General keybinds

Browse kill ring

(use-package browse-kill-ring
  :ensure t
  :bind (("C-x y" . browse-kill-ring)))

Compile shortcut

(global-set-key (kbd "C-x c") 'compile)

Disable C-z when in windowed mode

I prefer the window manager to handle this, and it only feels familiar in console, where C-z does exactly what it should. It shouldn’t minimize windows, it’s not the same thing.

(when window-system
  (global-unset-key (kbd "C-z")))

View register

Pretty nice to see what’s in the registers. Bind it to C-x r v.

(global-set-key (kbd "C-x r v") 'view-register)

Backup key for M-x (C-x M-x)

Just in case the M-x replacement de-jure messes up, keep it bound elsewhere.

(global-set-key (kbd "C-x M-x") 'execute-extended-command)

Find thing at point

C-c P f to find file at point. And C-c P u to find url at point.

(define-key global-map (kbd "C-c P f") 'find-file-at-point)
(define-key global-map (kbd "C-c P u") 'browse-url-at-point)

bury buffer

Like kill-buffer but just moves the buffer to the end of the buffer list.

(global-set-key (kbd "C-x K") 'bury-buffer)

Move macro keys

Default macro keys are in a weird place so let’s move them over. I had a reason to do this, but I’ve since forgotten.

(Note, f3 is already rebound in a different section. See here. Menu-bar-open (F10) default is not very useful – just use M-`.)

(global-set-key (kbd "<f9>") 'kmacro-start-macro-or-insert-counter)
(global-set-key (kbd "<f10>") 'kmacro-end-or-call-macro)

Lazy repeat

Having to type the default repeat key is torture. C-x z requires four actions. Hold down C, then type x. Release C. Type z. So instead, just Bind C-x C-z which means one can rapid-fire repeat with only two keystrokes per repeat.

(global-set-key (kbd "C-x C-z") 'repeat)

Save (yank) to register

(defun winny/save-last-kill-to-register (register)
  "Save the last kill to register."
  (interactive (list (register-read-with-preview "Copy last kill to register: ")))
  (set-register register (current-kill 0)))
(define-key global-map "\C-xr\C-y" 'winny/save-last-kill-to-register)

Advent of Code quickstart

This creates a new directory for the advent of code day, then creates a sample.txt buffer. Finally it creates a .rkt source file for the day with a template.

(fset 'new-aoc-day
      (kmacro-lambda-form [?\C-x ?d ?~ ?p ?r ?o ?/ ?a ?o ?c ?/ ?2 ?0 ?2 ?1 ?/ return ?+ ?d ?a ?y ?\C-u ?\C-x ?q return return ?\C-x ?\C-f ?s ?a ?m ?p ?l ?e ?. ?t ?x ?t return ?\C-x ?3 ?\C-x ?\C-f return ?^ ?\C-  ?\C-e ?\M-w return ?\C-x ?\C-f ?d ?a ?y backspace backspace backspace ?\C-y ?. ?r ?k ?t return ?\M-x ?y ?a return return] 0 "%d"))

Other packages / support stuff

writeroom-mode

This improves the presentation of emacs so it’s less distracting when writing prose. It centers the text, reduces paragraph width, and increases line height. It has application in other modes where reading content can be improved by applying the aforementioned visual tweaks.

(use-package writeroom-mode
  :ensure t
  :init
  (setq writeroom-fullscreen-effect nil))

projectile

Manage groups of buffers by project. Also do actions with respect to a project. A project root can be defined as a git repository, a folder with a .projectfile file in it, and so on.

(use-package projectile
  :ensure t
  :bind-keymap ("C-c p" . projectile-command-map)
  :config
  (setq projectile-mode-line-prefix " Pro")
  :init
  ;;(setq projectile-project-search-path '("~/pro" "~/code" "~/docs"))
  (setq projectile-project-search-path '("~/"))
  (projectile-mode 1))

systemd

(use-package systemd
  :ensure t)

ansible

Helper stuff for ansible.

(use-package ansible
  :ensure t)

Syntax highlight inventory files

(add-to-list 'auto-mode-alist '("/inventory[^/]*\\'" . conf-unix-mode))

Terraform

(use-package terraform-mode
  :ensure t)

vterm

;; Tell vterm to automatically try to compile the module when it's not present.
;; This prevents vterm from prompting the user if they wish to compile and
;; delaying productivity.
(setq vterm-always-compile-module t)

(use-package vterm
  :ensure t
  :bind (("C-`" . vterm))
  :init
  (setq vterm-set-bold-hightbright t))

Some other functions

Helper function for creating new emacs frames

(defun winny/raise-or-create-window-system-frame (display)
  "Raise an existing frame in the window system or create a new one.

DISPLAY is the X11 DISPLAY variable contents."
  (let ((frames (seq-filter #'(lambda (f) (frame-parameter f 'display)) (frame-list))))
    (if (null frames)
      (make-frame `((window-system . x)
                    (display . ,display)))
      (select-frame-set-input-focus (car frames)))))

Helper to remove item from an alist

(defun remove-from-list (list-var element)
  "Remove ELEMENT from LIST-VAR."
  (setq list-var (delete element list-var)))

toggle word wrap

M-x toggle-word-wrap RET

(defun toggle-word-wrap ()
  "Toggle word wrap."
  (interactive)
  (message (format
            "Word wrap %s."
            (if (setq word-wrap (not word-wrap))
              "enabled"
              "disabled"))))

Change the mode line and reload the major mode

(defun winny/change-prop-line-mode (mode &optional dont-change-mode)
  "Change the prop line's major MODE.
If DONT-CHANGE-MODE is not nil, dont change to that MODE first."
  (interactive "aMajor mode: \nP")
  (unless dont-change-mode
    (funcall-interactively mode))
  (delete-file-local-variable-prop-line 'mode)
  (let ((sans-mode (intern (replace-regexp-in-string "-mode$" "" (symbol-name mode)))))
    (add-file-local-variable-prop-line 'mode sans-mode nil)))

Bind a key in the current buffer only

Great for experimenting with keyboard shortcuts.

(defun buffer-local-set-key (key func)
  (interactive "KSet key on this buffer: \naCommand: ")
  (let ((name (format "%s-magic" (buffer-name))))
    (eval
     `(define-minor-mode ,(intern name)
        "Automagically built minor mode to define buffer-local keys."))
    (let* ((mapname (format "%s-map" name))
           (map (intern mapname)))
      (unless (boundp (intern mapname))
        (set map (make-sparse-keymap)))
      (eval
       `(define-key ,map ,key func)))
    (funcall (intern name) t)))

Find the current buffer as root

(defun winny/find-current-buffer-as-root ()
  "Find the current buffer as root using TRAMP sudo."
  (interactive)
  (when (file-remote-p default-directory)
    (error "Already a TRAMP buffer.  Giving up"))
  (let ((path (expand-file-name
               (if (eq major-mode 'dired-mode)
                 default-directory       ; Dired does not use buffer-file-name to represent a path
                 buffer-file-name))))
    (find-alternate-file (concat "/sudo:root@localhost:" path))))

Hide fringes

(defun hide-fringes ()
  "Hide fringes"
  (interactive)
  (set-window-fringes (selected-window) 0 0))

Change default directory

(defun change-default-directory (target)
  "Change DEFAULT-DIRECTORY to TARGET.

Useful for things like vterm, ansi-term, or term.  One can change
directory in the child shell but it won't reflect in Emacs.  This
allows the user to manually update this."
  (interactive "D")
  (setq default-directory target))

Narrow

Enable narrow-to-region

(put 'narrow-to-region 'disabled nil)

Emacs daemon/server quick keys

Like with-editor, set up server.el (see server-visit-files) with C-c C-c to “commit” save and close the buffer, and C-c C-k to revert and close the buffer (thereby discarding the edits).

(add-hook 'server-visit-hook
          (defun winny/server-visit-hook ()
            (when (frame-parameter nil 'winny/opened-from-editor)
              (buffer-local-set-key (kbd "C-c C-c") (defun winny/server-edit-commit ()
                                                      (interactive)
                                                      (save-buffer)
                                                      (server-edit)))
              (buffer-local-set-key (kbd "C-c C-k") (defun winny/server-edit-abort ()
                                                      (interactive)
                                                      (revert-buffer nil t)
                                                      (server-edit))))))

Load host-post-init.el

(winny/load-file-when-present "~/.emacs.d/host-post-init.el")

(And load the legacy host.el for now…)

(winny/load-file-when-present "~/.emacs.d/host.el")

Indication of completion

(message "configuration.org evaluation complete.")