Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
2138 lines (1734 sloc) 56.5 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)

;; (global-visual-line-mode)
;; (diminish 'visual-line-mode)

(setq line-move-visual nil)

(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)
(bind-key "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)

;; all files must have a newline at the end
;; (setq require-final-newline t)

(setq wdired-allow-to-change-permissions 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

(use-package saveplace
  (save-place-file (expand-file-name ".places" user-emacs-directory))
  (save-place t)
  (save-place-mode 1))


Auto reload file when there is a change

(global-auto-revert-mode t)
(delight '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 prints compilation messages that are not formated correctly. And it doesn’t want to disable colors.

(require 'compile)
(defconst bucklescript-compilation-regexp
  "^[ \t]*\\(?:We've found a bug for you!\\)?\\(?:Warning number \\([0-9]+\\)\\)?[\n ]*\
\\([^ ]+\\) +\\([0-9]+\\):\\([0-9]+\\)-?\\(?:\\([0-9]+\\):\\)?\\([0-9]+\\)?$"
  "The messages from -bs-super-errors are not standard")
 `(bucklescript ,bucklescript-compilation-regexp 2 (3 . 5) (4 . 6) 1))
(add-to-list 'compilation-error-regexp-alist 'bucklescript)

Util function to help detecting which compilation regexp matched.

(defun test-compilation-error-regexps ()
    (let ((matched nil))
      (dolist (rule compilation-error-regexp-alist)
        (let* ((item (if (symbolp rule)
		         (cdr (assq rule
	       (pat (car item)))
          (goto-char (point-min))
          (when (re-search-forward pat nil t)
	    (push rule matched))))
      (message "matched: %s" (nreverse matched)))))
(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 my/default-theme 'doom-one-light)
(setq my/bad-id-theme 'whiteboard)

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

(defun my/load-theme ()
  (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))))

;; (my/load-theme)

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 . turn-on-solaire-mode)
  (after-revert . turn-on-solaire-mode)
  (minibuffer-setup . 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-10"))
 ((member "Ubuntu Mono" (font-family-list))
  (set-face-attribute 'default nil :font "Ubuntu Mono-10"))
 ((member "DejaVu Sans Mono" (font-family-list))
  (set-face-attribute 'default nil :font "DejaVu Sans Mono-10")))

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


Mode line

Move (vc-mode vc-mode) at the end of the mode line.

(setq-default mode-line-format
                " "
                " "
                (vc-mode vc-mode)


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

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
  :bind (:map projectile-mode-map
              ("C-c p" . projectile-command-map)
              ("<f6>" . projectile-compile-project)
              ("M-j" . projectile-find-file)
              ("M-C-j" . projectile-switch-project))
  (projectile-completion-system 'helm)
  (projectile-enable-caching t)
  (projectile-switch-project-action #'helm-projectile-find-file)
(use-package projectile-ripgrep
  :ensure t)


(use-package flymake
  :ensure t
  ("<f7>"  . flymake-goto-prev-error)
  ("<f8>"  . flymake-goto-next-error)
  (defun flymake--transform-mode-line-format (ret)
    "Change the output of `flymake--mode-line-format'."
    (setf (seq-elt (car ret) 1) " FM")
  (advice-add #'flymake--mode-line-format
              :filter-return #'flymake--transform-mode-line-format))
(use-package flymake-diagnostic-at-point
  :if (version<= "26.0" emacs-version)
  :after flymake
  (add-hook 'flymake-mode-hook #'flymake-diagnostic-at-point-mode))


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 . eldoc-mode))


I use company mode as a completion backend

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

Popup for documentation or help

(use-package company-quickhelp
  :commands (company-quickhelp-mode)
  :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))
(use-package paren
  :ensure t
  (show-paren-delay 0.3)
  (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)
  (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))
(use-package paredit
  :ensure t
  (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))
  (unbind-key "M-?" paredit-mode-map)
  ((emacs-lisp-mode dune-mode tuareg-mode) . paredit-mode))

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

(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"))
(defun opam-which (bin)
  (shell-cmd (format "opam exec -- which %s" bin)))

(setq opam-p (shell-cmd "which opam"))
(setq utop-p (shell-cmd "which utop"))
(setq reason-npm-p (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 hideshow
  :ensure t
  :delight hs-minor-mode
  (defun deriving-inline-forward-sexp (&optional arg)
    (search-forward-regexp "\\[@@@end\\]") nil nil arg)
  (add-to-list 'hs-special-modes-alist
               '(tuareg-mode "\\[@@deriving_inline[^]]*\\]" "\\[@@@end\\]" nil
                             deriving-inline-forward-sexp nil)))

(use-package tuareg
  :ensure t
  :delight "O"
  :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$" . dune-mode)
         ("^dune-project$" . dune-mode)
         ("^dune-workspace$" . dune-mode))
  (unbind-key "<backspace>" tuareg-mode-map)
  (load "tuareg-site-file")
  (defun my/setup-tuareg ()
    (setq-local comment-style 'indent))
  (defconst my/ocaml-compilation-regexp
    "^ *\\(File \\(\"?\\)\\([^,\" \n\t<>]+\\)\\2, \
lines? \\([0-9]+\\)-?\\([0-9]+\\)?\
\\(?:, characters? \\([0-9]+\\)-?\\([0-9]+\\)?\\)?:\\)\
\\(?:\n[ \t]*\\(?:\\(?:[0-9]+ | .*\\|\\^+\\)\n[ \t]*\\)*\
\\(Warning\\(?: [0-9]+\\)?\\):\\)?"
    "Update the ocaml regexp to support >= 4.08")
   `(my/ocaml ,my/ocaml-compilation-regexp 3 (4 . 5) (6 . 7) (8) 1))
  (add-to-list 'compilation-error-regexp-alist 'my/ocaml)
  (defun setup-hide-deriving-inline ()
    (hs-minor-mode t)
    (let ((hs-hide-comments-when-hiding-all nil))
  (tuareg-mode . my/setup-tuareg)
  (tuareg-mode . setup-hide-deriving-inline))

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
  :ensure t
  :bind (:map reason-mode-map
              ("C-M-\\" . refmt))
  (refmt-width-mode 'fill)
  (refmt-command 'opam)
  (defun my/setup-reason-npm ()
    "When reason tools are only available from npm and not from an
opam switch, the configuration must be different."
    (message "Loading reason tools from npm...")
    (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)))
    (message "Loading reason tools from npm...done"))
  (when reason-npm-p (my/setup-reason-npm))
  (defun my/reason-npm-hook ()
    (when reason-npm-p
      (setq-local merlin-command (ocamlmerlin-where))))
  :hook (reason-mode . my/reason-npm-hook))


Configure merlin. Magical autocompletion and IDE features.

(use-package merlin
  (merlin-completion-with-doc t)
  (merlin-error-check-then-move nil)
  (merlin-command 'opam)
  (merlin-error-after-save t)
  (merlin-locate-preference 'mli)
  ;; (merlin-type-face ((t (:background "firebrick")))) ;; doom-one
  ;; (merlin-type-face ((t (:background "#a0bcf8")))) ;; doom-one-light
  (merlin-type-face ((t (:inherit (highlight)))))
  :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
  ((tuareg-mode reason-mode) . merlin-mode)
  (setq company-backends (remove 'merlin-company-backend company-backends)))


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
  :delight utop-minor-mode
  :custom (utop-edit-command t)
  :commands (utop-minor-mode)
  (defun my/setup-utop (cmd &optional name)
    (setq my/utop-name (if name name cmd))
    (setq utop-command (format "%s -emacs" cmd))
    (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 "opam exec -- utop" "utop"))
  (defun my/setup-utop-reason ()
    (my/setup-utop "opam exec -- rtop" "rtop"))
  (defun my/setup-utop-dune (&optional path from)
    (let* ((from (if from from default-directory))
           (path (if path path default-directory))
           (rel-path-to (file-relative-name path from))
           (cmd (format " %S %S" from rel-path-to))
           (path-dirname (file-name-nondirectory (directory-file-name (file-name-directory path))))
           (name (format "[dune %s]" path-dirname)))
      (my/setup-utop cmd name)))
  (tuareg-mode . my/setup-utop-tuareg)
  (reason-mode . my/setup-utop-reason)
  (utop-minor-mode . (lambda () (setq company-backends (remove 'utop-company-backend company-backends)))))


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


(use-package ocamlformat
  :commands (ocamlformat ocamlformat-before-save)
  :bind (:map tuareg-mode-map
              ("M-<iso-lefttab>" . ocamlformat)))


(defun my/merlin-lsp--current-font-among-fonts-p (pos fonts)
  "If current font at POS is among FONTS."
  (let* ((fontfaces (get-text-property pos 'face)))
    (when (not (listp fontfaces))
      (setf fontfaces (list fontfaces)))
    (delq nil
          (mapcar (lambda (f)
                    (member f fonts))

(defun my/merlin-lsp--in-comment-p (pos)
  "Return non-nil if character at POS is comment or documentation.
This is done by comparing font face.  So a mode such as
`tuareg-mode' or `reason-mode' must be activated in the buffer
before to call this function."
  (my/merlin-lsp--current-font-among-fonts-p pos '(font-lock-comment-face

(defun my/merlin-lsp--in-string-p (pos)
  "Return non-nil if character at POS is string.
This is done by comparing font face.  So a mode such as
`tuareg-mode' or `reason-mode' must be activated in the buffer
before to call this function."
  (my/merlin-lsp--current-font-among-fonts-p pos '(font-lock-string-face)))

(defun my/merlin-lsp--in-keyword-p (pos)
  "Return non-nil if character at POS is keyword.
This is done by comparing font face.  So a mode such as
`tuareg-mode' or `reason-mode' must be activated in the buffer
before to call this function."
  (my/merlin-lsp--current-font-among-fonts-p pos '(tuareg-font-lock-governing-face

(defun my/merlin-lsp--in-operator-p (pos)
  "Return non-nil if character at POS is operator.
This is done by comparing font face.  So a mode such as
`tuareg-mode' or `reason-mode' must be activated in the buffer
before to call this function."
  (my/merlin-lsp--current-font-among-fonts-p pos '(tuareg-font-lock-operator-face)))

(defun my/merlin-lsp--valid-type-position-p (pos)
  "Return non-nil if POS is in a place valid to get a type."
  (let ((symbol (thing-at-point 'symbol))
        (operator (my/merlin-lsp--in-operator-p pos))
        (string (my/merlin-lsp--in-string-p pos))
        (comment (my/merlin-lsp--in-comment-p pos))
        (keyword (my/merlin-lsp--in-keyword-p pos)))
    (and (or symbol operator string)
         (not comment)
         (or (not keyword) string))))

(defun my/merlin-lsp--hover ()
  "Call lsp-hover only in valid hover positions."
  (when (my/merlin-lsp--valid-type-position-p (point))

(defun my/merlin-lsp--document-highlight ()
  "Call lsp-document-highlight only in valid hover positions."
  (when (my/merlin-lsp--valid-type-position-p (point))

(defun my/merlin-lsp--setup-eldoc ()
  "Replace default lsp-eldoc-hook with custom functions checking
the validity of the position."
  (setq-local lsp-eldoc-hook
              '(my/merlin-lsp--hover my/merlin-lsp--document-highlight)))

(defun my/merlin-lsp-register ()
  "Register a lsp server for ocaml. This functions must be called
only after lsp-mode has been loaded."
    :new-connection (lsp-stdio-connection
                     '("opam" "exec" "--switch=ocaml-base-compiler.4.07.1" "--" "/home/louis/.opam/ocaml-base-compiler.4.07.1/bin/ocamlmerlin-lsp"))
    :major-modes '(caml-mode tuareg-mode reason-mode)
    :server-id 'O)))

(defun my/merlin-lsp ()
  "Setup and start merlin-lsp."

For debug only

    :new-connection (lsp-stdio-connection
    :major-modes '(caml-mode tuareg-mode reason-mode)
    :priority 1
    :server-id 'merlin-test))

dir locals

Then it is possible to create a .dir-locals.el file to setup compilation and utop commands.

  (eval progn
        (require 'projectile)
         "make -C backend/api byte" projectile-compilation-cmd-map)))
   (eval progn
          (format "%sbackend/api/src/" (projectile-project-root)))))))


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


(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
  :ensure t
  :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)
(when (and (executable-find "fish")
           (require 'fish-completion nil t))
(use-package fish-mode
  :ensure t)


(defun my/yarn-bash-lsp-register ()
  "Register a lsp server for bash installed globally with
yarn. This functions must be called only after lsp-mode has been
    :new-connection (lsp-stdio-connection
                     '("" "bash-language-server" "start"))
    :major-modes '(sh-mode)
    :server-id 'B)))

(use-package helm-lsp :ensure t)
(use-package lsp-ui
  ;; :load-path "/home/louis/Code/github/lsp-ui"
  :ensure t
  (lsp-ui-doc-enable t)
  (lsp-ui-doc-position 'at-point)
  (lsp-ui-sideline-enable nil)
  (lsp-ui-peek-peek-height 5))
(use-package company-lsp
  :ensure t
  (company-lsp-cache-candidates nil)
  (push 'company-lsp company-backends))
(use-package lsp-mode
  :ensure t
  (lsp-log-max 100000)
  (lsp-eldoc-render-all nil)
  (lsp-eldoc-enable-hover nil)
  (lsp-enable-snippet nil)
  :bind (:map lsp-ui-mode-map
              ([remap xref-find-references] . lsp-ui-peek-find-references)
              :map lsp-mode-map
              ("C-c r" . lsp-rename))
  (sh-mode . lsp)
  ;; (python-mode . lsp)
  ;; (tuareg-mode . my/merlin-lsp)


(use-package whitespace
  :ensure t
  :delight whitespace-mode
  (whitespace-line-column nil)
  (whitespace-style '(face empty tabs lines-tail trailing))
  ;; When it is enabled globally, there is a problem with magit
  ;; :config
  ;; (global-whitespace-mode t)
  (prog-mode . whitespace-mode))


Same indentation rules everywhere.

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


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


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
  (magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1)
  (magit-log-margin '(t "%Y-%m-%d %H:%M " magit-log-margin-width t 18)))


(use-package forge
  :ensure t
  :after magit
  (add-to-list 'forge-alist

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

Nice blame

(use-package vc-msg
  :ensure t)

Which function

(use-package which-func
  :commands which-function-mode
  (which-function-mode t))


(use-package helm
  :ensure t
  :delight 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)
         ("M-n" . helm-imenu-in-all-buffers)))
(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)


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)

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
  :pin "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:

echo "removing $(ls -1 ${ORG_DIR}/*.elc | wc -l) files from ${ORG_DIR}"
rm -f ${ORG_DIR}/*.elc

Org extentions installation

Enable org-tempo to enable <s template expansion

(use-package org-tempo)
(add-to-list 'org-structure-template-alist '("w" . "src emacs-lisp"))

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 "bash")
           (string= lang "sh")
           (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)

Multiple cursors

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


Emacs 26

(use-package display-line-numbers
  :if (version<= "26.0" emacs-version)
  (defcustom display-line-numbers-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 display-line-numbers mode
is on"
    :type '(repeat (sexp :tag "Major mode"))
    :tag " Major modes where display-line-numbers is disabled: "
    :group 'display-line-numbers)
  (defcustom display-line-numbers-disable-starred-buffers 't
    "Disable buffers that have stars in them like *Gnu Emacs*"
    :type 'boolean
    :group 'display-line-numbers)

  (defun display-line-numbers--turn-on ()
    "When display-line-numbers is running globally, disable line
number in modes defined in
`display-line-numbers-disabled-modes-list'. Also turns off
numbering in starred modes like *scratch*"
    (unless (or (minibufferp)
                (and (daemonp) (null (frame-parameter nil 'client)))
                ;; additions to original `display-line-numbers--turn-on'
                (member major-mode display-line-numbers-disabled-modes-list)
                (and display-line-numbers-disable-starred-buffers (string-match "*" (buffer-name))))

Emacs 25

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
  :if (version< emacs-version "26.0")
  (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)


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
  ("M-`" . ace-window)
  ("M-/" . aw-flip-window))


(use-package tramp)


(use-package deadgrep
  :ensure t
  :bind ("C-c g" . deadgrep))

SX – Stack Exchange

(use-package sx
  (:prefix "C-c e"
           :prefix-map my-sx-map
           :prefix-docstring "Global keymap for SX."
           ("q" . sx-tab-all-questions)
           ("i" . sx-inbox)
           ("o" . sx-open-link)
           ("u" . sx-tab-unanswered-my-tags)
           ("a" . sx-ask)
           ("s" . sx-search)))


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

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

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

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-w" #'kill-region-or-word)

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



(require eglot)
(use-package eglot
(add-to-list 'eglot-server-programs '(tuareg-mode . ("opam" "exec" "ocaml-base-compiler.4.07.1" "--" "/home/louis/.opam/ocaml-base-compiler.4.07.1/bin/ocamlmerlin-lsp"))))
  (defun my-projectile-project-find-function (dir)
    (let ((root (projectile-project-root dir)))
      (and root (cons 'transient root))))

  (projectile-mode t)

  (with-eval-after-load 'project
    (add-to-list 'project-find-functions 'my-projectile-project-find-function))