Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2240 lines (1809 sloc) 57.9 KB

Emacs configuration

Personal Information

(setq user-full-name "Louis Roché"
      user-mail-address "")

Customize settings

Set up the customize file to its own separate file, instead of saving customize

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file)

Sane defaults

Let’s start with some sane defaults, shall we?

Sources for this section include Magnars Sveen, Sacha Chua, Daniel Mai and Better defaults.

;; These functions are useful. Activate them.
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'dired-find-alternate-file 'disabled nil)

;; Answering just 'y' or 'n' will do
(defalias 'yes-or-no-p 'y-or-n-p)

;; UTF-8 please
(setq locale-coding-system 'utf-8) ; pretty
(set-terminal-coding-system 'utf-8) ; pretty
(set-keyboard-coding-system 'utf-8) ; pretty
(set-selection-coding-system 'utf-8) ; please
(prefer-coding-system 'utf-8) ; with sugar on top

(when (display-graphic-p)
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))

;; no tabs
(setq-default indent-tabs-mode nil)

;; Turn off the blinking cursor
(blink-cursor-mode -1)

(setq-default indicate-empty-lines t)

;; delete the region when typing, just like as we expect nowadays.
(delete-selection-mode t)

(column-number-mode t)

(diminish 'visual-line-mode)

(setq uniquify-buffer-name-style 'forward)

;; -i gets alias definitions from .bash_profile
;; (setq shell-command-switch "-ic")

;; Don't beep at me
(setq visible-bell t)

;; Don't load old .elc files when the .el file is newer
(setq load-prefer-newer t)

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

(autoload 'zap-up-to-char "misc"
  "Kill up to, but not including ARGth occurrence of CHAR." t)
(global-set-key (kbd "M-z") 'zap-up-to-char)

(setq ediff-window-setup-function 'ediff-setup-windows-plain)

(setq apropos-do-all t)

(setq mouse-yank-at-point t)


Keep all backup and auto-save files in one directory. Disk space is cheap. Save lots.

(setq backup-directory-alist '(("." . "~/.emacs.d/backups")))
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save-list/" t)))

(setq delete-old-versions -1)
(setq version-control t)
(setq vc-make-backup-files t)


Save point position between sessions

(setq dotfiles-dir (file-name-directory
                    (or (buffer-file-name) load-file-name)))
(use-package saveplace
  (save-place-file (expand-file-name ".places" dotfiles-dir))
  (save-place t)
  (save-place-mode 1)


Auto reload file when there is a change

(global-auto-revert-mode t)
(diminish 'auto-revert-mode)


;; (setq compilation-scroll-output t)
(setq compilation-scroll-output 'first-error)

This package allows the hide the compilation buffer if compilation is successful. But its behavior is hard to predict.

(use-package bury-successful-compilation
  :config (bury-successful-compilation 1))

Bucklescript is made of shit. So of course it prints compilation messages that are not formated correctly. And it doesn’t want to disable colors.

(require 'compile)
;; The messages from -bs-super-errors are not standard
 '("^[ \t]*\\(?:We've found a bug for you!\\)?\\(?:Warning number \\([0-9]+\\)\\)?[\n ]*\
\\([^ ]+\\) +\\([0-9]+\\):\\([0-9]+\\)-?\\(?:\\([0-9]+\\):\\)?\\([0-9]+\\)?$"
   2 (3 . 5) (4 . 6) 1))
(use-package xterm-color
  :ensure t
  :demand t
  :custom (compilation-environment
           '("TERM=xterm-256color"      ; allow colors
             ;; "NINJA_ANSI_FORCED=0"   ; but not from bsb or other ninja users
  (defun my/compilation-color (proc)
    ;; We need to differentiate between compilation-mode buffers
    ;; and running as part of comint (which at this point we assume
    ;; has been configured separately for xterm-color)
    (when (eq (process-filter proc) 'compilation-filter)
      ;; This is a process associated with a compilation-mode buffer.
      ;; We may call `xterm-color-filter' before its own filter function.
       (lambda (proc string)
         (funcall 'compilation-filter proc
                  (xterm-color-filter string))))))
  :hook (compilation-start . my/compilation-color))


Set shell to something not fancy. Not sure it works…

(setenv "SHELL" "/bin/bash")
(setq-default explicit-shell-file-name "/bin/bash")
(setq-default shell-file-name "/bin/bash")

Interlock files

Interlock files are the .#* files created by emacs when a file is edited and has not been saved yet. It is possible to disable them using this snippet.

(setq create-lockfiles nil)


Display Time

When displaying the time with display-time-mode, I don’t care about the load average.

(setq display-time-default-load-average nil)

OS X scrolling

(setq mouse-wheel-scroll-amount (quote (0.01)))


Install packages from github or local files.

(use-package quelpa
  :ensure t
  (quelpa-update-melpa-p nil)
  (quelpa-checkout-melpa-p nil))

   :fetcher github
   :repo "quelpa/quelpa-use-package"))
(require 'quelpa-use-package)


Doom themes

Theme of the doom emacs configuration.

(use-package doom-themes
  :ensure t
  (doom-themes-enable-bold t)   ; if nil, bold is universally disabled
  (doom-themes-enable-italic t) ; if nil, italics is universally disabled
  (doom-themes-visual-bell-config) ; Enable flashing mode-line on errors
  (doom-themes-org-config)         ; Corrects (and improves) org-mode's native fontification.

Solarized theme

Here’s some configuration for bbatsov’s solarized themes.

(use-package solarized-theme
  :defer 10
  (setq solarized-use-variable-pitch nil)
  :ensure t

Monokai theme

(use-package monokai-theme
  :if (window-system)
  :ensure t
  (setq monokai-use-variable-pitch nil))

Zenburn theme

Use zenburn in terminal by default

(use-package zenburn-theme
  :if (not window-system)
  :ensure t)

Zerodark theme

A dark theme for Emacs, inspired from Niflheim and One Dark

(use-package zerodark-theme
  :ensure t)

Convenient theme functions

(defun switch-theme (theme)
  "Disables any currently active themes and loads THEME."
  ;; This interactive call is taken from `load-theme'
    (intern (completing-read "Load custom theme: "
                             (mapc 'symbol-name
  (let ((enabled-themes custom-enabled-themes))
    (mapc #'disable-theme custom-enabled-themes)
    (load-theme theme t)))

(defun disable-active-themes ()
  "Disables any currently active themes listed in `custom-enabled-themes'."
  (mapc #'disable-theme custom-enabled-themes))

Choose theme

Use environment variables to choose theme.

(setq-local my/default-theme 'doom-one-light)
(setq-local my/bad-id-theme 'whiteboard)

(defun my/print-env-theme (kind expected)
  (message "Looking for theme %s `%s' detected from the env..." kind expected))

(let ((count-themes 1)
      (themes-ids (make-hash-table :size 30))
      (themes-names (make-hash-table :size 30 :test #'equal))
      (env-theme-name (getenv "EN"))
      (env-theme-id (getenv "EI")))
  (dolist (theme (custom-available-themes))
    (puthash count-themes theme themes-ids)
    (puthash (symbol-name theme) theme themes-names)
    (setq count-themes (+ 1 count-themes)))
  (when (or my/default-theme env-theme-name env-theme-id)
     (if env-theme-name
           (my/print-env-theme "name" env-theme-name)
           (gethash env-theme-name themes-names my/bad-id-theme))
       (if env-theme-id
             (my/print-env-theme "id" env-theme-id)
             (gethash (string-to-number env-theme-id) themes-ids my/bad-id-theme))
    (message "Loading theme `%s'..." selected-theme)
    (load-theme selected-theme t)))

Solaire mode

solaire-mode is an aesthetic plugin that helps visually distinguish file-visiting windows from other types of windows (like popups or sidebars) by giving them a slightly different – often brighter – background.

(use-package solaire-mode
  :ensure t
  (after-change-major-mode-hook . turn-on-solaire-mode)
  (after-revert-hook . turn-on-solaire-mode)
  (minibuffer-setup-hook . solaire-mode-in-minibuffer)

Auto dim other buffers

(use-package auto-dim-other-buffers
  :ensure t
  :config (auto-dim-other-buffers-mode t))


And here’s how we tell Emacs to use the font we want to use.

 ((member "PragmataPro" (font-family-list))
  (set-face-attribute 'default nil :font "PragmataPro-12"))
 ((member "Ubuntu Mono" (font-family-list))
  (set-face-attribute 'default nil :font "Ubuntu Mono-12"))
 ((member "DejaVu Sans Mono" (font-family-list))
  (set-face-attribute 'default nil :font "DejaVu Sans Mono-12")))

Note: To view all fonts that are available to use, run the following:



Disable hl-line-mode when in a terminal.

(add-hook 'after-change-major-mode-hook
          '(lambda () (hl-line-mode (if (equal major-mode 'term-mode) 0 1))))

Mac customizations

There are configurations to make when running Emacs on macOS (hence the “darwin” system-type check).

(when (string-equal system-type "darwin")
  ;; delete files by moving them to the trash
  (setq delete-by-moving-to-trash t)
  (setq trash-directory "~/.Trash")

  ;; Don't make new frames when opening a new file with Emacs
  (setq ns-pop-up-frames nil)

  ;; set the Fn key as the hyper key
  (setq ns-function-modifier 'hyper)

  ;; Use Command-` to switch between Emacs windows (not frames)
  (bind-key "s-`" 'other-window)

  ;; Use Command-Shift-` to switch Emacs frames in reverse
  (bind-key "s-~" (lambda() () (interactive) (other-window -1)))

  ;; Because of the keybindings above, set one for `other-frame'
  (bind-key "s-1" 'other-frame)

  ;; Fullscreen!
  (setq ns-use-native-fullscreen nil) ; Not Lion style
  (bind-key "<s-return>" 'toggle-frame-fullscreen)

  ;; buffer switching
  (bind-key "s-{" 'previous-buffer)
  (bind-key "s-}" 'next-buffer)

  ;; Compiling
  (bind-key "H-c" 'compile)
  (bind-key "H-r" 'recompile)
  (bind-key "H-s" (defun save-and-recompile () (interactive) (save-buffer) (recompile)))

  ;; disable the key that minimizes emacs to the dock because I don't
  ;; minimize my windows
  ;; (global-unset-key (kbd "C-z"))

  ;; Not going to use these commands
  (put 'ns-print-buffer 'disabled t)
  (put 'suspend-frame 'disabled t))

exec-path-from-shell makes the command-line path with Emacs’s shell match the same one on macOS.

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :ensure t

List buffers

ibuffer is the improved version of list-buffers.

;; make ibuffer the default buffer lister.
(defalias 'list-buffers 'ibuffer)


(add-hook 'dired-mode-hook 'auto-revert-mode)

;; Also auto refresh dired, but be quiet about it
(setq global-auto-revert-non-file-buffers t)
(setq auto-revert-verbose nil)


(use-package projectile
  :ensure t
  :init (setq projectile-keymap-prefix (kbd "C-c p"))
  (projectile-completion-system 'helm)
  (projectile-enable-caching t)
  (projectile-switch-project-action #'helm-projectile-find-file)
(use-package projectile-ripgrep
  :ensure t)


This direnv package works per file. It means that it will edit the env each time the focus change from one file to another. Which also mean that if a function like merlin-locate makes one jump into a file not under a .envrc file the env will be lost in this file.

(use-package direnv
  :ensure t


(defun next-flymake-error ()
  (let ((err-buf nil))
    (condition-case err
        (setq err-buf (next-error-find-buffer))
    (if err-buf
        (let ((err (get-char-property (point) 'help-echo)))
          (when err
            (message err)))))))

(use-package flymake
  :ensure t
  ("C-c <f8>" . flymake-mode)
  ("C-c <f7>" . next-flymake-error)
  ("C-c <f6>"  . flymake-goto-next-error)
  ("<f7>" . next-error))


eldoc-mode provides information about the symbol at point in the echo area. Usually it is the signature of a function.

(use-package eldoc
  :ensure t
  :hook (eval-expression-minibuffer-setup-hook . eldoc-mode))

Display eldoc message in an overlay instead of echo area.

(use-package eldoc-overlay
  :ensure t)


I use company mode as a completion backend

(use-package company
  :ensure t
  (company-quickhelp-delay 0)
  (company-idle-delay nil)
  (company-tooltip-align-annotations t)
  ((prog-mode utop-mode) . company-mode)
  (company-quickhelp-mode 1)
  ("M-o" . company-complete))

Popup for documentation or help

(use-package company-quickhelp
  :ensure t
  :bind (:map company-active-map
              ("M-h" . company-quickhelp-manual-begin))


Rainbow delimiters, to have a different color for each level of ([{.

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

Smartparens aims to be a replacement for electric-pair-mode, paren, autopair, paredit and other modes dedicated to balancing of delimiters. It is supposed to be more powerful and work in every mode.

(use-package smartparens
  :ensure t
  (defun my/enable-smartparens-prog ()
    "Enable all smartparens mode."
    (require 'smartparens-config))
  (defun my/enable-smartparens-text ()
    "Enable minimal smartparens mode without any keybinding."
  (defun sp-kill-region-or-word ()
    "Call `sp-kill-region' or `sp-backward-kill-word' depending
on whether or not a region is selected."
    (if (and transient-mark-mode mark-active)
        (sp-kill-region (point) (mark))
      (sp-backward-kill-word 1)))
  :custom-face (sp-show-pair-match-content-face ((t (:inherit sp-show-pair-match-face))))
  :bind (:map smartparens-strict-mode-map ("C-w" . sp-kill-region-or-word)))

show-paren-mode is smarter than smartparens to highlight between keywords that are detected by SMIE.

(use-package paren
  :ensure t
  (show-paren-delay 0)
  (blink-matching-paren t)
  (blink-matching-paren-on-screen t)
  (show-paren-style 'expression)
  (blink-matching-paren-dont-ignore-comments t)
  :config (show-paren-mode))

It is a global minor mode. To enable it only for prog modes, this trick can be used.

(show-paren-mode)          ;; activate the needed timer
(setq show-paren-mode nil) ;; The timer will do nothing if this is nil

(defun show-paren-local-mode ()
  (make-local-variable 'show-paren-mode) ;; The value of shom-paren-mode will be local to this buffer.
  (setq show-paren-mode t))

(add-hook 'prog-mode-hook #'show-paren-local-mode)

Autopair is now replaced with electric-pair-mode.

(use-package elec-pair
  :ensure t
  :config (electric-pair-mode))

When smartparens is not used, the classical paredit can be a good backup.

(use-package paredit
  (defun paredit-kill-region-or-word ()
    "Call `paredit-kill-region' or `paredit-backward-kill-word' depending
on whether or not a region is selected."
    (if (and transient-mark-mode mark-active)
        (paredit-kill-region (point) (mark))
  :hook (emacs-lisp-mode . paredit-mode))

For some modes like tuareg, I prefer to not use smartparens because it doesn’t handle the pair of keywords well. Paredit doesn’t handle them either, but it doesn’t affect the default navigation function. Those functions are working by default because tuareg is using a SMIE based parser. Smartparens has its own version of the navigation functions that are not good for ocaml yet.

(defun my/classic-parens ()
  "Disable smartparens and replace it with old school modes.

show-paren-mode + paredit + electric-pair-local-mode"
  (smartparens-strict-mode -1)
  (smartparens-mode -1)
  (show-smartparens-mode -1)
  (show-paren-mode 1)

Programming languages



Util function to select where to load merlin from.

(defun shell-cmd (cmd)
  "Returns the stdout output of a shell command or nil if the command returned
     an error"
  (car (ignore-errors (apply 'process-lines (split-string cmd)))))

(setq opam-p (shell-cmd "which opam"))
(setq reason-p (shell-cmd "which refmt"))
(setq utop-p (shell-cmd "which utop"))
(defun reason-cmd-where (cmd)
  (let ((where (shell-cmd cmd)))
    (if (string-equal "unknown flag ----where" where)
(defun ocamlmerlin-where ()
  (reason-cmd-where "ocamlmerlin ----where"))
(defun refmt-where ()
  (reason-cmd-where "refmt ----where"))
(defun utop-where ()
  (reason-cmd-where "utop ----where"))

Load opam

Setup environment variables using opam. To be used if the env is not configured before to launch emacs.

(if opam-p
    (dolist (var (car (read-from-string (shell-command-to-string "opam config env --sexp"))))
      (setenv (car var) (cadr var))))

Add opam libs.

(if opam-p
    (let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
      (when (and opam-share (file-directory-p opam-share))
        (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share)))))

ocp tools

Require ocp stuff first because of conflicts between shortcuts. It is installed from opam, ensure is not required.

(use-package ocp-indent :if (shell-cmd "which ocp-indent"))
(use-package ocp-index :if (shell-cmd "which ocp-index"))

caml, reasonml and tuareg modes

If no face is setup for merlin-type-face, it will be set to caml-types-expr-face which is provided by caml.

(use-package caml
  :ensure t)

The comment-continue variable is set so that multi lines comments are not prefixed by *. The length of comment-continue should be the same as the length of comment-start to preserve indentation.

(use-package tuareg
  :ensure t
  :bind (:map tuareg-mode-map
              ("M-;" . comment-dwim) ; restore default comment command
              ("C-w" . paredit-kill-region-or-word))
  :mode (("\\.ml[ily]?$" . tuareg-mode)
         ("\\.topml$" . tuareg-mode)
         ("\\.ocamlinit$" . tuareg-mode)
         ("dune" . tuareg-dune-mode))
  (unbind-key "<backspace>" tuareg-mode-map)
  (load "tuareg-site-file")
  (defun my/setup-tuareg ()
    (setq-local comment-continue "  ")
    (setq-local comment-style 'extra-line))
  (tuareg-mode . my/setup-tuareg)
  (tuareg-dune-mode . paredit-mode))

When using reason-mode, we want to load merlin from node_modules if it is available there. Otherwise it will come from opam.

(use-package reason-mode
  :if reason-p
  :ensure t
  :bind (:map reason-mode-map
              ("C-M-\\" . refmt))
  :custom (refmt-width-mode 'fill)
  (let* ((refmt-reason (refmt-where))
         (utop-reason (utop-where))
         (utop-base-dir (when utop-reason
                          (replace-regexp-in-string "bin/utop$" "" utop-reason)))
         (merlin-reason (ocamlmerlin-where))
         (merlin-base-dir (when merlin-reason
                            (replace-regexp-in-string "bin/ocamlmerlin$" "" merlin-reason))))
    ;; Add npm merlin.el to the emacs load path and tell emacs where to find ocamlmerlin
    (when merlin-base-dir
      (message "loading merlin from %s" (concat merlin-base-dir "share/emacs/site-lisp/"))
      (add-to-list 'load-path (concat merlin-base-dir "share/emacs/site-lisp/")))
    (when utop-base-dir
      (message "loading utop from %s" (concat utop-base-dir "share/emacs/site-lisp/"))
      (add-to-list 'load-path (concat utop-base-dir "share/emacs/site-lisp/")))
    (when refmt-reason
      (setq refmt-command refmt-reason))))


Configure merlin. Magical autocompletion and IDE features.

(use-package merlin
  (merlin-completion-with-doc t)
  (merlin-error-check-then-move nil)
  (merlin-command "ocamlmerlin")
  (merlin-type-face ((t (:background "#a0bcf8"))))
  :bind (:map merlin-mode-map
              ("M-." . merlin-locate)
              ("M-," . merlin-pop-stack)
              ("M-?" . merlin-occurrences)
              ("C-c m j" . merlin-jump)
              ("C-c m i" . merlin-locate-ident)
              ("C-c m e" . merlin-iedit-occurrences)
              ("C-c m d" . merlin-document))
  ;; Start merlin on ml files
  ((reason-mode tuareg-mode) . merlin-mode))


Enable automatic display of type and documentation of value at point after a small delay. Provides a merlin backend to eldoc-mode. Works for OCaml and Reason.

(quelpa '(merlin-eldoc :repo "Khady/merlin-eldoc" :fetcher github))
(add-hook 'tuareg-mode-hook #'merlin-eldoc-setup)
(add-hook 'reason-mode-hook #'merlin-eldoc-setup)
(use-package merlin-eldoc
  ;; :load-path "/home/louis/Code/github/merlin-eldoc"
  :ensure t
  (eldoc-echo-area-use-multiline-p t)
  (merlin-eldoc-max-lines 8)
  (merlin-eldoc-max-lines-function-arguments 1)
  (merlin-eldoc-type-verbosity 'min)
  (merlin-eldoc-function-arguments nil)
  (merlin-eldoc-doc nil)
  :bind (:map merlin-mode-map
              ("C-c m p" . merlin-eldoc-jump-to-prev-occurrence)
              ("C-c m n" . merlin-eldoc-jump-to-next-occurrence))
  :hook ((tuareg-mode reason-mode) . merlin-eldoc-setup))


(use-package utop
  :if utop-p
  :diminish utop-minor-mode
  :custom (utop-edit-command nil)
  (defun my/setup-utop (name)
    (setq my/utop-name name)
    (setq utop-command (format "%s -emacs" my/utop-name))
    (setq utop-prompt
          (lambda ()
            (let ((prompt (format "%s[%d]> " my/utop-name utop-command-number)))
              (add-text-properties 0 (length prompt) '(face utop-prompt) prompt)
  (defun my/setup-utop-tuareg ()
    (my/setup-utop "utop"))
  (defun my/setup-utop-reason ()
    (my/setup-utop "rtop"))
  (tuareg-mode . my/setup-utop-tuareg)
  (reason-mode . my/setup-utop-reason))


(use-package dune-flymake)
(use-package dune)


It is possible to use the ocaml-language-server, which is a lsp server for ocaml and reason, rather than calling merlin directly. It also comes with some nice UI if lsp-ui is installed.

I find it not as complete or neat than using merlin + merlin-eldoc for now, so this is not enabled by default.

(use-package lsp-ocaml
  :ensure t
  :hook (tuareg-mode . lsp-ocaml-enable))

ocaml-language-server has to be in the path. It can be installed with yarn global. It will be in yarn global bin.

yarn global add ocaml-language-server


(use-package tide
  :ensure t
  :bind (:map tide-mode-map
              ("M-?" . tide-references)
              ("C-c C-e" . tide-rename-symbol)
              ("C-c C-x" . flycheck-next-error))
  (defun my/setup-tide-mode ()
    (flycheck-mode +1)
    (setq flycheck-check-syntax-automatically '(save mode-enabled idle-change))
    ;; (flycheck-add-next-checker 'typescript-tide '(t . typescript-tslint) 'append)
    (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))
  ((typescript-mode js2-mode) . my/setup-tide-mode))
  ;; (add-hook 'typescript-mode-hook #'setup-tide-mode)
  ;; (add-hook 'js2-mode-hook #'setup-tide-mode))


(use-package indium
  :ensure t)
(use-package js2-mode
  :ensure t
  :mode "\\.js\\'"
  (js-indent-level 2)
(use-package web-mode
  :ensure t)


(use-package json-mode
  :ensure t)
(use-package json-reformat
  :ensure t
  (json-reformat:indent-width 1)

Based on hierarchy.el:

(use-package tree-mode
  :ensure t)

(use-package json-navigator
  :ensure t)


Python has a big jedi package to have completion and many other nice things.

(use-package jedi
  :ensure t)


(use-package rust-mode
  :ensure t
  :defer t)


(use-package markdown-mode
  :ensure t)


(use-package protobuf-mode
  :ensure t)


(use-package puppet-mode
  :ensure t)


(use-package yaml-mode
  :ensure t)


(use-package nginx-mode
  :ensure t)


(use-package macrostep
  :bind ("C-c e m" . macrostep-expand))
(use-package elisp-def
  :ensure t
  :hook (emacs-lisp-mode . elisp-def-mode))

package-lint to check files that are emacs libraries

(use-package package-lint
  :ensure t)


I use fish as a shell

(use-package fish-completion
  :ensure t
  :config (when (and (executable-find "fish")
                     (require 'fish-completion nil t))
(use-package fish-mode
  :ensure t)


(use-package whitespace
  :ensure t
  :diminish global-whitespace-mode
  (whitespace-style '(face empty tabs lines-tail trailing))
  (whitespace-line-column 160)
  (global-whitespace-mode t)


Same indentation rules everywhere.

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


(use-package aggressive-indent
  :ensure t
  (global-aggressive-indent-mode 1)
  (dolist (mode '(tuareg-mode reason-mode))
    (add-to-list 'aggressive-indent-excluded-modes mode)))


I will use it, one day… For now it is used to jump from opening to closing keywords in ocaml.

(use-package evil
  :ensure t)

When jumping between sexp does not work, it might be useful to use this evil-matchit package. It contains some bugs for ocaml.

(use-package evil-matchit
  :ensure t
  :config (global-evil-matchit-mode 1)
  :bind ("C-%" . evilmi-jump-items))


Number of columns in a git commit message

(setq git-commit-summary-max-length 72)


A great interface for git projects. It’s much more pleasant to use than the git interface on the command line. Use an easy keybinding to access magit.

(use-package magit
  :ensure t
  :custom (magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1))


(use-package magithub
  :after magit
  :ensure t
  ; (add-to-list 'magithub-github-hosts "")
  (magithub-feature-autoinject t))

git links

For magit buffers

(use-package orgit
  :ensure t)

All git links

(use-package org-git-link)

Git gutter

git-gutter is explicitely installed because it can’t be diminished from git-gutter-fringe.

(use-package git-gutter
  :ensure t

(use-package git-gutter-fringe
  :ensure t
  (git-gutter-fr:side 'right-fringe)
  (set-face-foreground 'git-gutter-fr:modified "yellow")
  (set-face-foreground 'git-gutter-fr:added    "blue")
  (set-face-foreground 'git-gutter-fr:deleted  "white")



There are plenty of alternatives. This one seems to work, so why bother with other packages?

(use-package git-link
  :ensure t
  :custom (git-link-use-commit 't)
  :config (add-to-list 'git-link-remote-alist
               '("git\\.ahrefs\\.com" git-link-github)))



(use-package monky
  :ensure t)


(use-package ahg
  :ensure t)


(use-package helm
  :ensure t
  :diminish helm-mode
  (helm-mode t)
  (helm-mode-fuzzy-match t)
  (helm-completion-in-region-fuzzy-match t)
  (helm-M-x-fuzzy-match t)
  (helm-recentf-fuzzy-match t)
  (helm-ff-fuzzy-matching t)
  (helm-buffers-fuzzy-matching t)
  :bind (("C-c h" . helm-command-prefix)
         ("C-x b" . helm-mini)
         ("C-`" . helm-resume)
         ("M-x" . helm-M-x)
         ("M-y" . helm-show-kill-ring)
         ("C-x C-f" . helm-find-files)))
(use-package helm-projectile
  :ensure t
(use-package helm-swoop
  :ensure t
  ("C-S-s" . helm-swoop)
  ("M-i" . helm-multi-swoop-projectile)
  ("M-I" . helm-swoop-back-to-last-point))
(use-package helm-ag
  :ensure t)
(use-package org-projectile-helm
  :ensure t)

Org mode


Although Org mode ships with Emacs, the latest version can be installed externally. The configuration here follows the Org mode ELPA installation instructions.

(use-package org
  :ensure org-plus-contrib
  :bind (("C-c l" . org-store-link)
         ("C-c c" . org-capture)
         ("C-c a" . org-agenda)))

On Org mode version 9 I wasn’t able to execute source blocks out of the box. Others have ran into the same issue too. The solution is to remove the .elc files from the package directory:

rm ${ORG_DIR}/*.elc

Org extentions installation

ob-http: curl queries from org files.

(use-package ob-http
  :ensure t)

ob-restclient: an alternative

(use-package ob-restclient
  :ensure t)

It comes with a completion backend for company

(use-package company-restclient
  :ensure t
  :config (add-to-list 'company-backends 'company-restclient))

org-projectile: org todo per project

(use-package org-projectile
  :bind (("C-c n p" . org-projectile-project-todo-completing-read))
    (setq org-projectile-projects-file
    (setq org-agenda-files (append org-agenda-files (org-projectile-todo-files)))
    (push (org-projectile-project-todo-entry) org-capture-templates))
  :ensure t)

Use htmlize to export org to html.

(use-package htmlize
  :ensure t)
(use-package ox-epub
  :ensure t)

Org agenda

Load all notes from the ~/Notes/*.org files.

(setq org-agenda-files (file-expand-wildcards "~/Notes/*.org"))

Learned about this delq and mapcar trick from Sacha Chua’s config.

(setq org-agenda-files
      (delq nil
            (mapcar (lambda (x) (and (file-exists-p x) x))

Easy standup at work.

(setq org-enforce-todo-dependencies t)
(setq org-log-done 'time)

(defun my/date-n-days-ago (n)
  (format-time-string "[%Y-%m-%d]" (time-subtract (current-time) (days-to-time n))))

(setq org-agenda-custom-commands
      '(("w" . "Standup")
        ("wd" "Week day"
         ((tags (concat "+TODO=\"DONE\"" "+CLOSED>=\"" (my/date-n-days-ago 1) "\""))
          (todo "IN-PROGRESS")
          (tags-todo "+TODO=\"TODO\"+@URGENT")
          (tags-todo (concat "+DEADLINE<=\"" (my/date-n-days-ago 0) "\""))
          (tags-todo (concat "+DEADLINE>\"" (my/date-n-days-ago 0) "\""))
          (tags-todo "@inbox")
        ("we" "Weekend"
         ((tags (concat "+TODO=\"DONE\"+CLOSED>=\"" (my/date-n-days-ago 3) "\""))
          (todo "IN-PROGRESS")
          (tags-todo "+@URGENT+TODO=\"TODO\"")
          (tags-todo (concat "+DEADLINE<=\"" (my/date-n-days-ago 0) "\""))
          (tags-todo (concat "+DEADLINE>\"" (my/date-n-days-ago 0) "\""))
          (tags-todo "@inbox")
         "last 3 days")
        ("1" "ongoing work" tags-tree
          "+TODO=\"DONE\"+CLOSED>=\"" (my/date-n-days-ago 1) "\""

Some config

(setq org-refile-targets (quote (("" :tag . "done")
                                 ("" :regexp . "Tasks"))))
(setq org-outline-path-complete-in-steps nil)         ; Refile in a single go
(setq org-refile-use-outline-path t)                  ; Show full paths for refiling
(setq org-agenda-tags-column -100) ; take advantage of the screen width

(setq org-capture-templates '(("a" "Todo [inbox]" entry
                               (file+headline "~/Notes/" "Inbox")
                               "** TODO %i%?")
                              ("t" "Deadline [inbox]" entry
                               (file+headline "~/Notes/" "Inbox")
                               "** TODO %i%? %^g\n   DEADLINE:%^{Deadline}t")

;; Place tags close to the right-hand side of the window
(add-hook 'org-finalize-agenda-hook 'place-agenda-tags)
(defun place-agenda-tags ()
  "Put the agenda tags by the right border of the agenda window."
  (setq org-agenda-tags-column (- 4 (window-width)))

Org setup

Speed commands are a nice and quick way to perform certain actions while at the beginning of a heading. It’s not activated by default.

See the doc for speed keys by checking out the documentation for speed keys in Org mode.

(setq org-use-speed-commands t)
(setq org-image-actual-width 550)
(setq org-highlight-latex-and-related '(latex script entities))

Disable validate link at the end of html export

(setq org-html-validation-link nil)
(setq org-startup-folded 'fold)
(setq org-html-htmlize-output-type 'css)
(setq org-adapt-indentation nil)

Org babel languages

 '((python . t)
   (C . t)
   (calc . t)
   (latex . t)
   (java . t)
   (ruby . t)
   (lisp . t)
   (scheme . t)
   (shell . t)
   (sqlite . t)
   (js . t)
   (http . t)
   (restclient . t)
   (elasticsearch . t)))

It is possible to skip the confimation evaluation for a set of languages. Not enabled.

(defun my-org-confirm-babel-evaluate (lang body)
  "Do not confirm evaluation for these languages."
  (not (or (string= lang "C")
           (string= lang "java")
           (string= lang "python")
           (string= lang "emacs-lisp")
           (string= lang "sqlite"))))
(setq org-confirm-babel-evaluate 'my-org-confirm-babel-evaluate)

Org babel/source blocks

I like to have source blocks properly syntax highlighted and with the editing popup window staying within the same window so all the windows don’t jump around. Also, having the top and bottom trailing lines in the block is a waste of space, so we can remove them.

I noticed that fontification doesn’t work with markdown mode when the block is indented after editing it in the org src buffer—the leading #s for headers don’t get fontified properly because they appear as Org comments. Setting org-src-preserve-indentation makes things consistent as it doesn’t pad source blocks with leading spaces.

(setq org-src-fontify-natively t
      org-src-window-setup 'current-window
      org-src-strip-leading-and-trailing-blank-lines t
      org-src-preserve-indentation t
      org-src-tab-acts-natively t)

Org publish

(setq org-publish-project-alist
         :base-directory "~/Notes/posts"
         :base-extension "org"
         :publishing-directory "~/Notes/publish"
         :publishing-function org-html-publish-to-html
         :htmlized-source t
         :html-doctype "html5"
         :html-head-include-default-style nil
         :html-html5-fancy t
         :html-htmlized-css-url "/org.css" ; from
         :section-numbers nil
         :auto-sitemap t
         "<a class=\"author\" href=\"\">%a</a> <span class=\"date\">%d</span>
          <span class=\"creator\">%c</span>"
         :html-link-home "archive.html"
         :sitemap-filename ""
         :sitemap-title "Archive"
         :sitemap-sort-files anti-chronologically
         :sitemap-style list
         :with-statistics-cookies nil)
         :base-directory "~/Notes/posts/"
         :base-extension "css"
         :publishing-directory "~/Notes/publish/"
         :recursive t
         :publishing-function org-publish-attachment)
        ("posts" :components ("posts-org" "posts-static"))))
(add-to-list 'org-structure-template-alist
             '("b" "#+TITLE: ?
#+DATE: ?


(use-package iedit
  :ensure t)

Expand region

Expand region increases the selected region by semantic units. Just keep pressing the key until it selects what you want.

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

Multiple cursors

(use-package multiple-cursors
  :ensure t
  ("C-<" . mc/mark-previous-like-this)
  ("C->" . mc/mark-next-like-this)


It’s seems to be a challenge to display line numbers. For performances reasons, people say to use nlinum. But I can’t do customization as with the normal linum mode.

(use-package linum
  (linum-format " %2d") ; numbers in the line gutter don't touch the left
  (defcustom linum-disabled-modes-list '(eshell-mode wl-summary-mode compilation-mode org-mode text-mode dired-mode doc-view-mode)
    "* List of modes disabled when global linum mode is on"
    :type '(repeat (sexp :tag "Major mode"))
    :tag " Major modes where linum is disabled: "
    :group 'linum
  (defcustom linum-disable-starred-buffers 't
    "* Disable buffers that have stars in them like *Gnu Emacs*"
    :type 'boolean
    :group 'linum)

  (defun linum-on ()
    "* When linum is running globally, disable line number in modes defined in `linum-disabled-modes-list'. Changed by linum-off. Also turns off numbering in starred modes like *scratch*"

    (unless (or (minibufferp) (member major-mode linum-disabled-modes-list)
                (and linum-disable-starred-buffers (string-match "*" (buffer-name)))
      (linum-mode 1)))
(use-package nlinum
  :ensure t)
(use-package nlinum-relative
  :ensure t)


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


I never took time to really use this…

(use-package visual-regexp
  :ensure t)
(use-package visual-regexp-steroids
  :ensure t)

Undo Tree

(use-package undo-tree
  :ensure t

Big files/lines

(use-package vlf
  :ensure t)


See Emacs Rocks! Episode 15 to learn how restclient can help out with testing APIs from within Emacs. The HTTP calls you make in the buffer aren’t constrainted within Emacs; there’s the restclient-copy-curl-command to get the equivalent curl call string to keep things portable.

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


It is convenient to be able to query elasticsearch from emacs. It can be done from restclient or ob-html. But es-mode comes with some nice things like tables when using ob-elasticsearch.

(use-package es-mode
  :ensure t)


Convenient package to create *scratch* buffers that are based on the current buffer’s major mode. This is more convienent than manually creating a buffer to do some scratch work or reusing the initial *scratch* buffer.

(use-package scratch
  :ensure t
  :commands scratch)


(setq-default gdb-many-windows t)

Frame/windows management


Keep an history of window positions and jump back to any previous configuration.

(use-package winner
  :ensure t


Convenient keybindings to resize windows.

(bind-key "M-S-C-<left>" 'shrink-window-horizontally)
(bind-key "M-S-C-<right>" 'enlarge-window-horizontally)
(bind-key "M-S-C-<down>" 'shrink-window)
(bind-key "M-S-C-<up>" 'enlarge-window)


Bind arrow keys to move between windows:

(bind-key "M-<up>" 'windmove-up)
(bind-key "M-<down>" 'windmove-down)
(bind-key "M-<right>" 'windmove-right)
(bind-key "M-<left>" 'windmove-left)

It could be done like this too:

(use-package windmove
  (windmove-default-keybindings 'ctrl))

Combine windmove with framemove

(use-package framemove
  :quelpa ((framemove :fetcher github :repo "emacsmirror/framemove"))
  :custom (framemove-hook-into-windmove t))


Whenever I split windows, I usually do so and also switch to the other window as well, so might as well rebind the splitting key bindings to do just that to reduce the repetition.

(defun vsplit-other-window ()
  "Splits the window vertically and switches to that window."
  (other-window 1 nil))
(defun hsplit-other-window ()
  "Splits the window horizontally and switches to that window."
  (other-window 1 nil))

(bind-key "C-x 2" 'vsplit-other-window)
(bind-key "C-x 3" 'hsplit-other-window)

Dedicated windows.

;; Toggle window dedication
(defun toggle-window-dedicated ()
  "Toggle whether the current active window is dedicated or not"
   (if (let (window (get-buffer-window (current-buffer)))
         (set-window-dedicated-p window
                                 (not (window-dedicated-p window))))
       "Window '%s' is dedicated"
     "Window '%s' is normal")

(define-minor-mode sticky-buffer-mode
  "Make the current window always display this buffer."
  nil " sticky" nil
  (set-window-dedicated-p (selected-window) sticky-buffer-mode))

(bind-key "C-c C-'" 'toggle-window-dedicated)

Ace window

(use-package ace-window
  :ensure t
  :bind ("M-`" . ace-window))


(use-package tramp)


(defun close-all-buffers ()
  (mapc 'kill-buffer (buffer-list)))

(defun close-code-buffers ()
  (mapc (lambda (b)
          (unless (string-match "*" (buffer-name b))
            (kill-buffer b)
          ) (buffer-list)))

(defun create-ocaml-buffers ()

  (setq total-window-size (window-width (frame-selected-window)))
  (setq total-window-height (window-height (frame-selected-window)))

  (other-window 1)
  (switch-to-buffer (get-buffer-create "*merlin-types*"))
  (setq second-window-height (window-height (frame-selected-window)))
  (setq expected-window-height (* 0.10 total-window-height))
  (setq expand-window-height (- expected-window-height second-window-height))
  (enlarge-window (round expand-window-height))

  (other-window 1)
  (switch-to-buffer (get-buffer-create "*merlin-occurrences*"))
  (setq second-window-size (window-width (frame-selected-window)))
  (setq expected-window-size (* 0.65 total-window-size))
  (setq expand-window-size (- expected-window-size second-window-size))
  (enlarge-window-horizontally (round expand-window-size))

  (other-window 1)
  (switch-to-buffer (get-buffer-create "*compilation*"))

  (other-window 1)

(require 'term)
(defun visit-ansi-term ()
  "If the current buffer is:
     1) a running ansi-term named *ansi-term*, rename it.
     2) a stopped ansi-term, kill it and create a new one.
     3) a non ansi-term, go to an already running ansi-term
        or start a new one while killing a defunt one"
  (let ((is-term (string= "term-mode" major-mode))
        (is-running (term-check-proc (buffer-name)))
        (term-cmd "/usr/bin/fish")
        (anon-term (get-buffer "*ansi-term*")))
    (if is-term
        (if is-running
            (if (string= "*ansi-term*" (buffer-name))
                (call-interactively 'rename-buffer)
              (if anon-term
                  (switch-to-buffer "*ansi-term*")
                (ansi-term term-cmd)))
          (kill-buffer (buffer-name))
          (ansi-term term-cmd))
      (if anon-term
          (if (term-check-proc "*ansi-term*")
              (switch-to-buffer "*ansi-term*")
            (kill-buffer "*ansi-term*")
            (ansi-term term-cmd))
        (ansi-term term-cmd)))))

(defun kill-region-or-word ()
  "Call `kill-region' or `backward-kill-word' depending on
whether or not a region is selected."
  (if (and transient-mark-mode mark-active)
      (kill-region (point) (mark))
    (backward-kill-word 1)))
(global-set-key "\C-w" 'kill-region-or-word)

(defun buffer-same-mode (change-buffer-fun)
  (let ((current-mode major-mode)
        (next-mode nil))
    (while (not (eq next-mode current-mode))
      (funcall change-buffer-fun)
      (setq next-mode major-mode))))

(defun previous-buffer-same-mode ()
  (buffer-same-mode #'previous-buffer))

(defun next-buffer-same-mode ()
  (buffer-same-mode #'next-buffer))

;; Remove "<clone>" from a name if present.
(defun get-clone-original-name (name)
  (let ((clone "<clone>"))
         (clen (length clone))
         (nlen (length name))
           (>= nlen clen)
           (string= clone (substring name (- clen) nil)))
          (substring name 0 (- nlen clen))

;; F12: clone the current buffer
;; If a clone already exists, use it.
 (lambda () (interactive)
   ;; Get the name of the buffer and its clone.
   (let ((original-name (get-clone-original-name (buffer-name))))
     (let ((clone-name (concat original-name "<clone>")))
       ;; Remember whether the current buffer is the clone.
       (let ((was-clone (string= (buffer-name) clone-name)))
             (not was-clone)
             ;; Kill the current clone to avoid having two of them and to reset
             ;; its position to the current position.
                   (get-buffer clone-name)
                   (kill-buffer clone-name))
               (make-indirect-buffer original-name clone-name 1)))
         (switch-to-buffer clone-name)

;; Alt-F12: clone the current buffer in a window on the right.
;; If a clone already exists, use it.
 [(meta f12)]
 (lambda () (interactive)
   ;; Get the name of the buffer and its clone.
   (let ((original-name (get-clone-original-name (buffer-name))))
     (let ((clone-name (concat original-name "<clone>")))
       ;; Remember whether the current buffer is the clone.
       (let ((was-clone (string= (buffer-name) clone-name)))
         ;; Split and set the left buffer.
         (switch-to-buffer original-name)
         (other-window 1)
             (not was-clone)
             ;; Kill the current clone to avoid having two of them and to reset
             ;; its position to the current position.
                   (get-buffer clone-name)
                   (kill-buffer clone-name))
               (make-indirect-buffer original-name clone-name 1)))
         (switch-to-buffer clone-name)
         ;; Go back to the left.
         (other-window 1))))))

;; F11: switch between clone and original
 (lambda () (interactive)
   ;; Get the name of the buffer and its clone.
   (let ((original-name (get-clone-original-name (buffer-name))))
     (let ((clone-name (concat original-name "<clone>")))
       ;; Test whether the current buffer is the clone.
       (if (string= (buffer-name) clone-name)
           (switch-to-buffer original-name)
         (switch-to-buffer clone-name))))))

;; Insert \n(********)\n(* Title *)\n(********)\n
(defun insert-ocaml-section-string (title)
  "Insert a commented OCaml section title at point"
  (interactive "*sSection title: ")
         "" title)))
     "***************************************)\n(* ")
    (insert (make-string (- 37 (/ (length title) 2)) ? ))
    (insert title)
       (- 37 (/ (length title) 2))
       (% (length title) 2))
      ? ))
     " *)\n(***************************************"

;; Surround region by \n(********)\n(* Title *)\n(********)\n
(defun insert-ocaml-section-region (start end)
  "Insert a commented OCaml section title around region"
  (interactive "*r")
  (let ((string (buffer-substring start end)))
    (delete-region start end)
    (insert-ocaml-section-string string)))

;; Insert ##### Title #####
(defun insert-upl-section-string (title)
  "Insert a commented UPL section title at point"
  (interactive "*sSection title: ")
         "\\(\n\\|#+ +\\| +#+\\)"
         "" title)))
    (insert (make-string (- 39 (/ (length title) 2)) 35 ))
    (insert " ")
    (insert title)
    (insert " ")
       (- 39 (/ (length title) 2))
       (% (length title) 2))
      35 ))

;; Surround region by ##### ... #####
(defun insert-upl-section-region (start end)
  "Insert a commented UPL section title around region"
  (interactive "*r")
  (let ((string (buffer-substring start end)))
    (delete-region start end)
    (insert-upl-section-string string)))

(defun insert-section-region (start end)
  "Insert a commented section title around region"
  (interactive "*r")
  (let ((string (buffer-substring start end)))
    (if (derived-mode-p 'tuareg-mode)
        (insert-ocaml-section-region start end)
      (insert-upl-section-region start end))

;; Get the contents of the current line
;; (let (p1 p2 myLine)
;;   (setq p1 (line-beginning-position) )
;;   (setq p2 (line-end-position) )
;;   (setq myLine (buffer-substring-no-properties p1 p2))
;; )

(defun insert-section-line ()
  "Insert a commented section title around line"
  (interactive "*")
  (let ((beg (point)))
    (insert-section-region beg (point)))

(defun resize-window ()
  (interactive "*")
  (setq current-window-size (window-width (frame-selected-window)))
  (if (>= 80 current-window-size)
      (setq change-window-size (- current-window-size 80))
    (setq change-window-size (- 80 current-window-size))
  (enlarge-window change-window-size)

 [(meta f11)] 'resize-window)

Kill ring

Interactively explore the kill ring.

Display the kill ring in a small popup.

(use-package popup-kill-ring
  :ensure t)

Display kill ring in another window and allow to search a pattern.

(use-package browse-kill-ring
  :ensure t)


Sometimes it is convenient to record a video of emacs.

(use-package camcorder
  :ensure t)


I want to be able to have multiple instances of emacs server running at the same time.

To launch emacs client and specify the server name: emacsclient -s $EMACS_SERVER_NAME -c -n -a "" $@.

(use-package server
  (let ((env-server-name (getenv "EMACS_SERVER_NAME")))
    (when env-server-name
      (message "Replacing original server name %s with %s...done"
               server-name env-server-name)
      (setq server-name env-server-name)))
  (add-to-list 'default-frame-alist `(title . ,server-name))

I always forget which server is in use.

(defun server-name ()
  "Display the name of the server"
  (message "%s" server-name))

Key bindings

(use-package which-key
  :ensure t
(bind-key "C-c j" 'replace-string)
(bind-key "C-c x" 'close-all-buffers)
(bind-key "C-c k" 'kill-this-buffer)

(bind-key "C-S-<iso-lefttab>" 'previous-buffer-same-mode)
(bind-key "C-<tab>" 'next-buffer-same-mode)