Table of Contents

1 Introduction

This is the Emacs environment I (Wilfred Hughes) use for development.

1.1 Licensing

Code I've written (init.el and files under user-lisp/) is BSD licensed, see COPYING.

1.2 Reading This Document

This file is a literate programming document written with org-mode and org-mode-babel. You can view init.html in a web browser, or read init.org in Emacs.

You can edit any code block by putting point inside the block and pressing C-c ' (press C-c ' again to escape).

1.3 Generating This Document

init.org is the source file that I edit, and init.el and init.html are generated from it. We define a convenience function to generate these files.

(defun wh/export-init-org ()
  "Generate init.el and init.html from the current init.org file."
  (interactive)
  (call-interactively #'org-babel-tangle)
  ;; Export as HTML 5, and include our styling overrides.
  (let ((org-html-doctype "html5")
        (org-html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"init.css\" />")
        (org-html-htmlize-output-type 'css))
    (call-interactively #'org-html-export-to-html)))

2 Installing locally

This repository seeks to be as self-contained as possible. Generally, you should be able to check out the repository and start using it on any of Linux, OS X and Windows. To use this repository, you will require:

2.1 The Git Repository

Remove any existing .emacs.d configuration (or move elsewhere):

rm -r ~/.emacs.d

Clone the repo:

git clone git://github.com/Wilfred/.emacs.d.git

2.2 Emacs

I use this repository with the most recent major Emacs release. Older Emacsen are used occasionally, but will probably have bugs.

2.3 Python

I use pyflakes and flake8, which are available on PyPI. You can install them with:

pip install pyflakes flake8

2.4 Less

I use lessc to check the syntax of .less files. You can install it with:

sudo npm install -g less

3 Contents

3.1 Folder Structure

Code I've written lives in user-lisp. This includes packages that I haven't polished enough to release yet, small convenience functions, and package customisations.

Interactive commands, configuration and keybindings are kept in files named FOO-customisations.el. Elisp convenience functions are kept in files named FOO-utils.el.

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

Since init.el is generated from init.org, I don't want customize writing to init.org. Define a separate file for customized variables and faces:

(setq custom-file "~/.emacs.d/user-lisp/customized.el")
(load custom-file)

Code that I haven't written lives in third-party-lisp. This directory should ultimately disappear once all these packages live in MELPA.

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

3.2 Packages

I use ELPA packages heavily for functionality, primarily the MELPA repository. We initalize all the packages here, so we can use them later.

(require 'package)
(setq package-archives
      '(("gnu" . "https://elpa.gnu.org/packages/")
        ("melpa" . "https://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

(package-initialize)

3.3 UI

3.3.1 Theme

I like the tangotango theme. It has very contrasting colours and uses bold faces for definitions. It also has good support for a range of popular packages.

TODO: I would also prefer zig-zag underlines for flycheck.

(load-theme 'tangotango t)

3.3.2 Visibility of UI Elements

;; hide toolbar and scrollbar
(tool-bar-mode 0)
(scroll-bar-mode 0)

;; show x-position (ie column number) for point in buffer
(column-number-mode 1)

3.3.3 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)

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)

Occasionally, you end up with an active minibuffer that you didn't want. ESC ESC ESC will close the minibuffer.

3.4 Moving Around

C-v and M-v don't undo each other, because the point position isn't preservered. Fix that.

(setq scroll-preserve-screen-position 'always)

3.4.1 By Symbol

It's extremely useful to be able to move between different occurrences of the same symbol.

(define-key prog-mode-map (kbd "M-n") #'highlight-symbol-next)
(define-key prog-mode-map (kbd "M-p") #'highlight-symbol-prev)

;; Whilst YAML isn't a programming language, it's useful to move by
;; symbol here too.
(use-package yaml-mode
  :config
  (define-key yaml-mode-map (kbd "M-n") #'highlight-symbol-next)
  (define-key yaml-mode-map (kbd "M-p") #'highlight-symbol-prev)
  (define-key yaml-mode-map (kbd "M-N") #'highlight-symbol-last)
  (define-key yaml-mode-map (kbd "M-P") #'highlight-symbol-first))

Jumping to the first occurrence of the symbol is handy for finding where a symbol was imported.

(defun highlight-symbol-first ()
  "Jump to the first location of symbol at point."
  (interactive)
  (push-mark)
  (eval
   `(progn
      (goto-char (point-min))
      (let ((case-fold-search nil))
        (search-forward-regexp
         (rx symbol-start ,(thing-at-point 'symbol) symbol-end)
         nil t))
      (beginning-of-thing 'symbol))))

(define-key prog-mode-map (kbd "M-P") #'highlight-symbol-first)

More rarely, it's useful to be able to jump to the last occurrence of a symbol.

(defun highlight-symbol-last ()
  "Jump to the last location of symbol at point."
  (interactive)
  (push-mark)
  (eval
   `(progn
      (goto-char (point-max))
      (let ((case-fold-search nil))
        (search-backward-regexp
         (rx symbol-start ,(thing-at-point 'symbol) symbol-end)
         nil t)))))

(global-set-key (kbd "M-N") 'highlight-symbol-last)

3.4.2 By indentation

C-a normally moves us to the beginning of the line unconditionally with move-beginning-of-line. This version 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.

(global-set-key (kbd "C-a") #'crux-move-beginning-of-line)

3.4.3 By Character

Vim has a handy command where you can type f to jump to the next occurrence of a character on a line.

We can do this with jump-char without the constraint that the character must be on the current line. This command needs to be accessible with a short shortcut, so we use M-m. M-m is bound to back-to-indentation by default, but our C-a behaviour makes it redundant.

(global-set-key (kbd "M-m") #'jump-char-forward)
(global-set-key (kbd "M-M") #'jump-char-backward)

3.4.4 Measuring Movement

Since movement commands tend to be used more than any others, it's useful to measure how much we use each command. This enables us to look at frequent commands to see if we need to create custom commands or different keybindings for common commands.

(keyfreq-mode 1)
(keyfreq-autosave-mode 1)

3.5 Inserting

It's often useful to start a new line of code that's above or below the current line. This code is based on http://emacsredux.com/blog/2013/03/26/smarter-open-line/ .

(require 'crux)

(global-set-key (kbd "M-o") #'crux-smart-open-line)

(global-set-key (kbd "M-O") #'crux-smart-open-line-above)

3.6 Killing

It's handy to also delete the trailing newline when using C-k.

(defadvice kill-line (around kill-line-remove-newline activate)
  (let ((kill-whole-line t))
    ad-do-it))

I sometimes want to simply delete a region, rather than saving it to the kill-ring. I've added a function that allows me to type C-u C-w to delete the region, whilst C-w works as normal.

(defun kill-or-delete-region (beg end prefix)
  "Delete the region, storing it in the kill-ring.
If a prefix argument is given, don't change the kill-ring."
  (interactive "r\nP")
  (if prefix
      (delete-region beg end)
    (kill-region beg end)))

(global-set-key (kbd "C-w") 'kill-or-delete-region)

3.7 Modifying and Editing

Modifying text is fundamental to Emacs, and I use many utilites to make life easier. Most of these are still in user-lisp/editing-customisations.el.

3.7.1 Matched Pairs

Smartparens is an excellent way of editing pairs of brackets, quotes etc. It's similar to paredit, but can be used in lisp, other programming languages and even HTML.

Currently, I only use a few smartparens commands, using the same keybindings as the equivalent paredit commands. You can view a list of all smartparens commands with the command sp-cheat-sheet.

(require 'smartparens)

;; (foo bar) -> foo bar
(define-key smartparens-mode-map (kbd "M-s") 'sp-splice-sexp)

;; (foo bar) -> [foo bar]
(define-key smartparens-mode-map (kbd "M-S") 'sp-rewrap-sexp)

;; (|foo) bar -> (|foo bar)
(define-key smartparens-mode-map (kbd "<C-right>") 'sp-slurp-hybrid-sexp)

;; (|foo bar) -> (|foo) bar
(define-key smartparens-mode-map (kbd "<C-left>") #'sp-forward-barf-sexp)

;; foo(1, |[2, 3], 4) -> foo(1, |, 2)
(define-key smartparens-mode-map (kbd "C-M-k") #'sp-kill-sexp)
(define-key smartparens-mode-map (kbd "s-k") #'sp-kill-sexp)

(defun wh/smartparens-wrap-round (arg)
  "Smartparens equivalent of `paredit-wrap-round'."
  (interactive "P")
  (sp-wrap-with-pair "("))

(define-key smartparens-mode-map (kbd "M-(") #'wh/smartparens-wrap-round)

(defun wh/smartparens-wrap-square-bracket (arg)
  "[] equivalent of `wh/smartparens-wrap-round'."
  (interactive "P")
  (sp-wrap-with-pair "["))

(define-key smartparens-mode-map (kbd "M-[") #'wh/smartparens-wrap-square-bracket)

(defun wh/smartparens-wrap-curly-paren (arg)
  "{} equivalent of `wh/smartparens-wrap-round'."
  (interactive "P")
  (sp-wrap-with-pair "{"))

(define-key smartparens-mode-map (kbd "M-{") #'wh/smartparens-wrap-curly-paren)

(defun wh/smartparens-wrap-singlequote (arg)
  "As `wh/smartparens-wrap-round' but for wrapping with single quotes."
  (interactive "P")
  (sp-wrap-with-pair "'"))

(define-key smartparens-mode-map (kbd "M-'") #'wh/smartparens-wrap-singlequote)

Many useful smartparens have a C-M- prefix, which I find difficult to type. I use super (usually the windows key) keybindings too (so C-M-f becomes s-f and so on).

(define-key smartparens-mode-map (kbd "s-f") #'sp-forward-sexp)
(define-key smartparens-mode-map (kbd "C-M-f") #'sp-forward-sexp)

(define-key smartparens-mode-map (kbd "s-b") #'sp-backward-sexp)
(define-key smartparens-mode-map (kbd "C-M-b") #'sp-backward-sexp)

(define-key smartparens-mode-map (kbd "s-u") #'sp-backward-up-sexp)

I like to use smartparens in all programming modes.

Smartparens strict mode ensures parens always stay balanced when editing. For example, given code of the form foo(1, |bar()), C-k produces foo(1, |).

(require 'smartparens-config)
(require 'smartparens-html)
(add-hook 'prog-mode-hook #'smartparens-strict-mode)

Outside of programming, strict mode is more easily confused, so I prefer normal smartparens-mode.

(add-hook 'yaml-mode-hook #'smartparens-mode)
(add-hook 'sqlplus-mode-hook #'smartparens-mode)

3.8 Files

3.8.1 Opening

It's useful to be able to quickly open files that we opened before. We load and configure a function for this:

(require 'recentf)

;; offer recently accessed files from the menu
(recentf-mode t)

;; remember this many files
(setq recentf-max-saved-items 500)

;; from http://www.masteringemacs.org/article/find-files-faster-recent-files-package
(require 'crux)

We bind this to C-x C-r (mnemonic: recent). By default, C-x C-r is bound to find-file-read-only, which isn't very useful. (You can set any file as read only with read-only-mode, mapped to C-x C-q.)

(global-set-key (kbd "C-x C-r") #'crux-recentf-ido-find-file)

Most of the time though, it's helpful to be able to pick a file in the same source code repository as the current buffer. There are several tools to do this. I've played with find-file-in-repository, projectile and find-file-in-project.

find-file-in-repository is fast and works well, but is only lightly maintained and doesn't support some version control systems. projectile is fast enough, actively maintained and featureful.

(projectile-mode)

We bind projectile-find-file to C-x C-g, as we use it a lot and it's right next to C-x C-f.

(global-set-key (kbd "C-x C-g") 'projectile-find-file)

3.8.2 Dired

Dired isn't very colourful by default, but dired+ has helpful highlighting.

(use-package dired+
  :init
  (setq diredp-hide-details-initially-flag nil))

3.8.3 Deleting and Backups

When we delete a file, it should go to the recycle bin rather than just acting like rm.

(setq delete-by-moving-to-trash t)

Emacs' backup behaviour is helpful, so we increase the number of backups. However, rather than writing foo~1~ files everywhere, we store all our backups in ~/.saves.

(setq
   backup-by-copying t      ; don't clobber symlinks
   backup-directory-alist
    '(("." . "~/.saves"))    ; don't litter my fs tree
   delete-old-versions t
   kept-new-versions 6
   kept-old-versions 2
   version-control t)       ; use versioned backups

However, Emacs isn't aggressive enough with backups. We use backup-each-save to ensure we have a copy of state of every file we've modified.

(add-hook 'after-save-hook 'backup-each-save)

3.8.4 Scratch Files

It's often useful to create a throwaway file to write a minimal testcase for some language or library feature.

(defun start--file (path)
  "Create a file at PATH, creating any containing directories as necessary.
Visit the file after creation."
  (make-directory (file-name-directory path) t)
  (find-file path))

(defun wh/start-scratch-file (file-name)
  "Create a file in ~/scratch for the given file name."
  (interactive "sName of scratch file: ")
  (start--file (expand-file-name (format "~/scratch/%s" file-name))))

(defun wh/start-tmp-file (file-name)
  "Create a file in /tmp for the given file name."
  (interactive "sName of temporary file: ")
  (start--file (expand-file-name (format "/tmp/%s" file-name))))

It's also useful to quickly generate a minimal HTML page to play with.

(defun wh/start-scratch-html-file (file-name)
  "Create a test HTML file in ~/scratch to play around with."
  (interactive "sName of scratch HTML file: ")
  (wh/start-scratch-file file-name)
  (erase-buffer)
  (insert "<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
        <title>
        </title>
        <style type=\"text/css\">
        </style>
    </head>
    <body>

    </body>
</html>")
  (forward-line -2)
  (move-end-of-line nil))

3.9 As-you-type Checks

3.9.1 Flycheck

Flycheck is an excellent on-the-fly checker that provides many additional features and languages. Flymake is part of stock Emacs, flycheck is third-party.

Flycheck can be quite slow with a large number of errors. We reduce how often we run it. We also change the highlighting to simply highlight the whole line, as it's much faster. See flycheck issue #53.

(setq flycheck-highlighting-mode 'lines)

I prefer my errors underlined.

(custom-set-faces
 '(flycheck-error ((((class color)) (:underline "Red"))))
 '(flycheck-warning ((((class color)) (:underline "Orange")))))

It's really useful to be able to move between flymake errors, so we bind F8 and F9 for this. Since there's a gap between these two keys, they're easy to find.

(require 'flycheck)
(define-key flycheck-mode-map (kbd "<f8>") 'flycheck-previous-error)
(define-key flycheck-mode-map (kbd "<f9>") 'flycheck-next-error)

flycheck also provides a great overview buffer, but it's usually bound to C-c ! f. This is tricky to type, so we use our own keybinding.

(define-key flycheck-mode-map (kbd "C-c f") #'flycheck-list-errors)

flycheck-next-error doesn't push the mark, so we can't use pop-mark to go back to our previous position. We define and activate advice to fix that.

(defadvice flycheck-next-error (before wh/flycheck-next-error-push-mark activate)
  (push-mark))

Since I like using the minibuffer for eldoc, showing flycheck errors in the minibuffer just causes it to jump around. I've played with flycheck-pos-tip, but I find the popup hides code, or covers company popups.

Instead, I show the flycheck error in the title bar of the window if there's an error at point.

(with-eval-after-load 'flycheck
  (flycheck-title-mode))

3.10 Undoing

Emacs' undo facility is excellent, but undo-tree is even better.

(require 'undo-tree)
(global-undo-tree-mode)

Rather than just showing 'o' for edits, show a relative timestamp for when the edit occurred.

(setq undo-tree-visualizer-timestamps t)

Since we're using it the whole time, it's not very informative to show it on the mode line. Hide it.

(diminish 'undo-tree-mode)

3.11 Languages

3.11.1 Emacs Lisp

  1. Shortcuts

    eval-defun is bound to C-M-x, but Gnome doesn't allow Emacs to receive that key sequence. When writing elisp, it's very useful, so we bind it to a convenient keybinding.

    edebug-eval-defun is even more powerful. It ensures that defvar and defcustom are re-evaluated, so they're reset to their initial values. It can even mark a function for edebug, if it's called with a prefix.

    (require 'edebug)
    (define-key emacs-lisp-mode-map (kbd "C-c e") #'edebug-eval-defun)
    

    Similarly, toggle-debug-on-error is something I call a lot when developing, and it doesn't have have any keybinding.

    (define-key emacs-lisp-mode-map (kbd "C-c d") 'toggle-debug-on-error)
    

    When writing and debugging macros, it's really important to be able to see what they expand to. Macrostep allows us to incrementally expand the macros in our elisp file.

    (define-key emacs-lisp-mode-map (kbd "C-c m") 'macrostep-expand)
    
  2. Highlighting Parentheses

    We colour each pair of parentheses according to their depth. This is useful for seeing similarly nested lines, such as conditions in a cond expression.

    (add-hook 'emacs-lisp-mode-hook 'rainbow-delimiters-mode)
    

    Our theme (tangotango) only provides colours for the first few nesting levels before repeating. We override the face colours so we have unique colours until we're seven levels deep.

    (require 'rainbow-delimiters)
    (set-face-foreground 'rainbow-delimiters-depth-1-face "white")
    (set-face-foreground 'rainbow-delimiters-depth-2-face "cyan")
    (set-face-foreground 'rainbow-delimiters-depth-3-face "yellow")
    (set-face-foreground 'rainbow-delimiters-depth-4-face "green")
    (set-face-foreground 'rainbow-delimiters-depth-5-face "orange")
    (set-face-foreground 'rainbow-delimiters-depth-6-face "purple")
    (set-face-foreground 'rainbow-delimiters-depth-7-face "white")
    (set-face-foreground 'rainbow-delimiters-depth-8-face "cyan")
    (set-face-foreground 'rainbow-delimiters-depth-9-face "yellow")
    (set-face-foreground 'rainbow-delimiters-unmatched-face "red")
    
  3. Function Signatures

    We use eldoc to show the signature of the function at point in the minibuffer.

    (add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
    

    We don't want this minor mode to be shown in the minibuffer, however.

    (use-package eldoc
      :diminish "")
    
  4. On-the-fly Checking

    It's really useful to use flycheck when coding elisp. It detects mistyped variables, deprecated functions (everything that byte-compilation checks).

    (add-hook 'emacs-lisp-mode-hook 'flycheck-mode)
    

    By default, flycheck also runs checkdoc on elisp code. This gets in the way for quick throwaway elisp scripts, so we switch off checkdoc.

    (use-package flycheck
      :config
      (setq flycheck-checkers (--remove (eq it 'emacs-lisp-checkdoc) flycheck-checkers)))
    
  5. Highlighting

    Emacs lisp highlighting works pretty well out of the box. However, dash.el provides addition highlighting for its functions and variables used in its anaphoric macros (e.g. it).

    (eval-after-load "dash" '(dash-enable-font-lock))
    
  6. Execute on package load

    Finally, wrap this logic in a use-package block so we don't slow down startup.

    (use-package elisp-mode
      :config
      (require 'edebug)
      (define-key emacs-lisp-mode-map (kbd "C-c e") #'edebug-eval-defun)
      (define-key emacs-lisp-mode-map (kbd "C-c d") 'toggle-debug-on-error)
      (define-key emacs-lisp-mode-map (kbd "C-c m") 'macrostep-expand)
      (add-hook 'emacs-lisp-mode-hook 'rainbow-delimiters-mode)
      (require 'rainbow-delimiters)
      (set-face-foreground 'rainbow-delimiters-depth-1-face "white")
      (set-face-foreground 'rainbow-delimiters-depth-2-face "cyan")
      (set-face-foreground 'rainbow-delimiters-depth-3-face "yellow")
      (set-face-foreground 'rainbow-delimiters-depth-4-face "green")
      (set-face-foreground 'rainbow-delimiters-depth-5-face "orange")
      (set-face-foreground 'rainbow-delimiters-depth-6-face "purple")
      (set-face-foreground 'rainbow-delimiters-depth-7-face "white")
      (set-face-foreground 'rainbow-delimiters-depth-8-face "cyan")
      (set-face-foreground 'rainbow-delimiters-depth-9-face "yellow")
      (set-face-foreground 'rainbow-delimiters-unmatched-face "red")
      (add-hook 'emacs-lisp-mode-hook 'eldoc-mode)
      )
    

3.11.2 Python

We use flake8 with flycheck to check for coding errors. Flycheck includes other Python checkers which we disable.

(add-hook 'python-mode-hook 'flycheck-mode)

(add-hook 'python-mode-hook
          (lambda ()
            (add-to-list 'flycheck-disabled-checkers 'python-pylint)))

Since flake8 includes pep8 style checks, we use a whitelist of coding errors to ignore style checks. This file is in unix configuration file format, so ensure we highlight it correctly when editing.

(add-to-list 'auto-mode-alist
             (cons (rx "flake8" eos) #'conf-mode))

I often write triple-quoted docstrings, so it's convenient to have a shortcut for inserting them.

(use-package python
  :config
  (define-skeleton python-insert-docstring
    "Insert a Python docstring."
    "This string is ignored!"
    "\"\"\"" - "\n\n    \"\"\"")

  (define-key python-mode-map (kbd "C-c s") 'python-insert-docstring))

3.11.3 Haskell

Flycheck supports Haskell well, so we switch it on inside Haskell buffers.

(add-hook 'haskell-mode-hook 'flycheck-mode)

Tab doesn't indent in haskell-mode by default, so we enable indentation.

(add-hook 'haskell-mode-hook 'turn-on-haskell-indentation)

3.11.4 Ruby

Vagrant files are Ruby, so use Ruby syntax highlighting for them.

(add-to-list 'auto-mode-alist '("Vagrantfile" . ruby-mode))

3.11.5 C/C++

Flycheck supports C, so we switch it on.

(add-hook 'c-mode-common-hook #'flycheck-mode)

Always indent with 4 spaces, in the Linux kernel style.

(setq-default c-default-style "linux"
              c-basic-offset 4)

Hungry delete is useful in C (i.e. remove up to the next non-whitespace character on C-d) when removing indentation.

(setq-default c-hungry-delete-key t)

3.11.6 HTML

I like to indent my HTML with four spaces.

(use-package sgml-mode
  :config
  (setq sgml-basic-offset 4))

Automatically close < and " character inside HTML using smartparens.

(require 'smartparens-config)
(add-hook 'html-mode-hook 'smartparens-strict-mode)

Much of my HTML is for Django templates. These sometimes have .dtml filenames, so use html-mode for those files.

(add-to-list 'auto-mode-alist '("\\.dtml$" . html-mode))

We want syntax highlighting for Django template syntax, so add extra font faces and use them if we see Django syntax.

;; Define coloured faces for Django syntax.
(defvar django-tag-face (make-face 'django-tag-face))
(set-face-foreground 'django-tag-face "Orange")
;
(defvar django-variable-face (make-face 'django-variable-face))
(set-face-foreground 'django-variable-face "Green")

(defvar django-comment-face (make-face 'django-comment-face))
(set-face-foreground 'django-comment-face "Gray")

;; Use these faces for Django syntax.  
(font-lock-add-keywords
 'html-mode
 '(
   ("\\({%[^%]*%}\\)" 1 django-tag-face prepend)
   ("\\({{[^}]*}}\\)" 1 django-variable-face prepend)
   ("\\({#[^}]*#}\\)" 1 django-comment-face prepend)
   ("\\({% comment %}\\(.\\|
\\)*{% endcomment %}\\)" 1 django-comment-face prepend)
   ))

TODO: document the rest of our HTML configuration.

; skeletons for Django template tags
(define-skeleton template-tag-skeleton
  "Insert a {% foo %} template tag"
  "Template tag name: "
  "{% " str " %}")
(define-skeleton template-variable-skeleton
  "Insert a {{ foo }} template variable"
  "Template variable: "
  "{{ " str " }}")
(define-skeleton template-comment-skeleton
  "Insert a {# foo #} template variable"
  "Comment: "
  "{# " str " #}")
(define-skeleton template-block-skeleton
  "Insert {% block foo %}{% endblock %}"
  "Block name: "
  "{% block " str " %}\n" - "\n{% endblock %}")
(define-skeleton template-if-else-skeleton
  "Insert {% if foo %}{% else %}{% endif %}"
  "If condition: "
  "{% if " str " %}\n" - "\n{% else %}\n\n{% endif %}")
(define-skeleton template-if-skeleton
  "Insert {% if foo %}{% endif %}"
  "If condition: "
  "{% if " str " %}" - "{% endif %}")
(define-skeleton underscore-skeleton
  "Insert <%= foo %>"
  "Contents: "
  "<%= " str " %>")

(defvar template-skeletons
  '(template-tag-skeleton
    template-variable-skeleton
    template-comment-skeleton
    template-block-skeleton
    template-if-skeleton
    template-if-else-skeleton
    underscore-skeleton))

(defun insert-django-skeleton ()
  (interactive)
  (let* ((skeleton-names (mapcar 'symbol-name template-skeletons))
         (skeleton-chosen (ido-completing-read "HTML skeleton: " skeleton-names)))
    (funcall (intern skeleton-chosen))))

(use-package sgml-mode
  :config
  (define-key html-mode-map "\C-ct" 'insert-django-skeleton))

(defun visit-parent-django-template ()
  "In a buffer containg {% extends \"foo.html\" %}, visit foo.html."
  (interactive)
  (let (start-pos end-pos template-name)
    (save-excursion
      (widen)
      (goto-char (point-min))
      ;; Find the extends tag
      (while (not (looking-at "{% ?extends"))
        (forward-char 1))
      ;; Find the opening " of the file name.
      (while (not (looking-at "\""))
        (forward-char 1))
      (forward-char)
      (setq start-pos (point))

      ;; Find the closing "
      (while (not (looking-at "\""))
        (forward-char 1))
      (setq end-pos (point))

      (setq template-name (buffer-substring-no-properties start-pos end-pos)))

    ;; Open this file, assuming it's in the same directory.
    ;; TODO: Search the current VCS checkout for it.
    (find-file template-name)))

(defun html-linkify-region (url)
  "Wraps the region in an <a> tag with href set to URL."
  (interactive "sURL: ")
  (let* ((initial-cursor-position (point))
         (beginning (region-beginning))
         (end (region-end))
         (first-replacement (concat "<a href=\"" url "\">"))
         (second-replacement "</a>"))
    (goto-char beginning)
    (insert first-replacement)
    (goto-char (+ end (length first-replacement)))
    (insert second-replacement)
    (goto-char (+ initial-cursor-position (length first-replacement)))))

;; zen coding: converts selector-style lines to tags
;; e.g. table>tr*2 becomes <table><tr></tr><tr></tr></table>
(add-hook 'sgml-mode-hook 'zencoding-mode) ;; Auto-start on any markup modes

3.11.7 CSS

Typically I work on projects that use 4 spaces for CSS indenetation.

(setq css-indent-offset 4)

It's really handy to highlight CSS colour values to show the colour they represent.

(add-hook 'css-mode-hook 'rainbow-mode)

Smartparens is well suited to CSS too, to automatically pair up curly brackets.

(add-hook 'css-mode-hook #'smartparens-strict-mode)

Highlight symbols, so we can see repeated tag names and classes.

(add-hook 'css-mode-hook #'highlight-symbol-mode)

Company does a great job with completion for CSS, so use it here.

(add-hook 'css-mode-hook #'company-mode)

I often toggle !important when editing, so define a keybinding for this.

(require 'css-mode)
(define-key css-mode-map (kbd "C-c i") #'emr-css-toggle-important)

3.11.8 Less (CSS)

The less compiler doesn't give much feedback, but it does gives us a syntax check.

(use-package less-css-mode
  :config
  (add-hook 'less-css-mode-hook 'flymake-mode))

3.11.9 Org-mode

We often use code snippets in org-mode files, so syntax highlight them.

(setq org-src-fontify-natively t)

3.11.10 Markdown

Markdown is essentially prose, so it's nice to automatically line-wrap (by inserting newlines) as we type.

(add-hook 'markdown-mode-hook 'auto-fill-mode)

3.11.11 Shell

In OS X, starting Emacs in GUI mode doesn't inherit the shell's environment. We set up Emacs' exec-path based on PATH in a shell, so any command we can call from a shell, we can call inside Emacs.

(use-package exec-path-from-shell
  :init
  (setq exec-path-from-shell-check-startup-files nil)
  (exec-path-from-shell-initialize))

3.11.12 Assembly

GNU as comment syntax varies, but # is used on i386 and x8664. See http://en.wikipedia.org/wiki/GNU_Assembler#Comments

(use-package asm-mode
  :config
  (setq asm-comment-char ?#))

3.11.13 LLVM

(use-package llvm-mode
  :config
  ;; TODO: llvm-mode should really inherit from prog-mode.
  (add-hook 'llvm-mode-hook #'highlight-symbol-mode))

3.11.14 CoffeeScript

Set indentation, dabbrev completion and flycheck in CS.

(use-package coffee-mode
  :config
  (setq coffee-tab-width 2)

  (defun wh/company-in-coffee-mode ()
    (set (make-local-variable 'company-backends) (list #'company-dabbrev-code)))
  (add-hook 'coffee-mode-hook #'wh/company-in-coffee-mode)

  (add-hook 'coffee-mode-hook #'flycheck-mode))

3.12 Applications

3.12.1 IRC

(use-package rcirc
  :config
  (setq rcirc-default-nick "wilfredh")
  (setq rcirc-server-alist
        '(("irc.freenode.net" :channels ("#emacs"))
          ("irc.mozilla.org" :channels ("#rust"))))
  ;; Keep history.
  (setq rcirc-log-flag t)
  (setq rcirc-log-directory "~/irc_logs")
  ;; Taken from
  ;; https://github.com/s1n4/dotfiles/blob/master/emacs.d/config/rcirc-config.el
  (defun wh/log-filename-with-date (process target)
    (format
     "%s_%s.log"
     (if target
         (rcirc-generate-new-buffer-name process target)
       (process-name process))
     (format-time-string "%Y-%m-%d")))

  (setq rcirc-log-filename-function #'wh/log-filename-with-date)
  ;; Ignore away/join/part messages from lurkers.
  (setq rcirc-omit-responses '("JOIN" "PART" "QUIT" "NICK" "AWAY"))
  (add-hook 'rcirc-mode-hook #'rcirc-omit-mode)
  (require 'rcirc-color))

3.12.2 Twitter

(use-package twittering-mode
  :init
  ;; Don't show the twitter client or location, it's just distracting.
  (setq twittering-status-format "%i %s,  %@:\n%FILL[  ]{%T %r%R}\n "))

3.12.3 Google

Googling the word at point or current region is very handy.

(use-package google-this
  :config
  (global-set-key (kbd "C-c g") #'google-this))

3.13 Performance

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 10 MiB we reduce the number of pauses due to garbage collection.

(setq gc-cons-threshold (* 10 1024 1024))

3.14 Shut Down

I rarely close Emacs, but using Zile means I use C-x C-c a lot. It's annoying to accidentally close Emacs, so warn first.

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

3.15 Workarounds

3.15.1 prog-mode

Modes derived from cc-mode (e.g. c-mode, c++-mode, java-mode) don't derive from prog-mode. This means that keybindings in prog-mode-map don't apply.

This has been fixed in Emacs 26 (bug #26658), but patch in the meantime.

(eval-after-load 'cc-mode
  '(set-keymap-parent c-mode-base-map prog-mode-map))

3.15.2 Crux

Don't try to reopen all files that are owned by other users. https://github.com/bbatsov/crux/issues/20

(remove-hook 'find-file-hook #'crux-reopen-as-root)

3.16 Undocumented

(require 'ui-customisations)

(require 'file-customisations)
(require 'movement-customisations)
(require 'editing-customisations)
(require 'kill-ring-customisations)

(if (eq system-type 'darwin)
    (require 'os-x-fixes))

(require 'minibuffer-completion-customisations)

;; make re-builder use the same regexp format as regexp-replace (no double escaping)
(setq reb-re-syntax 'string)

;; treat space charcters as matching space characters, not like PCRE's '\s+'
(setq search-whitespace-regexp nil)

(require 'completion-customisations)
(require 'snippet-customisations)
(require 'structured-text-customisations)
(require 'isearch-customisations)
(require 'projectile-customisations)

(require 'ocaml-customisations)
(require 'go-customisations)
(require 'c-customisations)
(require 'javascript-customisations)
(require 'lisp-customisations)
(require 'makefile-customisations)
(require 'python-customisations)
(require 'rust-customisations)
(require 'sh-customisations)
(require 'xml-customisations)
(require 'html-customisations)
(require 'lsp-customisations)

(require 'startup-customisations)

(require 'git-customisations)
(require 'eshell-customisations)

(require 'compilation-customisations)

;; TODO: distinguish missing file from error during execution
(ignore-errors (require 'site-customisations))

(setq ag-highlight-search 't)
(global-set-key (kbd "<f5>") #'ag-project)
(global-set-key (kbd "<f5>") #'deadgrep)

(require 'conflicts-customisations)
(require 'org-customisations)

(require 'tags-customisations)

(require 'blog-utils)

;; crontab mode for files named crontab.foo
(add-to-list 'auto-mode-alist '("crontab.*?\\'" . crontab-mode))

(setq ring-bell-function 'ignore)

;; suspend seems to crash on Gnome 3, and I don't use it anyway, so just disable it
(setq cannot-suspend t)
(defun suspend-emacs (&rest _)
  (interactive))
(defun suspend-frame (&rest _)
  (interactive))

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

(defun indent-buffer ()
  "Indent the everything in the current buffer."
  (interactive)
  (indent-region (point-min) (point-max)))

(require 'f)
(require 's)

(defun wh/download-file (url directory file-name)
  "Download the file at URL into DIRECTORY.
The FILE-NAME defaults to the one used in the URL."
  (interactive
   ;; We're forced to let-bind url here since we access it before
   ;; interactive binds the function parameters.
   (let ((url (read-from-minibuffer "URL: ")))
     (list
      url
      (read-directory-name "Destination dir: ")
      ;; deliberately not using read-file-name since that inludes the directory
      (read-from-minibuffer
       "File name: "
       (car (last (s-split "/" url)))))))
  (let ((destination (f-join directory file-name)))
    (url-copy-file url destination 't)
    (find-file destination)))

(setq-default dired-listing-switches "-alhv")

(global-anzu-mode +1)

(require 'diminish)
(diminish 'anzu-mode)
(put 'dired-find-alternate-file 'disabled nil)

;; There are a few applications, such as crontab, that require a
;; trailing new line. To be safe, always leave a trailing newline.
(setq-default require-final-newline t)

;; cycle through amounts of spacing
;; http://pragmaticemacs.com/emacs/cycle-spacing/
(global-set-key (kbd "M-SPC") #'cycle-spacing)

Author: Wilfred Hughes

Created: 2021-07-25 Sun 09:59

Validate