My Emacs configuration

Below are fragments from my Emacs configuration file (~/.emacs.d/init.el).

The newest version of this config is available from a github repo.

I recommend watching the following video by BuildFunThings called My GNU Emacs configuration for programming.

When I want to ensure all of the packages (on a new machine) I set use-package-always-ensure below to t start Emacs and then set it back to nil.

Basic settings

Directory with local Emacs lisp files

I add a directory to the lisp search path where I can add my own lisp code and (now less often) downloaded lisp code which is not available through MELPA. Then I initialize secure downloading from GNU and MELPA archives of Emacs packages.

(let ((path (expand-file-name "~/.emacs.d/lisp")))
  (if (file-accessible-directory-p path)
      (add-to-list 'load-path path t)))

Add MELPA package list

You can install many Emacs packages from MELPA repository. To add MELPA to the package list add the following to your ~/.emacs.d/init.el file

(require 'package)
(setq package-archives
      '(("gnu" . "")
        ("melpa-stb" . "")
        ("melpa" . ""))
      tls-checktrust t
      tls-program '("gnutls-cli --x509cafile %t -p %p %h")
      gnutls-verify-error t)


(setq use-package-always-ensure nil)
(require 'use-package)

This also turns on checking TLS certificates (in both possible modes) with tls-program set to only the first value from the default value (for more info see Your Text Editor Is Malware).

Now you can list available packages by running M-x list-packages. Mark packages you want to install by pressing i and later press x to install all marked packages (the necessary dependencies will be installed automatically).

Note: The last line above requires installing package named use-package to work.

Other settings

(setq recentf-max-saved-items 100)

Workaround for security vulnerability in Emacs >= 21.1 and < 25.3

See Changes in Emacs 25.3

(eval-after-load "enriched"
    '(defun enriched-decode-display-prop (start end &optional param)
       (list start end)))


More efficient buffer/file selection

(use-package helm
  (setq helm-split-window-default-side 'other)
  (helm-mode 1)
  (define-key helm-find-files-map
    (kbd "<backtab>") #'helm-select-action)
  (define-key helm-find-files-map
    (kbd "C-i")  #'helm-execute-persistent-action)
  (("M-x" . helm-M-x)
   ("M-y" . helm-show-kill-ring)
   ("C-x C-f" . helm-find-files)
   ("C-c o" . helm-occur)
   ("C-x b" . helm-mini)
   ("C-x r b" . helm-bookmarks)
   ("C-h a" . helm-apropos)
   ("C-h d" . helm-info-at-point)
   ("C-c L" . helm-locate)
   ("C-c r" . helm-resume)
   ("C-c i" . helm-imenu)))

(use-package helm-swoop
  (("C-s" . helm-swoop-without-pre-input)
   ("C-S-s" . helm-swoop)))

(use-package helm-descbinds

(use-package helm-git-grep
  (("C-c j" . helm-git-grep)
   ("C-c J" . helm-git-grep-at-point)))

(use-package helm-ls-git
  (("C-c g" . helm-ls-git-ls)))

(use-package helm-make
  (("C-c K" . helm-make)))

(use-package helm-c-yasnippet
  (("C-c y" . helm-yas-complete)))

(use-package rg
  (("C-c R" . rg)))

(use-package treemacs
  (("C-c t" . treemacs)
   ("s-a" . treemacs)))

Cycle through buffers' history

(use-package buffer-flip
  (("s-v" . buffer-flip)
   :map buffer-flip-map
   ("s-v" . buffer-flip-forward)
   ("s-V" . buffer-flip-backward)
   ("C-g" . buffer-flip-abort)))

fzf and lcd for finding files and directories

(defun my-lcd ()
  (fzf/start default-directory
             (fzf/grep-cmd "lcd" "-l %s")))

(use-package fzf
  (autoload 'fzf/start "fzf")
  (("C-c f" . fzf)
   ("C-c d" . my-lcd)))

Window selection enhancements

(use-package ace-window
  ("C-x o" . ace-window))

(use-package windmove
  (("C-s-n" . windmove-down)
   ("C-s-p" . windmove-up)
   ("C-s-b" . windmove-left)
   ("C-s-f" . windmove-right))

(use-package perspective

Allow for Undo/Redo of window manipulations (such as C-x 1)

(winner-mode 1)

In buffer movement enhancements

Remind of keys than can follow in a key sequence

(use-package which-key

Type prefix and wait to select one of the with a single or two letters

(use-package avy
  ("C-:" . avy-goto-char-timer))

Bind key o to selection of links by a single or two letters

(use-package ace-link

Select from visible errors by a single letter

(use-package avy-flycheck
  ("C-c '" . avy-flycheck-goto-error))

Go to last change in the buffer

(use-package goto-chg
  ("C-c G" . goto-last-change))

Editing enhancements

Context aware insertion of pairs of parenthesis

(use-package smartparens

Edit with multiple cursors

(use-package multiple-cursors
  (("C-c n" . mc/mark-next-like-this)
   ("C-c p" . mc/mark-previous-like-this)))

Fix trailing spaces but only in modified lines

(use-package ws-butler
  (add-hook 'prog-mode-hook #'ws-butler-mode))

Convenience functions, aliases, and key bindings

;; Convenience functions and aliases

(defun am ()
  "Change dictionary to american."
  (setq ispell-local-dictionary "american"))

(defun pl ()
  "Change dictionary to polish."
  (setq ispell-local-dictionary "polish"))

(defalias 'st #'magit-status)
(defalias 'ir #'ispell-region)
(defalias 'md #'markdown-mode)

;; Bind keys

(global-set-key "\C-ck" #'compile)
(global-set-key "\C-cq" #'bury-buffer)

(use-package shell-pop
  (setq shell-pop-full-span t)
  :bind (("C-c s" . shell-pop)))

(use-package helm-mt
  :bind (("C-c S" . helm-mt)))

(use-package magit
  :bind ("C-c m" . magit-status))

(use-package git-messenger
  :bind ("C-c M" . git-messenger:popup-message)
  (setq git-messenger:show-detail t
        git-messenger:use-magit-popup t))

Switching buffers

Set keys from s-s a to s-s z to switch to buffers from a register from a to z

(defalias 'pr #'point-to-register)

(defun my-switch-to-register ()
  "Switch to buffer given by a register named by last character
of the key binding used to execute this command."
  (let* ((v (this-command-keys-vector))
         (c (aref v (1- (length v))))
         (r (get-register c)))
    (if (and (markerp r) (marker-buffer r))
        (switch-to-buffer (marker-buffer r))
      (jump-to-register c))))

(setq my-switch-to-register-map (make-sparse-keymap))

(let ((character ?a))
  (while (<= character ?z)
    (define-key my-switch-to-register-map
      (format "%c" character) #'my-switch-to-register)
    (setq character (1+ character))))

(global-set-key (kbd "s-s") my-switch-to-register-map)

Programming languages

C and C++

The following Emacs packages from MELPA need to be installed: cmake-ide, company, and rtags. Package cmake-ide automatically configures other C++ Emacs packages (here company and rtags) when you open a C/C++ file from a project which uses cmake to build.

(defconst my-cc-style
    (c-offsets-alist . ((innamespace . [0])))))

(c-add-style "my-cc-mode" my-cc-style)

(setq-default c-basic-offset 8)
(setq c-default-style '((java-mode . "java")
                        (awk-mode . "awk")
                        (c++-mode . "my-cc-mode")
                        (other . "k&r"))
      company-async-timeout 5		; completion may be slow
      rtags-completions-enabled t)

(use-package rtags
  ;; need to install all three: rc rdm rp for jump to definition to work
  (rtags-enable-standard-keybindings nil "C-c R"))

(use-package cmake-ide
  :after cc-mode

(defun my-c-c++-mode-hook-fn ()
  (set (make-local-variable 'company-backends) '(company-rtags))
  (local-set-key (kbd "M-.") #'rtags-find-symbol-at-point)
  (local-set-key (kbd "M-,") #'rtags-location-stack-back)
  (local-set-key "\C-i" #'company-indent-or-complete-common)
  (local-set-key (kbd "<tab>") #'company-indent-or-complete-common)
  (local-set-key "\C-\M-i" #'company-indent-or-complete-common))

(add-hook 'c-mode-hook #'my-c-c++-mode-hook-fn)
(add-hook 'c++-mode-hook #'my-c-c++-mode-hook-fn)


  1. Install clang compiler or more accurately libclang library (package libclang-dev or may be newer libclang-X.Y-dev under Debian) which is required by rtags.

  2. Under Emacs having rtags package installed press M-: and evaluate expression (require 'rtags) and then press M-x and run command rtags-install (should work if you have llvm-config in your path) and wait until it compiles to 100%. The rtags-install command needs to be rerun if you install newer rtags from MELPA and the below commands do not work complaining about protocol mismatch.

  3. Usefull rtags functions (use C-c r C-h to see these and other key bindings)

    Key Function
    C-c r . rtags-find-symbol-at-point
    C-c r [ rtags-location-stack-back
    C-c r , rtags-find-references-at-point
    C-c r / rtags-find-all-references-at-point
    C-c r v rtags-find-virtuals-at-point
    C-c r ; rtags-find-file (in the current project no metter in which directory)

Lisp and Emacs lisp

;; in emacs 25.1: M-. runs xref-find-definitions,  M-, jumps back
(global-set-key (kbd "C-c e l") #'find-library)

(setq slime-lisp-implementations '((sbcl ("sbcl")))
      slime-default-lisp 'sbcl
      slime-contribs '(slime-fancy))

(let ((path (expand-file-name "/usr/local/share/doc/HyperSpec/")))
  (if (file-accessible-directory-p path)
      (setq common-lisp-hyperspec-root (concat "file://" path))))

(use-package paredit
  (add-hook 'eval-expression-minibuffer-setup-hook #'paredit-mode)

(use-package paren-face

(defun my-emacs-lisp-mode-hook-fn ()
  (set (make-local-variable 'lisp-indent-function) #'lisp-indent-function)
  (paredit-mode 1)
  (local-set-key (kbd "C-c S") (global-key-binding (kbd "M-s")))
  (local-set-key (kbd "C-c C-z")
                 (lambda () (interactive) (switch-to-buffer "*scratch*")))
  (show-paren-mode 1)

(defun my-lisp-mode-hook-fn ()
  (set (make-local-variable 'lisp-indent-function)
  (paredit-mode 1)
  (local-set-key (kbd "C-c S") (global-key-binding (kbd "M-s")))
  (show-paren-mode 1)

(add-hook 'emacs-lisp-mode-hook #'my-emacs-lisp-mode-hook-fn)
(add-hook 'lisp-mode-hook #'my-lisp-mode-hook-fn)

(defun slime-qlot-exec (directory)
  (interactive (list (read-directory-name "Project directory: ")))
   :program "qlot"
   :program-args '("exec" "ros" "-S" "." "run")
   :directory directory
   :name 'qlot
   :env (list (concat "PATH="
                      (mapconcat #'identity exec-path ":"))
              (concat "QUICKLISP_HOME="
                      (file-name-as-directory directory) "quicklisp/"))))


(setq js-indent-level 8)


I use the following setup for the go-mode in my ~/.emacs.d/init.el. This adds syntax highlighting but without fontifing names of called functions, autocompletion and eldoc support, auto formatting of the code on save with adding of missing imports (goimports).

It is quite long as I define two interactive functions:

  1. my-go-electric-brace which is bind to { key and inserts an indented pair of braces (if previous character is a space, otherwise it inserts single opening brace),

  2. my-godoc-package which is bind to C-c P key and display documentation for a package choosen from a list of installed packages.

(defun my-go-electric-brace ()
  "Insert an opening brace may be with the closing one.
If there is a space before the brace also adds new line with
properly indented closing brace and moves cursor to another line
inserted between the braces between the braces."
  (if (not (looking-back " "))
      (insert "{")
    (insert "{")
      (insert "}")

(defun my-go-list-packages ()
  "Return list of Go packages."
     (shell-command "go list ... 2>/dev/null" (current-buffer))
     (buffer-substring-no-properties (point-min) (point-max)))

(defun my-godoc-package ()
  "Display godoc for given package (with completion)."
  (godoc (helm :sources (helm-build-sync-source "Go packages"
                          :candidates (my-go-list-packages))
               :buffer "*godoc packages*")))

(use-package flycheck

(use-package go-eldoc

(use-package company-go

(use-package go-guru

(use-package go-mode
  (setq gofmt-command "goimports"     ; use goimports instead of gofmt
        go-fontify-function-calls nil ; fontifing names of called
                                      ; functions is too much for me
        company-idle-delay nil)
  (require 'go-guru)
  (:map go-mode-map
   ("M-." . go-guru-definition)
   ("C-c d" . godoc-at-point)
   ("C-c g" . godoc)
   ("C-c h" . go-guru-hl-identifier)
   ("C-c P" . my-godoc-package)
   ("{" . my-go-electric-brace)
   ("C-i" . company-indent-or-complete-common)
   ("C-M-i" . company-indent-or-complete-common))
  ;; run gofmt/goimports when saving the file
  (add-hook 'before-save-hook #'gofmt-before-save)

  (defun my-go-mode-hook-fn ()
    (set (make-local-variable 'company-backends) '(company-go))
    (smartparens-mode 1)
    (flycheck-mode 1)
    (setq imenu-generic-expression
          '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
            ("func" "^func *\\(.*\\) {" 1))))

  (add-hook 'go-mode-hook #'my-go-mode-hook-fn))

;; Go/speedbar integration

(eval-after-load 'speedbar
  '(speedbar-add-supported-extension ".go"))

Now, in go buffers you can use M-. to jump to the definition of the identifier at point (use M-, to jump back as for normal tags in Emacs 25.1) and you can also use C-c C-d for a short description of the identifier at point (actually it is constantly displayed in the mode line by enabled Go eldoc support). You can use C-c d for a longer description of the identifier at point.

For this to work you have to

  1. Install from MELPA the following Emacs packages: go-mode, company-go, go-eldoc, and go-guru.

  2. Install Go compiler. Under Debian you install golang-go package (but in Debian 9 Stretch it is 1.7 while in Debian 8 Jessie it is 1.3.3 compared to the current 1.11, so you may consider downloading the current version of Go). Otherwise search for the package for your system or otherwise see Getting started.

  3. Install godef with

    $ go get
  4. Install goimports which can be installed from Debian package or with

    $ go get
  5. Install gocode which can be installed from Debian package gocode or with

    $ go get -u
  6. Install guru with

    $ go get
  7. Add your $GOPATH/bin to your PATH environment variable (or copy the godef, goimports, gocode, and guru executables from $GOPATH/bin to some directory which is in your PATH).

See also Writing Go in Emacs for more info.


(use-package company-jedi

(defun my-python-mode-hook-fn ()
  (set (make-local-variable 'company-backends) '(company-jedi))
  (smartparens-mode 1)
  (local-set-key (kbd "M-.") #'jedi:goto-definition)
  (local-set-key (kbd "M-,") #'jedi:goto-definition-pop-marker)
  (local-set-key "\C-i" #'company-indent-or-complete-common))

(add-hook 'python-mode-hook #'my-python-mode-hook-fn)


(use-package cargo

(use-package racer

(use-package rust-mode
  (setq company-tooltip-align-annotations t
        rust-format-on-save t)
  (add-hook 'rust-mode-hook #'company-mode)
  (add-hook 'rust-mode-hook #'cargo-minor-mode)
  (add-hook 'rust-mode-hook #'racer-mode)
  (add-hook 'racer-mode-hook #'eldoc-mode)
  (:map rust-mode-map
   ("C-i" . company-indent-or-complete-common)))

Language server with Vala support

(use-package lsp-mode
  :commands lsp
  (add-to-list 'lsp-language-id-configuration '(vala-mode . "vala"))
   (make-lsp-client :new-connection (lsp-stdio-connection '("vala-language-server"))
                    :major-modes '(vala-mode)
                    :server-id 'vala-ls)))

(use-package company-lsp

Meson build system

(use-package meson-mode
  (setq meson-indent-basic 4))


(use-package dumb-jump

(defun my-vala-mode-hook-fn ()
  (setq c-basic-offset 4
        tab-width 8
        indent-tabs-mode nil)
  (set (make-local-variable 'company-backends) '(company-lsp))
  (company-mode 1)
  (local-set-key "\C-i" #'company-indent-or-complete-common)

(use-package vala-mode
  (add-hook 'vala-mode-hook #'my-vala-mode-hook-fn))


(defun my-dart-goto ()

(use-package dart-mode
  (let ((path (expand-file-name
    (if (file-accessible-directory-p path)
        (setq dart-sdk-path path)))
  (setq dart-enable-analysis-server t)
  (:map dart-mode-map
   ("M-." . my-dart-goto)
   ("M-/" . dabbrev-expand)
   ("C-i" . company-indent-or-complete-common)
   ("C-M-i" . company-indent-or-complete-common))

  (defun my-dart-mode-hook-fn ()
    (smartparens-mode 1)
    (flycheck-mode 1))

  (add-hook 'dart-mode-hook #'my-dart-mode-hook-fn))


(use-package php-mode

(use-package company-php

(defun my-php-mode-hook-fn()
  (when (require 'company-php nil t)
    (set (make-local-variable 'company-backends)
    (company-mode t)
    (local-set-key (kbd "M-.") #'ac-php-find-symbol-at-point)))

(add-hook 'php-mode-hook #'my-php-mode-hook-fn)


(defun my-setup-tide-mode ()
  (flycheck-mode 1)
  (setq flycheck-check-syntax-automatically '(save mode-enabled))
  (eldoc-mode 1)
  (company-mode 1))

(use-package typescript-mode
  (setq typescript-indent-level 2)
  (:map typescript-mode-map
   ("C-i" . company-indent-or-complete-common)
   ("C-M-i" . company-indent-or-complete-common)))

(use-package tide
  (add-hook 'before-save-hook #'tide-format-before-save)
  (add-hook 'typescript-mode-hook #'my-setup-tide-mode))

(use-package ng2-mode

Other modes

Yasnippet and abbrev mode

(setq-default abbrev-mode 1)

(use-package yasnippet
  (yas-global-mode 1)
  (:map yas-minor-mode-map
        ("C-c & t" . yas-describe-tables)
        ("C-c & &" . org-mark-ring-goto)))

Web mode

(defun my-web-mode-hook-fn()
   ((string= web-mode-engine "php")

(use-package web-mode
  (add-hook 'web-mode-hook #'my-web-mode-hook-fn)
  (add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))
  (:map web-mode-map
        ("C-i" . company-indent-or-complete-common)))


(use-package rainbow-mode

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

Org mode

(use-package org-bullets

(use-package org
  (setq org-default-notes-file "~/org/"
        org-highlight-latex-and-related '(latex)
        org-bullets-bullet-list '("●" "○" "✸" "✿")
        org-ellipsis "…"
        org-catch-invisible-edits 'smart
        gnuplot-program "pyxplot")
  (defun my-org-timer-done ()
    (shell-command "echo Timer timed out; date &"))
  (("C-c a" . org-agenda)
   ("C-c B" . org-iswitchb)
   ("C-c c" . org-capture)
   ("C-c l" . org-store-link))
  (add-hook 'org-timer-done-hook #'my-org-timer-done)
  (add-hook 'org-mode-hook #'org-bullets-mode)
  (require 'ox-beamer))

Search engines

(use-package engine-mode
  (engine-mode t)
  (defengine duckduckgo
    :keybinding "d")
  (defengine google
    :keybinding "g"))

EWW browser

(setq browse-url-browser-function #'eww-browse-url)

(defun my-eww-scale-adjust ()
  "Slightly bigger font but text shorter than text."
  (text-scale-adjust 0)
  (text-scale-adjust 1)
  (other-window 1)
  (sleep-for 1)

Appearance and custom file


Minimalistic look

(setq inhibit-startup-screen t
      frame-title-format (list "[" user-login-name "@" (system-name) "] %b")
      ediff-window-setup-function #'ediff-setup-windows-plain)
(set-scroll-bar-mode 'right)
(menu-bar-mode 0)
(tool-bar-mode 0)

Easy switching between some fonts

(setq my-font-list '("Fantasque Sans Mono" "Go mono" "Inconsolata" "Monoid" "mononoki"))

(defun my-set-frame-font (font-name size &optional frames)
  "Set font to one of the fonts from `my-font-list'
Argument FRAMES has the same meaning as for `set-frame-font'"
   (list (or (helm :prompt "Font name: "
                   :resume 'noresume
                   :sources (helm-build-sync-source "Fonts"
                              :candidates my-font-list)
                   :buffer "*font selection*")
             (signal 'quit nil))
         (read-number "Font size: ")))
   (format "%s:pixelsize=%d:antialias=true:autohint=true" font-name size)
   nil frames))

(global-set-key (kbd "C-c F") #'my-set-frame-font)

Fancy mode line and some themes

(use-package powerline

(use-package nimbus-theme

(use-package leuven-theme

Easy switching between themes

(defun my-helm-themes-after ()
  (set-face-background 'scroll-bar (face-background 'fringe)))

(use-package helm-themes
  (("C-c T" . helm-themes))
  ;; need to update powerline after changing theme
  (advice-add 'helm-themes :after #'my-helm-themes-after))

Toggle between dark and light themes with a key

(use-package solarized-theme

(setq my-dark-theme 'solarized-dark
      my-light-theme 'solarized-light)

(defun my-toggle-theme ()
  "Toggle between dark and light themes"
  (let ((dark-p (custom-theme-enabled-p my-dark-theme)))
    (mapc #'disable-theme custom-enabled-themes)
    (if dark-p
        (load-theme my-light-theme t)
      (load-theme my-dark-theme t)))

(global-set-key (kbd "C-S-<f6>") #'my-toggle-theme)


(defun my-frame-setup-fn (&optional frame)
  (unless (display-graphic-p frame)
    (set-face-background 'default "unspecified-bg" (or frame (selected-frame)))))

(add-hook 'after-make-frame-functions #'my-frame-setup-fn)
(add-hook 'window-setup-hook #'my-frame-setup-fn)

My customization for some used themes

(eval-after-load 'firebelly-theme
    '(font-lock-comment-delimiter-face ((t (:foreground "#505050"))))))

(eval-after-load 'nimbus-theme
    '(region ((t (:background "#505050"))))))

Use separate custom file

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