Skip to content

Latest commit

 

History

History
3618 lines (3073 loc) · 137 KB

config.org

File metadata and controls

3618 lines (3073 loc) · 137 KB

GNU Emacs Configuration

Table of Content

About

After using GNU Emacs for a while, I decided to create my own config to simplify my daily life by adding scripts and useful functions. For more informations about myself, you can visit my website.

To manage package configurations, I use the use-package package from John Wiegley, that I recommend.

This configuration is mainly based on the following user configurations:

Thanks to them for their incredible work!

Emacs Initialization

This section contains the basic commands you need to know to properly initialize your GNU Emacs.

Package Sources

To install packages, it is useful to configure the package sources.

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

Package Configurations

To manage the package configurations with use-package, you must install it with the following code snippet.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(use-package delight :ensure t)
(use-package use-package-ensure-system-package :ensure t)

Start Up

This section contains the packages and configurations needed to get started with GNU Emacs.

Authentication

I mainly use pass under GNU Linux as password manager. However, I prefer to store IRC and email passwords directly in an authinfo.gpg file for simplicity. Afterward, this encrypted file is placed in Syncthing to be able to easily use my configuration on other devices.

(use-package auth-source
  :ensure nil
  :custom
  (auth-sources '("~/.config/gnupg/shared/authinfo.gpg"
                  "~/.authinfo.gpg"
                  "~/.authinfo"
                  "~/.netrc")))

Defaults

For a better user experience of GNU Emacs, here are the default values that I use.

(setq-default
 ad-redefinition-action 'accept                   ; Silence warnings for redefinition
 cursor-in-non-selected-windows nil               ; Hide the cursor in inactive windows
 display-time-default-load-average nil            ; Don't display load average
 fill-column 80                                   ; Set width for automatic line breaks
 help-window-select t                             ; Focus new help windows when opened
 indent-tabs-mode nil                             ; Prefer spaces over tabs
 inhibit-startup-screen t                         ; Disable start-up screen
 initial-scratch-message ""                       ; Empty the initial *scratch* buffer
 kill-ring-max 128                                ; Maximum length of kill ring
 load-prefer-newer t                              ; Prefer the newest version of a file
 mark-ring-max 128                                ; Maximum length of mark ring
 read-process-output-max (* 1024 1024)            ; Increase the amount of data reads from the process
 scroll-conservatively most-positive-fixnum       ; Always scroll by one line
 select-enable-clipboard t                        ; Merge system's and Emacs' clipboard
 tab-width 4                                      ; Set width for tabs
 use-package-always-ensure t                      ; Avoid the :ensure keyword for each package
 user-full-name "Terencio Agozzino"               ; Set the full name of the current user
 user-mail-address "terencio.agozzino@gmail.com"  ; Set the email address of the current user
 vc-follow-symlinks t                             ; Always follow the symlinks
 view-read-only t)                                ; Always open read-only buffers in view-mode
(column-number-mode 1)                            ; Show the column number
(fset 'yes-or-no-p 'y-or-n-p)                     ; Replace yes/no prompts with y/n
(global-hl-line-mode)                             ; Hightlight current line
(set-default-coding-systems 'utf-8)               ; Default to utf-8 encoding
(show-paren-mode 1)                               ; Show the parent

XDG Base Directory Specification

To keep the user’s home and the ~/.emacs.d folder as clean as possible, I follow the XDG base directory specification. Be careful that GNU Emacs will not create the appropriate folders if they do not exist. Therefore, it is necessary to create them yourself.

mkdir ~/.cache/emacs ~/.local/share/emacs/

NOTE: you can find out more by going to my dotfiles.

(defvar xdg-bin (getenv "XDG_BIN_HOME")
  "The XDG bin base directory.")

(defvar xdg-cache (getenv "XDG_CACHE_HOME")
  "The XDG cache base directory.")

(defvar xdg-config (getenv "XDG_CONFIG_HOME")
  "The XDG config base directory.")

(defvar xdg-data (getenv "XDG_DATA_HOME")
  "The XDG data base directory.")

(defvar xdg-lib (getenv "XDG_LIB_HOME")
  "The XDG lib base directory.")

Some GNU Emacs packages implicitly use the request package to make HTTP requests. By default, the data for this package is stored in the GNU Emacs directory. Let’s change this value to respect the XDG base directory specifications.

(use-package request
  :custom
  (request-storage-directory (expand-file-name (format "%s/emacs/request/" xdg-data))))

Similarly, GNU Emacs has the built-in url-cookie package to manage cookies. Let’s also change this value to respect the XDG base directory specifications.

(use-package url-cookie
  :ensure nil
  :custom
  (url-cookie-file (expand-file-name (format "%s/emacs/url/cookies/" xdg-data))))

Customizations

To avoid overloading the GNU Emacs customization init.el file made with the UI, I add the generated code in a separate file.

(use-package cus-edit
  :ensure nil
  :custom (custom-file (expand-file-name (format "%s/emacs/custom.el" xdg-data)))
  :config
  (when (file-exists-p custom-file)
    (load custom-file t)))

Fonts

Spending most of our time on GNU Emacs, it is important to use a font that will make our reading easier. Source Code Pro is one of the best monospaced font.

(set-face-attribute 'default nil :font "Source Code Pro")
(set-fontset-font t 'latin "Noto Sans")

Do not forget to install this font with your system manager and to check the font installation.

fc-list | grep "Source Code Pro"

LSP

The main benefit of using Language Server Protocol (LSP) to configure the management of your programming languages is that LSP servers are also used by other text editors, increasing contributions to these packages. What could be better than benefiting from the larger community, while keeping a quality text editor ;-)

We need a Language Server Protocol (LSP) client to use different LSP servers according to the programming languages that we would like to use. That’s where lsp-mode comes in!

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :hook ((prog-mode . lsp-deferred)
         (lsp-mode . lsp-enable-which-key-integration))
  :custom
  (lsp-enable-folding nil)
  (lsp-enable-links nil)
  (lsp-enable-snippet nil)
  (lsp-keymap-prefix "C-c ;")
  (lsp-session-file (expand-file-name (format "%s/emacs/lsp-session-v1"  xdg-data)))
  (read-process-output-max (* 1024 1024)))

In addition to lsp-mode, it is possible to use lsp-ui to get additional information (e.g., documentation) when hovering a variable or a function.

(use-package lsp-ui
  :hook (lsp-mode . lsp-ui-mode))

When using lsp, it is likely that you will encounter programming errors. To navigate through these errors via the minibuffer, you can use a package for that. If like me, you use consult with your minibuffer completion, then consult-lsp is made for you.

(use-package consult-lsp
  :commands (consult-lsp-diagnostics consult-lsp-symbols))

Finally, if like me you need a debugger, dap-mode uses the Debug Adapter Protocol wire protocol for communication between client and Debug Server. You won’t find a better debugger.

(use-package dap-mode
  :after lsp-mode
  :config
  (dap-mode t)
  (dap-ui-mode t))

Package Manager

Contrary to what some users might think, use-package is not a package manager. To download and use packages that are not available in package sources, I use straight. The snippet below takes care of installing straight.

;; Bootstrap straight
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; Integrates `straight' directly into the `use-package' package through the
;; `:straight' expression.
(straight-use-package 'use-package)

Theme

I am a fan of tomorrow-night and combined with doom-modeline it’s happiness! To get icons in the doom-modeline, you will need to install icons.

(use-package doom-themes
  :config
  (load-theme 'doom-tomorrow-night t)
  (doom-themes-org-config))

(use-package doom-modeline
  :init (doom-modeline-mode)
  :custom
  (doom-modeline-icon (display-graphic-p))
  (doom-modeline-mu4e t))

Since we do not do things by halves, it is also interesting to visually differentiate “real” buffers (e.g., buffers that contain our work) from “unreal” buffers (e.g., popups) by giving the latter a darker color. From then on, solar-mode is the ideal package.

(use-package solaire-mode
  :defer 0.1
  :custom (solaire-mode-remap-fringe t)
  :config (solaire-global-mode))

Remove Mouse Interface

If you have to use the mouse with GNU Emacs, you probably have the wrong text editor. Let’s remove all those menu items related to the mouse interface.

(when window-system
  (menu-bar-mode -1)
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
  (tooltip-mode -1))

Saving Configuration

A good practice is to use an .org file to modify your GNU Emacs configuration with org-mode and to load this configuration via an .el file. This way you can maintain an org-mode configuration and still get a faster load.

Using the async package and the org-babel-tangle command, the code below will executes org-babel-tangle asynchronously when config.org is saved, to update the config.el file. From then on, you only need to add a add the my/config-tangle function to the after-save hook and specify the loading of the config.el file into the init.el file.

(use-package async
  :after org
  :preface
  (defvar config-file (expand-file-name "config.org" user-emacs-directory)
    "The configuration file.")

  (defvar config-last-change (nth 5 (file-attributes config-file))
    "The last modification time of the configuration file.")

  (defvar show-async-tangle-results nil
    "Keep *emacs* async buffers around for later inspection.")

  (defun my/config-tangle ()
    "Tangle the org file asynchronously."
    (when (my/config-updated)
      (setq config-last-change
            (nth 5 (file-attributes config-file)))
      (my/async-babel-tangle config-file)))

  (defun my/config-updated ()
    "Check if the configuration file has been updated since the last time."
    (time-less-p config-last-change
                 (nth 5 (file-attributes config-file))))

  (defun my/async-babel-tangle (org-file)
    "Tangle the org file asynchronously."
    (let ((init-tangle-start-time (current-time))
          (file (buffer-file-name))
          (async-quiet-switch "-q"))
      (async-start
       `(lambda ()
          (require 'org)

          (org-babel-tangle-file ,org-file))
       (unless show-async-tangle-results
         `(lambda (result)
            (if result
                (message "[✓] %s successfully tangled (%.2fs)"
                         ,org-file
                         (float-time (time-subtract (current-time)
                                                    ',init-tangle-start-time)))
              (message "[✗] %s as tangle failed." ,org-file))))))))

Backups

It is important to have file backups available with GNU Emacs. It is rare when I have to go back into backups and when I do, it is to go back to a previous backup of a buffer.

Finally, some saved files require a command to be run so that changes within the file are taken into account. The my/cmd-after-saved-file function below handles the command execution according to a file at each save of this file.

(use-package files
  :ensure nil
  :preface
  (defvar afilename-cmd
    `((,(format "%s/X11/Xresources" xdg-config) . ,(format "xrdb -merge %s/X11/Xresources" xdg-config))
      (,(format "%s/xbindkeysrc" (getenv "HOME")) . "xbindkeys -p"))
    "File association list with their respective command.")

  (defun my/cmd-after-saved-file ()
    "Execute a command after saved a specific file."
    (let* ((match (assoc (buffer-file-name) afilename-cmd)))
      (when match
        (shell-command (cdr match)))))
  :hook (after-save . my/cmd-after-saved-file)
  :init
  ;; Create the "~/.cache/emacs/auto-save" folder if it does not exist.
  (let ((auto-save-folder (expand-file-name
                           (file-name-as-directory
                            (expand-file-name (format "%s/emacs/auto-save/" xdg-cache))))))
    (unless (file-exists-p (locate-user-emacs-file auto-save-folder))
      (make-directory (locate-user-emacs-file auto-save-folder))))
  :custom
  (auto-save-file-name-transforms
   `((".*" ,(expand-file-name (format "%s/emacs/auto-save/" xdg-cache) t))))
  (backup-directory-alist
   `(("." . ,(expand-file-name (format "%s/emacs/backups/" xdg-data)))))
  (delete-old-versions t)
  (vc-make-backup-files t)
  (version-control t))

Management

Section dedicated to managing buffers, files, and windows on GNU Emacs to provide a more pleasant experience.

Buffers

Buffers can quickly become a mess to manage. To manage them better, I use the ibuffer built-in package instead of buffer-menu, to have a nicer visual interface with a syntax color.

In addition, some buffers may contain useful temporary information that should not be killed by accident. I make sure to set the buffers *scratch* and *Messages* to read-only.

(use-package ibuffer
  :ensure nil
  :preface
  (defvar protected-buffers '("*scratch*" "*Messages*")
    "Buffer that cannot be killed.")

  (defun my/protected-buffers ()
    "Protect some buffers from being killed."
    (dolist (buffer protected-buffers)
      (with-current-buffer buffer
        (emacs-lock-mode 'kill))))
  :bind ("C-x C-b" . ibuffer)
  :init (my/protected-buffers))

For buffer navigation, the next function is a more efficient way to go to the beginning of a line with move-beginning-of-line (C-a) and back-to-indentation (M-m).

(use-package imenu
  :ensure nil
  :preface
  (defun my/smarter-move-beginning-of-line (arg)
    "Move point back to indentation of beginning of line.

   Move point to the first non-whitespace character on this line.
   If point is already there, move to the beginning of the line.
   Effectively toggle between the first non-whitespace character and
   the beginning of the line.

   If ARG is not nil or 1, move forward ARG - 1 lines first. If
   point reaches the beginning or end of the buffer, stop there."
    (interactive "^p")
    (setq arg (or arg 1))

    ;; Move lines first
    (when (/= arg 1)
      (let ((line-move-visual nil))
        (forward-line (1- arg))))

    (let ((orig-point (point)))
      (back-to-indentation)
      (when (= orig-point (point))
        (move-beginning-of-line 1))))
  :bind (("C-a" . my/smarter-move-beginning-of-line)
         ("C-r" . imenu)))

Finally, it is useful to be able to move a line or a region up and down without having to kill-region (C-w) and yank (C-y). With the move-text-up and move-text-down commands, the move-text package allows to moves the current line or if marked, the current region’s, whole lines.

(use-package move-text
  :bind (("M-p" . move-text-up)
         ("M-n" . move-text-down))
  :config (move-text-default-bindings))

Files

Sometimes you may want to discard your changes to a file and revert to the saved version of this file.

(use-package autorevert
  :ensure nil
  :delight auto-revert-mode
  :bind ("C-x R" . revert-buffer)
  :custom (auto-revert-verbose nil)
  :config (global-auto-revert-mode))

To manage your files, dired is already a good file manager. To fine-tune its use, let’s change some default values.

(use-package dired
  :ensure nil
  :commands (dired dired-jump)
  :bind (:map dired-mode-map
              ("h" . dired-up-directory)
              ("j" . dired-next-line)
              ("k" . dired-previous-line)
              ("l" . dired-single-buffer))
  :delight "Dired"
  :custom
  (dired-auto-revert-buffer t)
  (dired-dwim-target t)
  (dired-hide-details-hide-symlink-targets nil)
  (dired-listing-switches "-alh --group-directories-first")
  (dired-ls-F-marks-symlinks nil)
  (dired-recursive-copies 'always))

To avoid dired to keep buffers, I use dired-single.

(use-package dired-single
  :after dired
  :bind (:map dired-mode-map
              ([remap dired-find-file] . dired-single-buffer)
              ([remap dired-up-directory] . dired-single-up-directory)
              ("M-DEL" . dired-prev-subdir)))

By default, dired opens files in plain text. This behavior is sometimes undesirable. Hopefully, dired-open can be used to informs dired that certain desired file extensions must be opened with external packages to GNU Emacs.

(use-package dired-open
  :after (dired dired-jump)
  :custom (dired-open-extensions '(("mp4" . "mpv"))))

To know the type of file at a glance, all-the-icons-dired integrates icons directly into dired.

(use-package all-the-icons-dired
  :if (display-graphic-p)
  :hook (dired-mode . all-the-icons-dired-mode))

It is sometimes convenient to hide dotfiles. With dired-hide-dotfiles this becomes possible.

(use-package dired-hide-dotfiles
  :hook (dired-mode . dired-hide-dotfiles-mode)
  :bind (:map dired-mode-map
              ("H" . dired-hide-dotfiles-mode)))

I like being able to <TAB> on a folder and see its contents, without me getting into it. dired-subtree allows this behavior.

(use-package dired-subtree
  :after dired
  :bind (:map dired-mode-map
              ("<tab>" . dired-subtree-toggle)))

Finally, to manage folders with a large number of files, it may be useful to filter with dired-narrow

(use-package dired-narrow
  :ensure nil
  :bind (("C-c C-n" . dired-narrow)
         ("C-c C-f" . dired-narrow-fuzzy)))

Windows

Most of the time, I want to split a window and put the focus on it to perform an action. By default GNU Emacs does not give the focus to this new window. I have no idea why this is not the default behavior, but we can easily set this behavior.

(use-package window
  :ensure nil
  :bind (("C-x 2" . vsplit-last-buffer)
         ("C-x 3" . hsplit-last-buffer)
         ;; Don't ask before killing a buffer.
         ([remap kill-buffer] . kill-this-buffer))
  :preface
  (defun hsplit-last-buffer ()
    "Focus to the last created horizontal window."
    (interactive)
    (split-window-horizontally)
    (other-window 1))

  (defun vsplit-last-buffer ()
    "Focus to the last created vertical window."
    (interactive)
    (split-window-vertically)
    (other-window 1)))

To maximize concentration, I prefer to only center individual windows and keep a default behavior when multiple windows are present. centered-window deals with this behavior.

(use-package centered-window
  :custom
  (cwm-centered-window-width 130)
  (cwm-frame-internal-border 0)
  (cwm-incremental-padding t)
  (cwm-incremental-padding-% 2)
  (cwm-left-fringe-ratio 0)
  (cwm-use-vertical-padding t)
  :config (centered-window-mode))

The way I move between several windows in GNU Emacs is by indicating the number of the window I want to move to. Most people use ace-window, but I prefer switch-window which displays the window number while hiding its content. I find this behavior more convenient than moving from window to window to get to the one we are looking for.

(use-package switch-window
  :bind (("C-x o" . switch-window)
         ("C-x w" . switch-window-then-swap-buffer)))

There are times when I would like to bring back a windows layout with their content. With the winner-undo and winner-redo commands from the built-in winner package, I can easily do that.

(use-package winner
  :ensure nil
  :config (winner-mode))

Minibuffer Completion

Having a good minibuffer completion is important on GNU Emacs since it is one of the elements we will frequently interact with. In the beginning I used helm, but I found it to be memory intensive for the few features I was using. From this observation I switched to ivy for many years, which is a faster and a lighter framework than helm. However, ivy is still a framework.

Since then, newer completion systems have emerged (e.g., vertico, selectrum, and icomplete-vertical), designed to be optimized for a single task and nested with other packages with the same vision. That’s why I now use vertico instead of ivy.

NOTE: selectrum is also a good alternative to ivy, but is less minimal than vertico.

(use-package vertico
  :straight (:files (:defaults "extensions/*"))
  :init (vertico-mode)
  :bind (:map vertico-map
              ("C-<backspace>" . vertico-directory-up))
  :custom (vertico-cycle t)
  :custom-face (vertico-current ((t (:background "#1d1f21")))))

To enable richer annotations (e.g., summary documentation of the functions and variables, as well as having the size and the last consultation of the files) for minibuffer completions, marginalia is awesome.

(use-package marginalia
  :after vertico
  :init (marginalia-mode)
  :custom
  (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)))

If like me you like to have icons associated with candidates, you can use all-the-icons-completion.

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

By default, vertico sorts the candidates according to their history position, then by length and finally by alphabetical. To improves searching across completion (e.g., by filter expressions separated by spaces), you should use orderless (or prescient).

(use-package orderless
  :custom
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles . (partial-completion)))))
  (completion-styles '(orderless)))

There is nothing like a collection of commands to have additional completions. consult provides this collection.

(use-package consult
  :after projectile
  :bind  (;; Related to the control commands.
          ("<help> a" . consult-apropos)
          ("C-x b" . consult-buffer)
          ("C-x M-:" . consult-complex-command)
          ("C-c k" . consult-kmacro)
          ;; Related to the navigation.
          ("M-g a" . consult-org-agenda)
          ("M-g e" . consult-error)
          ("M-g g" . consult-goto-line)
          ("M-g h" . consult-org-heading)
          ("M-g i" . consult-imenu)
          ("M-g k" . consult-global-mark)
          ("M-g l" . consult-line)
          ("M-g m" . consult-mark)
          ("M-g o" . consult-outline)
          ("M-g I" . consult-project-imenu)
          ;; Related to the search and selection.
          ("M-s G" . consult-git-grep)
          ("M-s g" . consult-grep)
          ("M-s k" . consult-keep-lines)
          ("M-s l" . consult-locate)
          ("M-s m" . consult-multi-occur)
          ("M-s r" . consult-ripgrep)
          ("M-s u" . consult-focus-lines)
          ("M-s f" . consult-find))
  :custom
  (completion-in-region-function #'consult-completion-in-region)
  (consult-narrow-key "<")
  (consult-project-root-function #'projectile-project-root)
  ;; Provides consistent display for both `consult-register' and the register
  ;; preview when editing registers.
  (register-preview-delay 0)
  (register-preview-function #'consult-register-preview))

Finally, embark is great if like me you like to interact directly with your files (e.g., for renaming, deleting and copying) through your completion system without having to go through dired.

(use-package embark
  :bind ("C-." . embark-act))

Auto-Completion

Auto-completion with GNU Emacs is mainly combined with LSP mode. Therefore the development of any programming language is made easier with auto-completion, which involves a completion at point followed by the display of a small pop-in containing the candidates.

Nowadays there are two main possibilities: company-mode and corfu. Personally I have tried to make an attempt with corfu but I always prefer company-mode as it seems to be easier and smoother to configure.

(use-package company
  :after lsp-mode
  :hook (lsp-mode . company-mode)
  :custom
  (company-begin-commands '(self-insert-command))
  (company-idle-delay 0.5)
  (company-minimum-prefix-length 1)
  (company-show-quick-access t)
  (company-tooltip-align-annotations 't))

To get nice candidate icons differentiating a function from a variable at a glance, I use company-box.

(use-package company-box
  :if (display-graphic-p)
  :after company
  :hook (company-mode . company-box-mode))

Spelling

No one is immune to spelling mistakes. It is therefore crucial to check our spelling when we write documents, reply to an email or perform any other writing task. With abbrev, flyspell, ispell, and LanguageTool, you will have the weapons to reduce those nasty little spelling and grammar mistakes.

Abbreviations

According to a list of misspelled words, the abbrev built-in package automatically corrects these words on the fly.

NOTE: to start with a comfortable list, you can save the common misspellings from Wikipedia (cf. Correcting Typos and Misspellings with Abbrev)

(use-package abbrev
  :ensure nil
  :delight
  :hook (text-mode . abbrev-mode)
  :custom (abbrev-file-name (expand-file-name (format "%s/emacs/abbrev_defs" xdg-data)))
  :config
  (if (file-exists-p abbrev-file-name)
      (quietly-read-abbrev-file)))

Fly Spell

I use the flyspell built-in package to enable spell checking on-the-fly in GNU Emacs. A useful flyspell command is flyspell-auto-correct-word (M-TAB) which automatically corrects a word according to the best suggestion.

NOTE: a call to flyspell-buffer could be extremely slow.

(use-package flyspell
  :ensure nil
  :delight
  :hook ((text-mode . flyspell-mode)
         (prog-mode . flyspell-prog-mode))
  :custom
  ;; Add correction to abbreviation table.
  (flyspell-abbrev-p t)
  (flyspell-default-dictionary "en_US")
  (flyspell-issue-message-flag nil)
  (flyspell-issue-welcome-flag nil))

Spell Checker

To correct spelling mistakes, the ispell built-in package use a spell checker package (e.g., hunspell or aspell). The aspell spell checker package would make it easier to spot errors in camelCase, which can be handy when programming. However, I personally use hunspell which only checks the spelling of comments in the code and because it is more consistent on fly spells than aspell.

To use hunspell you need to install it with your system package manager, as well as install the desired language dictionaries (e.g., hunspell-en_US and hunspell-fr).

Finally, you can check that you have installed the language dictionaries by using the hunspell -D command.

(use-package ispell
  :preface
  (defun my/switch-language ()
    "Switch between the English and French for ispell, flyspell, and LanguageTool."
    (interactive)
    (let* ((current-dictionary ispell-current-dictionary)
           (new-dictionary (if (string= current-dictionary "en_US") "fr_BE" "en_US")))
      (ispell-change-dictionary new-dictionary)
      (if (string= new-dictionary "fr_BE")
          (progn
            (setq lsp-ltex-language "fr"))
        (progn
          (setq lsp-ltex-language "en-US")))
      (flyspell-buffer)
      (message "[✓] Dictionary switched to %s" new-dictionary)))
  :custom
  (ispell-hunspell-dict-paths-alist
   '(("en_US" "/usr/share/hunspell/en_US.aff")
     ("fr_BE" "/usr/share/hunspell/fr_BE.aff")))
  ;; Save words in the personal dictionary without asking.
  (ispell-silently-savep t)
  :config
  (setenv "LANG" "en_US")
  (cond ((executable-find "hunspell")
         (setq ispell-program-name "hunspell")
         (setq ispell-local-dictionary-alist '(("en_US"
                                                "[[:alpha:]]"
                                                "[^[:alpha:]]"
                                                "['’-]"
                                                t
                                                ("-d" "en_US" )
                                                nil
                                                utf-8)
                                               ("fr_BE"
                                                "[[:alpha:]ÀÂÇÈÉÊËÎÏÔÙÛÜàâçèéêëîïôùûü]"
                                                "[^[:alpha:]ÀÂÇÈÉÊËÎÏÔÙÛÜàâçèéêëîïôùûü]"
                                                "['’-]"
                                                t
                                                ("-d" "fr_BE")
                                                nil
                                                utf-8))))
        ((executable-find "aspell")
         (setq ispell-program-name "aspell")
         (setq ispell-extra-args '("--sug-mode=ultra"))))
  ;; Ignore file sections for spell checking.
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_align" . "#\\+end_align"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_align*" . "#\\+end_align*"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_equation" . "#\\+end_equation"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_equation*" . "#\\+end_equation*"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_example" . "#\\+end_example"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_labeling" . "#\\+end_labeling"))
  (add-to-list 'ispell-skip-region-alist '("#\\+begin_src" . "#\\+end_src"))
  (add-to-list 'ispell-skip-region-alist '("\\$" . "\\$"))
  (add-to-list 'ispell-skip-region-alist '(org-property-drawer-re))
  (add-to-list 'ispell-skip-region-alist '(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:")))

Grammar Checker

LanguageTool is great for correcting your grammar while you are writing or saving your buffer. Combined with abbrev-mode and flyspell, you will have better quality documents. To use LanguageTool with LSP mode, the lsp-ltex package is what you need. The first time you use it, it will download the LTEX Language Server LSP server for you.

NOTE: I don’t hook lsp-ltex to text-mode since it would process the config.org file which has too many errors to be processed properly.

(use-package lsp-ltex
  :disabled
  :custom
  (lsp-ltex-enabled nil)
  (lsp-ltex-mother-tongue "fr"))

Programming Languages and Tools

Section dedicated to the definition and customization of different programming languages and their tools. If you have the need to specify other programming languages, please check out the languages section of the LSP mode website.

Bash

Being a UNIX user, I often do Bash. Since GNU Emacs already supports it, I only use the bash-language-server (bash-ls) as LSP server. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

Finally, the following snippet ensures that execution right (with chmod +x) is automatically granted to save a shell script file that begins with a #! shebang.

(use-package sh-script
  :ensure nil
  :hook (after-save . executable-make-buffer-file-executable-if-script-p))

C++

When I develop in C++ with GNU Emacs, I use the ccls LSP server. To use it, do not forget to configure the LSP package and to install this LSP server through your system package manager.

(use-package ccls
  :after projectile
  :hook ((c-mode c++-mode objc-mode cuda-mode) . lsp-deferred)
  :custom
  (ccls-args nil)
  (ccls-executable (executable-find "ccls"))
  (projectile-project-root-files-top-down-recurring
   (append '("compile_commands.json" ".ccls")
           projectile-project-root-files-top-down-recurring))
  :config (add-to-list 'projectile-globally-ignored-directories ".ccls-cache"))

To allow ccls to know the dependencies of your .cpp files with your .h files, it is important to provide an compile.commands.json file (or a .ccls file) at the root of your project.

For this, nothing could be easier. If like me you use a CMakeLists.txt file for all your C++ projects, then you just need to install the cmake package on your operating system. From then on, the compile.commands.json file is generated with the following commands:

cmake -H. -BDebug -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=YES
ln -s Debug/compile_commands.json

Finally, if you follow Google’s C/++ conventions like I do, the google-c-style package changes some default values to ensure that you follow these conventions as much as possible.

(use-package google-c-style
  :hook (((c-mode c++-mode) . google-set-c-style)
         (c-mode-common . google-make-newline-indent)))

CMake

CMake is a cross-platform build system generator. For its support with GNU Emacs, the cmake-mode package with the installation of the cmake-language-server LSP server is sufficient. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package cmake-mode
  :hook (cmake-mode . lsp-deferred)
  :mode ("CMakeLists\\.txt\\'" "\\.cmake\\'"))

To better manage syntax colors, especially with respect to function arguments, it is visually useful to install cmake-font-lock.

(use-package cmake-font-lock
  :hook (cmake-mode . cmake-font-lock-activate))

Finally, to compile with CMake in C++, I use cmake-ide, by indicating where the CMakeLists.txt file is present in the project.

(use-package cmake-ide
  :after projectile
  :init (cmake-ide-setup)
  :hook (c++-mode . my/cmake-ide-find-project)
  :preface
  (defun my/cmake-ide-find-project ()
    "Find the directory of the project for cmake-ide."
    (with-eval-after-load 'projectile
      (setq cmake-ide-project-dir (projectile-project-root))
      (setq cmake-ide-build-dir (concat cmake-ide-project-dir "build")))
    (setq cmake-ide-compile-command
          (concat "cd " cmake-ide-build-dir " && cmake .. && make"))
    (cmake-ide-load-db))

  (defun my/switch-to-compilation-window ()
    "Switch to the *compilation* buffer after compilation."
    (other-window 1))
  :bind ([remap comment-region] . cmake-ide-compile)
  :config (advice-add 'cmake-ide-compile :after #'my/switch-to-compilation-window))

CSV

For my viewing pleasure, csv-mode provides a color syntax when editing CSV files.

(use-package csv-mode :mode ("\\.\\(csv\\|tsv\\)\\'"))

Dart and Flutter

For the few times I have to develop in Dart, dart-mode with lsp-dart as server LSP is great! To use it, do not forget to configure the LSP package and to install dart with your system package manager.

(use-package dart-mode
  :after projectile
  :mode "\\.dart\\'"
  :config
  (add-to-list 'projectile-project-root-files-bottom-up "pubspec.yaml")
  (add-to-list 'projectile-project-root-files-bottom-up "BUILD"))

To configure lsp-dart, nothing difficult. Remember to indicate the path to the Dart SDK dir. Finally, if you use Flutter, do not forget to install it too with your system package manager.

(use-package lsp-dart
  :hook (dart-mode . lsp-deferred)
  :custom
  (lsp-dart-dap-flutter-hot-reload-on-save t)
  (lsp-dart-sdk-dir "/opt/flutter/bin/cache/dart-sdk/"))

Dockerfile

I often use Docker with Dockerfile. To support it with LSP and GNU Emacs, the package dockerfile-mode with dockerfile-language-server-nodejs (dockerfile-ls) as LSP server is enough. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package dockerfile-mode :delight "δ" :mode "Dockerfile\\'")

EPUB

Sometimes I have to read digital books in EPUB format. The nov package allows to open this kind of file.

(use-package nov
  :mode ("\\.epub\\'" . nov-mode)
  :custom (nov-text-width 75))

Gnuplot

Whether professionally or personally, it is often necessary to visualize your data in a quality graph. Gnuplot is the perfect tool for this and the gnuplot package allows to support this tool with GNU Emacs. To use gnuplot, do not forget to install it with your system package manager.

(use-package gnuplot
  :mode "\\.\\(gp\\|gpi\\|plt\\)'"
  :bind (:map gnuplot-mode-map
              ("C-c C-c".  gnuplot-send-buffer-to-gnuplot)))

INI

To modify the INI files, you need to install the ini-mode package.

(use-package ini-mode :mode "\\.ini\\'")

Java

To support Java with GNU Emacs, I use lsp-java as LSP client with Eclipse JDT Language Server (jdtls) as LSP server. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager. If the LSP server is not yet installed, LSP will install it in the ~/.emacs.d/.cache/lsp/eclipse.jdt.ls/ directory. Personally, I like to follow XDG conventions by moving the LSP server to a more appropriate location (e.g., ~/.local/lib/eclipse.jdt.ls).

(use-package lsp-java
  :hook (java-mode . lsp-deferred)
  :custom (lsp-java-server-install-dir
           (expand-file-name (format "%s/eclipse.jdt.ls/" xdg-lib))))

Most of my Java projects are made with gradle. From then on, gradle-mode with the configuration below allows me to compile my Java project with gradle easily.

(use-package gradle-mode
  :hook (java-mode . gradle-mode)
  :preface
  (defun my/switch-to-compilation-window ()
    "Switch to the *compilation* buffer after compilation."
    (other-window 1))
  :bind (:map gradle-mode-map
              ("C-c C-c" . gradle-build)
              ("C-c C-t" . gradle-test))
  :config
  (advice-add 'gradle-build :after #'my/switch-to-compilation-window)
  (advice-add 'gradle-test :after #'my/switch-to-compilation-window))

JavaScript

JavaScript is one of those languages that needs a bit of setup time to get some stability with GNU Emacs. By default GNU Emacs uses js-mode as the major mode for JavaScript buffers. However, I prefer to use js2-mode which is an enhanced version of js-mode. This package offers a better syntax highlighting and proposes many other features.

As LSP server I use typescript-language-server (ts-ls) which is the one recommended by the LSP mode community. To use the LSP server, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager

(use-package js2-mode
  :ensure flycheck
  :mode "\\.js\\'"
  :hook ((js2-mode . js2-imenu-extras-mode)
         (js2-mode . prettier-js-mode))
  :custom (js-indent-level 2)
  :config (flycheck-add-mode 'javascript-eslint 'js2-mode))

I like to use prettier to get my TypeScript code clean. To use it, do not forget to install it with your package manager.

(use-package prettier-js
  :delight
  :custom (prettier-js-args '("--print-width" "100"
                              "--single-quote" "true"
                              "--trailing-comma" "all")))

To get additional refactoring functions, I use the js2-refactor package. This package also allows me to use the js2r-kill commands which easily delete the implementation of a function.

(use-package js2-refactor
  :hook (js2-mode . js2-refactor-mode)
  :bind (:map js2-mode-map
              ("C-k" . js2r-kill)
              ("M-." . lsp-find-definition)))

NOTE: I have long used xref-js2 to navigate through definitions and references in JavaScript. However, lsp-mode now already provides this functionality for us.

Finally, I sometimes take a look at the generated yarn.lock file. To have a nice syntax color and avoid modifying it, the yarn-mode package is perfect.

(use-package yarn-mode :mode "yarn\\.lock\\'")

JSON

JSON is probably the data format I use the most in the web. That’s why its setup below is a bit more advanced. As LSP server I prefer vscode-json-languageserver. To use it, make sure you install it with your package manager and to configure the LSP package.

(use-package json-mode
  :delight "J"
  :mode "\\.json\\'"
  :hook (before-save . my/json-mode-before-save-hook)
  :preface
  (defun my/json-mode-before-save-hook ()
    (when (eq major-mode 'json-mode)
      (json-pretty-print-buffer)))

  (defun my/json-array-of-numbers-on-one-line (encode array)
    "Print the arrays of numbers in one line."
    (let* ((json-encoding-pretty-print
            (and json-encoding-pretty-print
                 (not (loop for x across array always (numberp x)))))
           (json-encoding-separator (if json-encoding-pretty-print "," ", ")))
      (funcall encode array)))
  :config (advice-add 'json-encode-array :around #'my/json-array-of-numbers-on-one-line))

LaTeX

Being a lover of beautiful writing, it is important for me to have a stable LaTeX environment. To have access to this stability, I use the tex-mode built-in package and texlab as LSP server. To use it, make sure you install it with your package manager and to configure the LSP package.

With tex-mode we need to ensure to install AUCTeX, which is a built-in package for writing and formatting TeX files in GNU Emacs. With AUCTeX you can for example use the =TeX-command-master (C-c C-c) command to compile your TeX files and the LaTeX-environment (C-c C-e) command to insert a LaTeX environment.

(use-package tex
  :ensure auctex
  :preface
  (defun my/switch-to-help-window (&optional ARG REPARSE)
    "Switches to the *TeX Help* buffer after compilation."
    (other-window 1))
  :hook (LaTeX-mode . reftex-mode)
  :bind (:map TeX-mode-map
              ("C-c C-o" . TeX-recenter-output-buffer)
              ("C-c C-l" . TeX-next-error)
              ("M-[" . outline-previous-heading)
              ("M-]" . outline-next-heading))
  :custom
  (TeX-auto-save t)
  (TeX-byte-compile t)
  (TeX-clean-confirm nil)
  (TeX-master 'dwim)
  (TeX-parse-self t)
  (TeX-PDF-mode t)
  (TeX-source-correlate-mode t)
  (TeX-view-program-selection '((output-pdf "PDF Tools")))
  :config
  (advice-add 'TeX-next-error :after #'my/switch-to-help-window)
  (advice-add 'TeX-recenter-output-buffer :after #'my/switch-to-help-window)
  ;; the ":hook" doesn't work for this one... don't ask me why.
  (add-hook 'TeX-after-compilation-finished-functions 'TeX-revert-document-buffer))

Also, I like to use a TeX engine that can handle Unicode and use the font of my choice.

(setq-default TeX-engine 'xetex)

By default, LSP mode uses lsp-tex as the LSP client for LaTeX. However, I prefer to use lsp-latex which fully supports texlab (cf. ROCKTAKEY/lsp-latex#26)

(use-package lsp-latex
  :if (executable-find "texlab")
  ;; To properly load `lsp-latex', the `require' instruction is important.
  :hook (LaTeX-mode . (lambda ()
                        (require 'lsp-latex)
                        (lsp-deferred)))
  :custom (lsp-latex-build-on-save t))

To easier deal with \label, \ref, and \cite commands in LaTeX, I use the reftex built-in package.

(use-package reftex
  :ensure nil
  :custom
  (reftex-save-parse-info t)
  (reftex-use-multiple-selection-buffers t))

Finally, it is often useful to put our hands in a bibliography in LaTeX. The built-in package bibtex improves the visual and provides several commands.

(use-package bibtex
  :ensure nil
  :preface
  (defun my/bibtex-fill-column ()
    "Ensure that each entry does not exceed 120 characters."
    (setq fill-column 120))
  :hook ((bibtex-mode . lsp-deferred)
         (bibtex-mode . my/bibtex-fill-column)))

Lua

I rarely program in Lua, but when I do, lua-mode with lua-language-server as LSP server satisfies me amply. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package lua-mode :delight "Λ" :mode "\\.lua\\'")

Markdown

To edit my files in Markdown, I use markdown-mode with unified-language-server as LSP server. To use it, do not forget to configure the LSP package and to install this LSP server with your system package manager. Added to that, to convert Markdown files, you can also install pandoc with your package manager system.

(use-package markdown-mode
  :delight "μ"
  :mode ("\\.\\(md\\|markdown\\)\\'")
  :custom (markdown-command "/usr/bin/pandoc"))

Finally, it is always good to have a preview of the Markdown rendering. The markdown-preview-mode package allows this.

(use-package markdown-preview-mode
  :commands markdown-preview-mode
  :custom
  (markdown-preview-javascript
   (list (concat "https://github.com/highlightjs/highlight.js/"
                 "9.15.6/highlight.min.js")
         "<script>
            $(document).on('mdContentChange', function() {
              $('pre code').each(function(i, block)  {
                hljs.highlightBlock(block);
              });
            });
          </script>"))
  (markdown-preview-stylesheets
   (list (concat "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/"
                 "3.0.1/github-markdown.min.css")
         (concat "https://github.com/highlightjs/highlight.js/"
                 "9.15.6/styles/github.min.css")

         "<style>
            .markdown-body {
              box-sizing: border-box;
              min-width: 200px;
              max-width: 980px;
              margin: 0 auto;
              padding: 45px;
            }

            @media (max-width: 767px) { .markdown-body { padding: 15px; } }
          </style>")))

PHP and HTML

I hope to never develop in PHP again. The few times I had to do it, I use mainly use web-mode with intelephense (iph) as LSP server for PHP and vscode-html-languageserver (html-ls) as LSP server for HTML. To use them, do not forget to configure the LSP package and to install these LSP servers through LSP mode or with your system package manager.

NOTE: to deal with HTML, I rarely use html-mode which does not handle HTML well in the presence of CSS and JavaScript.

(use-package web-mode
  :delight ""
  :preface
  (defun enable-minor-mode (my-pair)
    "Enable minor mode if filename match the regexp."
    (if (buffer-file-name)
        (if (string-match (car my-pair) buffer-file-name)
            (funcall (cdr my-pair)))))
  :mode ("\\.\\(html\\|jsx\\|php\\)\\'" . web-mode)
  :hook (web-mode . (lambda ()
                      (enable-minor-mode
                       '("\\.jsx?\\'" . prettier-js-mode))))
  :custom
  (web-mode-attr-indent-offset 2)
  (web-mode-block-padding 2)
  (web-mode-css-indent-offset 2)
  (web-mode-code-indent-offset 2)
  (web-mode-comment-style 2)
  (web-mode-enable-current-element-highlight t)
  (web-mode-markup-indent-offset 2))

PlantUML

To make my UML diagrams with PlantUML, I use plantuml-mode. Make sure you download the PlantUML compiled JAR and to indicate the path (e.g., ~/.local/lib/plantuml.jar) of this JAR file with the plantuml-jar-path variable.

(use-package plantuml-mode
  :mode ("\\.\\(plantuml\\|puml\\)\\'")
  :custom (plantuml-jar-path
           (expand-file-name (format "%s/plantuml.jar" xdg-lib))))

Python

Python with GNU Emacs is one of the best supported languages. By using python-mode and pyright as LSP server, it’s fun to develop in Python. With python-mode I like to add some bindings to speed up the code code navigation in Python. Besides that, I use autoflake to remove unused imports and variables.

(use-package python
  :ensure flycheck
  :delight "π"
  :preface
  (defun python-remove-unused-imports()
    "Remove unused imports and unused variables with autoflake."
    (interactive)
    (if (executable-find "autoflake")
        (progn
          (shell-command (format "autoflake --remove-all-unused-imports -i %s"
                                 (shell-quote-argument (buffer-file-name))))
          (revert-buffer t t t))
      (warn "[✗] python-mode: Cannot find autoflake executable.")))
  :bind (:map python-mode-map
              ("M-[" . python-nav-backward-block)
              ("M-]" . python-nav-forward-block)
              ("M-|" . python-remove-unused-imports))
  :custom
  (flycheck-pylintrc "~/.pylintrc")
  (flycheck-python-pylint-executable "/usr/bin/pylint"))

I have tried several LSP servers. I have experienced that mspyls is faster than pylsp, but mspyls has a memory leakage and became depreciated in favor of pyright. To configure pyright with GNU Emacs, the lsp-pyright package is enough.

(use-package lsp-pyright
  :if (executable-find "pyright")
  ;; To properly load `lsp-pyright', the `require' instruction is important.
  :hook (python-mode . (lambda ()
                         (require 'lsp-pyright)
                         (lsp-deferred)))
  :custom
  (lsp-pyright-python-executable-cmd "python3")
  (lsp-pyright-venv-path "~/.cache/pypoetry/virtualenvs/"))

To make sure my Python code is well formatted, I use black. Feel free to install it in your virtual environment or directly on your system.

(use-package blacken
  :delight
  :hook (python-mode . blacken-mode)
  :custom (blacken-line-length 79))

To sort my Python imports, py-isort does a good job. Also, do not forget to install in your virtual environment or directly on your system.

(use-package py-isort
  :hook ((before-save . py-isort-before-save)
         (python-mode . pyvenv-mode)))

I use a single virtual environment for all my Python projects. The combination of venv with pyvenv does the job well. When I encounter a Python buffer, my virtual environment activates and stays activated even after I finish working in Python. A better behavior would be to define a function through a kill-buffer-hook that would call the pyvenv-deactivate command when all Python buffers are closed.

(use-package pyvenv
  :after python
  :custom
  (pyvenv-default-virtual-env-name (expand-file-name (format "%s/myenv/" xdg-data)))
  (pyvenv-workon (expand-file-name (format "%s/myenv/" xdg-data)))
  :config (pyvenv-tracking-mode))

Finally, to better manage the different versions of Python through projects, I use pyenv through pyenv-mode-mode. To use it, make sure you have pyenv installed on your system.

(use-package pyenv-mode
  :hook ((python-mode . pyenv-mode)
         (projectile-switch-project . projectile-pyenv-mode-set))
  :custom (pyenv-mode-set "3.8.5")
  :preface
  (defun projectile-pyenv-mode-set ()
    "Set pyenv version matching project name."
    (let ((project (projectile-project-name)))
      (if (member project (pyenv-mode-versions))
          (pyenv-mode-set project)
        (pyenv-mode-unset)))))

SQL

For handling SQL files, the sql-mode built-in package of GNU Emacs with the sqls LSP server does the job.

(use-package sql-mode
  :ensure nil
  :mode "\\.sql\\'")

Finally, I use sql-indent to better manage the indentations of my SQL queries.

(use-package sql-indent
  :delight sql-mode "Σ"
  :hook (sql-mode . sqlind-minor-mode))

Style Sheet

GNU Emacs already has built-in packages for style sheet languages (e.g., CSS, LESS, and SCSS). Therefore, installing an appropriate LSP server and making sure that the variables are properly indented is more than enough. For the LSP server, I use vscode-css-languageserver (css-ls) although it does not support the LESS language. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package css-mode
  :ensure flycheck
  :mode "\\.css\\'"
  :custom (css-indent-offset 2)
  :config (flycheck-stylelintrc "~/.stylelintrc.json"))

TypeScript

For my TypeScript adventures, the typescript-mode package and the typescript-language-server (ts-ls) LSP server are more than enough. To use the LSP server, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager if it is not already done.

Finally, I also use prettier-js to ensure proper indentation of my code. To enable it, you must install the prettier package with your package manager system. Concerning the configuration of prettier, this is done in the JavaScript section.

(use-package typescript-mode
  :ensure flycheck
  :hook ((typescript-mode . prettier-js-mode)
         (typescript-mode . lsp-deferred))
  :mode ("\\.\\(ts\\|tsx\\)\\'")
  :custom
  ;; TSLint is depreciated in favor of ESLint.
  (flycheck-disable-checker 'typescript-tslint)
  (lsp-clients-typescript-server-args '("--stdio" "--tsserver-log-file" "/dev/stderr"))
  (typescript-indent-level 2)
  :config
  (flycheck-add-mode 'javascript-eslint 'typescript-mode))

Vue.js

God keep me away from Vue.js. The few times I had to develop with it, I pulled my hair out. For its support, I managed to get some semblance of stability with the vue-mode package and the vue-language-server (vls) LSP. However, the CSS support with vue-mode is not great. To use the LSP server, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package vue-mode
  :delight "V"
  :hook (vue-mode . lsp-deferred)
  :mode "\\.vue\\'"
  :custom (vue-html-extra-indent 2))

XML

To manage XML related files the nxml-mode package built into GNU Emacs and the LemMinX (xmlls) LSP server does the job. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package nxml-mode
  :ensure nil
  :hook (nxml-mode . lsp-deferred)
  :mode ("\\.\\(xml\\|xsd\\|wsdl\\)\\'"))

YAML

When I have to develop through YAML files, the yaml-mode package with the yaml-language-server LSP server meets my needs. To use it, do not forget to configure the LSP package and to install this LSP server through LSP mode or with your system package manager.

(use-package yaml-mode
  :delight "ψ"
  :hook (yaml-mode . lsp-deferred)
  :mode ("\\.\\(yaml\\|yml\\)\\'"))

Advanced Configuration

This section contains the configuration of anecdotal GNU Emacs packages.

Browser

By default GNU Emacs provides the browse-url package to open a browser based on a query entered via our lovely text editor. Personally, I still prefer to directly use my browser, namely qutebrowser. However, nothing prevents us from configuring the browse-url package.

(use-package browse-url
  :ensure nil
  :custom
  (browse-url-browser-function 'browse-url-generic)
  (browse-url-generic-program "qutebrowser"))

Calculator

On a daily basis, you may have to make calculations or conversions from one unit to another. With the calc build-in package (C-x * c) it is possible to do this easily with GNU Emacs.

Among the useful commands within calc that you should know:

  • calc-algebraic-entry: allows you to enter a value and its unit to later to convert it or associate it with an operation.
  • calc-convert-units (u c): converts the output of an algebraic entry (e.g., 25 m25000 mm).
  • calc-simplify-units (u s): simplifies the output of an algebraic entry (e.g., 5 m + 23 m5.023 m).
  • calc-view-units-table (u V): displays a table of units supported by calc.
(use-package calc
  :ensure nil
  :custom
  (math-additional-units
   '((GiB "1024 * MiB" "Giga Byte")
     (MiB "1024 * KiB" "Mega Byte")
     (KiB "1024 * B" "Kilo Byte")
     (B nil "Byte")
     (Gib "1024 * Mib" "Giga Bit")
     (Mib "1024 * Kib" "Mega Bit")
     (Kib "1024 * b" "Kilo Bit")
     (b "B / 8" "Bit")))
  ;; Resets the calc's cache.
  (math-units-table nil))

Calendar

It is important to know the vacations and remember some official dates to better organize your vacation planning and gift buying. The built-in calendar and holidays packages are there to remind these dates to us.

(use-package calendar
  :ensure nil
  :bind ("C-c 0" . calendar)
  :custom
  (calendar-mark-holidays-flag t)
  (calendar-week-start-day 1))

By default GNU Emacs fills in too many dates and most of the ones I am interested in are not included. Especially those specific to vacations in my country. For this reason, I specify to holidays to hide some dates and add some others.

(use-package holidays
  :ensure nil
  :custom
  (holiday-bahai-holidays nil)
  (holiday-hebrew-holidays nil)
  (holiday-islamic-holidays nil)
  (holiday-oriental-holidays nil)
  (holiday-christian-holidays
   '((holiday-fixed 1 6 "Epiphany")
     (holiday-fixed 2 2 "Candlemas")
     (holiday-easter-etc -47 "Mardi Gras")
     (holiday-easter-etc 0 "Easter Day")
     (holiday-easter-etc 1 "Easter Monday")
     (holiday-easter-etc 39 "Ascension")
     (holiday-easter-etc 49 "Pentecost")
     (holiday-fixed 8 15 "Assumption")
     (holiday-fixed 11 1 "All Saints' Day")
     (holiday-fixed 11 2 "Day of the Dead")
     (holiday-fixed 11 22 "Saint Cecilia's Day")
     (holiday-fixed 12 1 "Saint Eloi's Day")
     (holiday-fixed 12 4 "Saint Barbara")
     (holiday-fixed 12 6 "Saint Nicholas Day")
     (holiday-fixed 12 25 "Christmas Day")))
  (holiday-general-holidays
   '((holiday-fixed 1 1 "New Year's Day")
     (holiday-fixed 2 14 "Valentine's Day")
     (holiday-fixed 3 8 "International Women's Day")
     (holiday-fixed 10 31 "Halloween")
     (holiday-fixed 11 11 "Armistice of 1918")))
  (holiday-local-holidays
   '((holiday-fixed 5 1 "Labor Day")
     (holiday-float 3 0 0 "Grandmothers' Day")
     (holiday-float 4 4 3 "Secretary's Day")
     (holiday-float 5 0 2 "Mother's Day")
     (holiday-float 6 0 2 "Father's Day")
     (holiday-fixed 7 21 "Belgian National Day"))))

Color Display in Text

To automatically display a color when typing a color or hex code values, the rainbow-mode package is useful.

(use-package rainbow-mode
  :delight
  :hook ((prog-mode text-mode) . rainbow-mode))

Dashboard

Organization is even more important in the 21st century than it was before. What could be better than launching GNU Emacs with a dashboard that lists the tasks of the week with org-agenda and a list of projects we have recently contributed to with projectile. To our delight the dashboard package offers these features and more.

(use-package dashboard
  :custom
  (dashboard-banner-logo-title "With Great Power Comes Great Responsibility!")
  (dashboard-center-content t)
  (dashboard-items '((agenda)
                     (projects . 5)))
  (dashboard-projects-switch-function 'counsel-projectile-switch-project-by-name)
  (dashboard-set-file-icons t)
  (dashboard-set-footer nil)
  (dashboard-set-heading-icons t)
  (dashboard-set-navigator t)
  (dashboard-startup-banner 'logo)
  :config (dashboard-setup-startup-hook))

Displays Available Keybindings in Popup

It is difficult to remember every keyboard shortcuts. The which-key package helps to solve this. I used guide-key in my past days, but which-key is a good replacement.

(use-package which-key
  :defer 0.2
  :delight
  :custom (which-key-idle-delay 0.5)
  :config (which-key-mode))

Documentation

To have a more user-friendly documentation I use the helpful package.

(use-package helpful
  :commands (helpful-at-point
             helpful-callable
             helpful-command
             helpful-function
             helpful-key
             helpful-macro
             helpful-variable)
  :bind
  ([remap display-local-help] . helpful-at-point)
  ([remap describe-function] . helpful-callable)
  ([remap describe-variable] . helpful-variable)
  ([remap describe-symbol] . helpful-symbol)
  ([remap describe-key] . helpful-key)
  ([remap describe-command] . helpful-command))

EditorConfig

It often happens that you have to work on the same project as other developers. To keep a coding style you can use EditorConfig through the editorconfig package.

(use-package editorconfig
  :defer 0.3
  :config (editorconfig-mode))

History

There are times when it is necessary to remember a command. The savehist built-in package allows you to save commands in a file so that you can run them again later.

(use-package savehist
  :ensure nil
  :custom
  (history-delete-duplicates t)
  (history-length 25)
  (savehist-file (expand-file-name (format "%s/emacs/history" xdg-cache)))
  :config (savehist-mode))

Hydra

GNU Emacs has so many commands per mode that it is tedious to remember all the keybindings for quick access. Fortunately, hydra allows you to create menu commands and on the basis of a popup, display the commands you have associated with it.

(use-package hydra
  :bind (("C-c I" . hydra-image/body)
         ("C-c L" . hydra-ledger/body)
         ("C-c M" . hydra-merge/body)
         ("C-c T" . hydra-tool/body)
         ("C-c b" . hydra-btoggle/body)
         ("C-c c" . hydra-clock/body)
         ("C-c e" . hydra-circe/body)
         ("C-c f" . hydra-flycheck/body)
         ("C-c g" . hydra-go-to-file/body)
         ("C-c m" . hydra-magit/body)
         ("C-c o" . hydra-org/body)
         ("C-c p" . hydra-projectile/body)
         ("C-c s" . hydra-spelling/body)
         ("C-c t" . hydra-tex/body)
         ("C-c u" . hydra-upload/body)
         ("C-c w" . hydra-windows/body)))

(use-package major-mode-hydra
  :after hydra
  :preface
  (defun with-alltheicon (icon str &optional height v-adjust face)
    "Display an icon from all-the-icon."
    (s-concat (all-the-icons-alltheicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))

  (defun with-faicon (icon str &optional height v-adjust face)
    "Display an icon from Font Awesome icon."
    (s-concat (all-the-icons-faicon icon ':v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))

  (defun with-fileicon (icon str &optional height v-adjust face)
    "Display an icon from the Atom File Icons package."
    (s-concat (all-the-icons-fileicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))

  (defun with-octicon (icon str &optional height v-adjust face)
    "Display an icon from the GitHub Octicons."
    (s-concat (all-the-icons-octicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str)))

Hydra / BToggle

Group a lot of commands.

(pretty-hydra-define hydra-btoggle
  (:hint nil :color amaranth :quit-key "q" :title (with-faicon "toggle-on" "Toggle" 1 -0.05))
  ("Basic"
   (("a" abbrev-mode "abbrev" :toggle t)
    ("h" global-hungry-delete-mode "hungry delete" :toggle t))
   "Coding"
   (("e" electric-operator-mode "electric operator" :toggle t)
    ("F" flyspell-mode "flyspell" :toggle t)
    ("f" flycheck-mode "flycheck" :toggle t)
    ("l" lsp-mode "lsp" :toggle t)
    ("s" smartparens-mode "smartparens" :toggle t))
   "UI"
   (("i" ivy-rich-mode "ivy-rich" :toggle t))))

Hydra / Circe

Group circe commands.

(pretty-hydra-define hydra-circe
  (:hint nil :color teal :quit-key "q" :title (with-faicon "comments-o" "Circe" 1 -0.05))
  ("Action"
   (("c" circe "connect")
    ("r" circe-reconnect "reconnect")
    ("u" my/circe-count-nicks "user"))))

Hydra / Clock

Group clock commands.

(pretty-hydra-define hydra-clock
  (:hint nil :color teal :quit-key "q" :title (with-faicon "clock-o" "Clock" 1 -0.05))
  ("Action"
   (("c" org-clock-cancel "cancel")
    ("d" org-clock-display "display")
    ("e" org-clock-modify-effort-estimate "effort")
    ("i" org-clock-in "in")
    ("j" org-clock-goto "jump")
    ("o" org-clock-out "out")
    ("p" org-pomodoro "pomodoro")
    ("r" org-clock-report "report"))))

Hydra / Flycheck

Group Flycheck commands.

(pretty-hydra-define hydra-flycheck
  (:hint nil :color teal :quit-key "q" :title (with-faicon "plane" "Flycheck" 1 -0.05))
  ("Checker"
   (("?" flycheck-describe-checker "describe")
    ("d" flycheck-disable-checker "disable")
    ("m" flycheck-mode "mode")
    ("s" flycheck-select-checker "select"))
   "Errors"
   (("<" flycheck-previous-error "previous" :color pink)
    (">" flycheck-next-error "next" :color pink)
    ("f" flycheck-buffer "check")
    ("l" flycheck-list-errors "list"))
   "Other"
   (("M" flycheck-manual "manual")
    ("v" flycheck-verify-setup "verify setup"))))

Hydra / Go To

Group jump-to-files commands.

(pretty-hydra-define hydra-go-to-file
  (:hint nil :color teal :quit-key "q" :title (with-octicon "file-symlink-file" "Go To" 1 -0.05))
  ("Agenda"
   (("ac" (find-file "~/.personal/agenda/contacts.org") "contacts")
    ("ah" (find-file "~/.personal/agenda/home.org") "home")
    ("ai" (find-file "~/.personal/agenda/inbox.org") "inbox")
    ("ag" (find-file "~/.personal/agenda/goals.org") "goals")
    ("ap" (find-file "~/.personal/agenda/people.org") "people")
    ("ar" (find-file "~/.personal/agenda/routine.org") "routine")
    ("aR" (find-file "~/.personal/agenda/review.org") "review")
    ("as" (find-file "~/.personal/agenda/someday.org") "someday")
    ("aw" (find-file "~/.personal/agenda/work.org") "work"))
   "Config"
   (("ca" (find-file (format "%s/sh/aliases" xdg-config)) "aliases")
    ("ce" (find-file "~/.emacs.d/config.org") "emacs")
    ("cE" (find-file (format "%s/sh/environ" xdg-config)) "environ")
    ("cf" (find-file (format "%s/foot/foot.ini" xdg-config)) "foot")
    ("cn" (find-file (format "%s/neofetch/config.conf" xdg-config)) "neofetch")
    ("cq" (find-file (format "%s/qutebrowser/config.py" xdg-config)) "qutebrowser")
    ("cr" (find-file (format "%s/ranger/rc.conf" xdg-config)) "ranger")
    ("cs" (find-file (format "%s/sway/config" xdg-config)) "sway")
    ("ct" (find-file (format "%s/tmux/tmux.conf" xdg-config)) "tmux")
    ("cw" (find-file (format "%s/waybar/config" xdg-config)) "waybar")
    ("cx" (find-file (format "%s/sh/xdg" xdg-config)) "xdg"))
   "Item"
   (("ib" (find-file "~/.personal/items/books.org") "book")
    ("il" (find-file "~/.personal/items/learning.org") "learning")
    ("im" (find-file "~/.personal/items/movies.org") "movies")
    ("ip" (find-file "~/.personal/items/purchases.org") "purchases"))
   "Notes"
   (("na" (find-file (format "~/.personal/notes/affirmations.pdf" xdg-config)) "affirmations"))
   "Other"
   (("ol" (find-file "~/.personal/other/long-goals.org") "long-terms goals")
    ("os" (find-file "~/.personal/other/short-goals.org") "short-terms goals")
    ("ou" (find-file "~/.personal/other/usb.org") "usb"))))

Hydra / Image

Group images commands.

(pretty-hydra-define hydra-image
  (:hint nil :color pink :quit-key "q" :title (with-faicon "file-image-o" "Images" 1 -0.05))
  ("Action"
   (("r" image-rotate "rotate")
    ("s" image-save "save" :color teal))
    "Zoom"
    (("-" image-decrease-size "out")
     ("+" image-increase-size "in")
     ("=" image-transform-reset "reset"))))

Hydra / Ledger

Group Ledger commands.

(pretty-hydra-define hydra-ledger
  (:hint nil :color teal :quit-key "q" :title (with-faicon "usd" "Ledger" 1 -0.05))
  ("Action"
   (("b" leadger-add-transaction "add")
    ("c" ledger-mode-clean-buffer "clear")
    ("i" ledger-copy-transaction-at-point "copy")
    ("s" ledger-delete-current-transaction "delete")
    ("r" ledger-report "report"))))

Hydra / Magit

Group Magit commands.

(pretty-hydra-define hydra-magit
  (:hint nil :color teal :quit-key "q" :title (with-octicon "mark-github" "Magit" 1 -0.05))
  ("Action"
   (("b" magit-blame "blame")
    ("c" magit-clone "clone")
    ("i" magit-init "init")
    ("l" magit-log-buffer-file "commit log (current file)")
    ("L" magit-log-current "commit log (project)")
    ("s" magit-status "status"))))

Hydra / Merge

Group Merge commands.

(pretty-hydra-define hydra-merge
  (:hint nil :color pink :quit-key "q" :title (with-octicon "mark-github" "Magit" 1 -0.05))
  ("Move"
   (("n" smerge-next "next")
    ("p" smerge-prev "previous"))
   "Keep"
   (("RET" smerge-keep-current "current")
    ("a" smerge-keep-all "all")
    ("b" smerge-keep-base "base")
    ("l" smerge-keep-lower "lower")
    ("u" smerge-keep-upper "upper"))
   "Diff"
   (("<" smerge-diff-base-upper "upper/base")
    ("=" smerge-diff-upper-lower "upper/lower")
    (">" smerge-diff-base-lower "base/lower")
    ("R" smerge-refine "redefine")
    ("E" smerge-ediff "ediff"))
   "Other"
   (("C" smerge-combine-with-next "combine")
    ("r" smerge-resolve "resolve")
    ("k" smerge-kill-current "kill current"))))

Hydra / Org

Group Org commands.

(pretty-hydra-define hydra-org
  (:hint nil :color teal :quit-key "q" :title (with-fileicon "org" "Org" 1 -0.05))
  ("Action"
   (("A" my/org-archive-done-tasks "archive")
    ("a" org-agenda "agenda")
    ("c" org-capture "capture")
    ("d" org-decrypt-entry "decrypt")
    ("i" org-insert-link-global "insert-link")
    ("j" org-capture-goto-last-stored "jump-capture")
    ("k" org-cut-subtree "cut-subtree")
    ("o" org-open-at-point-global "open-link")
    ("r" org-refile "refile")
    ("s" org-store-link "store-link")
    ("t" org-show-todo-tree "todo-tree"))))

Hydra / Projectile

Group Projectile commands.

(pretty-hydra-define hydra-projectile
  (:hint nil :color teal :quit-key "q" :title (with-faicon "rocket" "Projectile" 1 -0.05))
  ("Buffers"
   (("b" projectile-switch-to-buffer "list")
    ("k" projectile-kill-buffers "kill all")
    ("S" projectile-save-project-buffers "save all"))
   "Find"
   (("d" projectile-find-dir "directory")
    ("D" projectile-dired "root")
    ("f" projectile-find-file "file")
    ("p" consult-projectile "project"))
   "Other"
   (("i" projectile-invalidate-cache "reset cache"))
   "Search"
   (("r" projectile-replace "replace")
    ("R" projectile-replace-regexp "regexp replace")
    ("s" consult-git-grep "search"))))

Hydra / Roam

Group org-roam commands.

(pretty-hydra-define hydra-notes
  (:hint nil :color teal :quit-key "q" :title (with-octicon "pencil" "Notes" 1 -0.05))
  ("Notes"
   (("c" org-roam-dailies-capture-today "capture")
    ("C" org-roam-dailies-capture-tomorrow "capture tomorrow")
    ("g" org-roam-graph "graph")
    ("f" org-roam-node-find "find")
    ("i" org-roam-node-insert "insert"))
   "Go To"
   ((">" org-roam-dailies-goto-next-note "next note")
    ("<" org-roam-dailies-goto-previous-note "previous note")
    ("d" org-roam-dailies-goto-date "date")
    ("t" org-roam-dailies-goto-today "today")
    ("T" org-roam-dailies-goto-tomorrow "tomorrow")
    ("y" org-roam-dailies-goto-yesterday "yesterday"))))

Hydra / Spelling

Group spelling commands.

(pretty-hydra-define hydra-spelling
  (:hint nil :color teal :quit-key "q" :title (with-faicon "magic" "Spelling" 1 -0.05))
  ("Checker"
   (("c" langtool-correct-buffer "correction")
    ("C" langtool-check-done "clear")
    ("d" ispell-change-dictionary "dictionary")
    ("l" (message "Current language: %s (%s)" langtool-default-language ispell-current-dictionary) "language")
    ("s" my/switch-language "switch")
    ("w" wiki-summary "wiki"))
   "Errors"
   (("<" flyspell-correct-previous "previous" :color pink)
    (">" flyspell-correct-next "next" :color pink)
    ("f" langtool-check "find"))))

Hydra / TeX

Group TeX commands.

(pretty-hydra-define hydra-tex
  (:hint nil :color teal :quit-key "q" :title (with-fileicon "tex" "LaTeX" 1 -0.05))
  ("Action"
   (("g" reftex-goto-label "goto")
    ("r" reftex-query-replace-document "replace")
    ("s" counsel-rg "search")
    ("t" reftex-toc "table of content"))))

Hydra / Tool

Group Tool commands.

(pretty-hydra-define hydra-tool
  (:hint nil :color teal :quit-key "q" :title (with-faicon "briefcase" "Tool" 1 -0.05))
  ("Network"
   (("c" ipcalc "subnet calculator")
    ("i" ipinfo "ip info"))))

Hydra / TypeScript

Group TypeScript commands.

(defhydra hydra-typescript (:color blue)
  "
  ^
  ^TypeScript^          ^Do^
  ^──────────^──────────^──^───────────
  _q_ quit             _b_ back
  ^^                   _e_ errors
  ^^                   _j_ jump
  ^^                   _r_ references
  ^^                   _R_ restart
  ^^                   ^^
  "
  ("q" nil)
  ("b" tide-jump-back)
  ("e" tide-project-errors)
  ("j" tide-jump-to-definition)
  ("r" tide-references)
  ("R" tide-restart-server))

Hydra / Upload

Group upload commands.

(pretty-hydra-define hydra-upload
  (:hint nil :color teal :quit-key "q" :title (with-faicon "cloud-upload" "Upload" 1 -0.05))
  ("Action"
   (("b" webpaste-paste-buffer "buffer")
    ("i" imgbb-upload "image")
    ("r" webpaste-paste-region "region"))))

Hydra / Windows

Group window-related commands.

(pretty-hydra-define hydra-windows
  (:hint nil :forein-keys warn :quit-key "q" :title (with-faicon "windows" "Windows" 1 -0.05))
  ("Window"
   (("b" balance-windows "balance")
    ("c" centered-window-mode "center")
    ("i" enlarge-window "heighten")
    ("j" shrink-window-horizontally "narrow")
    ("k" shrink-window "lower")
    ("u" winner-undo "undo")
    ("r" winner-redo "redo")
    ("l" enlarge-window-horizontally "widen")
    ("s" switch-window-then-swap-buffer "swap" :color teal))
   "Zoom"
   (("-" text-scale-decrease "out")
    ("+" text-scale-increase "in")
    ("=" (text-scale-increase 0) "reset"))))

Icons

To integrate icons with doom-modeline and other packages, all-the-icons is the best package that you can have with GNU Emacs. To download the icons on your machine, you must run the all-the-icons-install-fonts command when you install this package.

(use-package all-the-icons
  :if (display-graphic-p)
  :commands all-the-icons-install-fonts
  :config (unless (find-font (font-spec :name "all-the-icons"))
            (all-the-icons-install-fonts t)))

Indentations

By default GNU Emacs auto-indents the code while typing with electric-indent-mode, but the indentation made is wrong when moving blocks, transposing lines and so on. The aggresive-indent package is an improved version of the previous mode, allowing a code to be always indented.

(use-package aggressive-indent
  :custom (aggressive-indent-comments-too t))

With code nesting, it is important to always have a discrete visual on the indentation of our code. The highlight-indent-guides package allows you to see at a glance if an indentation is bad through block highlight.

(use-package highlight-indent-guides
  :hook (prog-mode . highlight-indent-guides-mode)
  :custom (highlight-indent-guides-method 'character))

IRC

IRC is the best way for me to get a quick answer to a question and to learn from more competent people than me on a subject.

To start using IRC, you will need to create an account. Once your account is created, you can store it in your .authinfo.gpg file and add this file to the auth-sources variable. To add your IRC account in the authinfo.gpg file, add in the following line with your credentials:

machine irc.libera.chat login <nickname> password <password> port 6697

After spending many years on ERC, I decided to move to circe as a IRC client which I find more user-friendly.

(use-package circe
  :commands circe
  :preface
  (defun my/circe-count-nicks ()
    "Display the number of users connected on the current channel."
    (interactive)
    (when (eq major-mode 'circe-channel-mode)
      (message "%i users are online on %s."
               (length (circe-channel-nicks)) (buffer-name))))

  (defun my/circe-fetch-password (&rest params)
    "Fetch the NickServ password for an username."
    (require 'auth-source)
    (let ((match (car (apply 'auth-source-search params))))
      (if match
          (let ((secret (plist-get match :secret)))
            (if (functionp secret)
                (funcall secret)
              secret))
        (error "[✗] Password not found for %S" params))))

  (defun my/circe-nickserv-password (server)
    "Fetch the NickServ password for the Libera Chat."
    (my/circe-fetch-password :login "rememberYou" :machine "irc.libera.chat"))
  :custom
  (circe-default-part-message nil)
  (circe-default-quit-message nil)
  (circe-format-say (format "{nick:+%ss}: {body}" 8))
  (circe-network-options
   '(("Libera Chat"
      :nick "rememberYou"
      :tls t
      :port 6697
      :server-buffer-name "⇄ Libera Chat"
      :channels (:after-auth "#archlinux" "#bash" "#emacs" "#linux" "#python" "#qutebrowser" "#sway")
      :nickserv-password my/circe-nickserv-password)))
  (circe-reduce-lurker-spam t)
  (circe-use-cycle-completion t)
  (lui-flyspell-p t)
  :config
  (circe-lagmon-mode)
  (enable-circe-color-nicks)
  (enable-circe-display-images))

Finally if like me you would like to be informed by desktop notifications when you are mentioned on IRC, the circe-notifications package allows you to do that.

(use-package circe-notifications
  :after circe
  :hook (circe-server-connected . enable-circe-notifications))

Linters

To integrate syntax checking during development, Flycheck lints warnings and errors directly within buffers. To use it, you need to install the flycheck package on GNU Emacs and install the necessary linters for the programming languages you use, via your system package manager.

NOTE: The GNU Emacs community has produced a number of extensions to Flycheck.

(use-package flycheck
  :delight
  :hook (lsp-mode . flycheck-mode)
  :bind (:map flycheck-mode-map
              ("M-'" . flycheck-previous-error)
              ("M-\\" . flycheck-next-error))
  :custom (flycheck-display-errors-delay .3))

Mails

After trying the gnus built-in package that I found too old and notmuch that in my opinion lacks features like the ability to delete some emails and be able to write emails easily with org, I finally found my happiness with mu4e.

I used offlineimap, but I find it slower than mbsync. This is why I now use mbsync to synchronize emails on IMAP server with local mails dir folder.

NOTE: to use mbsync with your Gmail account, you will need to enable access for less secure apps in your Google account.

mu4e

Before that you can use this configuration, make sure to install mu on your operating system and to create directories corresponding to those in your mailbox. Then, initialize the mu database, by replacing values of --maildir and --my-address with yours.

mu init --maildir ~/mails --my-address=${EMAIL}

Now all that remains is to configure mu4e:

(use-package mu4e
  :ensure nil
  :ensure-system-package mu
  :load-path "/usr/share/emacs/site-lisp/mu4e"
  :commands mu4e
  :hook (mu4e-compose-mode . turn-off-auto-fill)
  :bind (:map mu4e-headers-mode-map
              ("M-[" . scroll-down-command)
              ("M-]" . scroll-up-command))
  :preface
  (defun my/set-email-account (label letvars)
    "Registers an email address for mu4e."
    (setq mu4e-contexts
          (cl-loop for context in mu4e-contexts
                   unless (string= (mu4e-context-name context) label)
                   collect context))
    (let ((context (make-mu4e-context
                    :name label
                    :enter-func (lambda () (mu4e-message "Switched context"))
                    :leave-func #'mu4e-clear-caches
                    :match-func
                    (lambda (msg)
                      (when msg
                        (string-prefix-p (format "/%s" msg)
                                         (mu4e-message-field msg :maildir))))
                    :vars letvars)))
      (push context mu4e-contexts)
      context))
  :custom
  (mu4e-attachment-dir "~/downloads")
  ;; To avoid synchronization issues/ with mbsync
  (mu4e-change-filenames-when-moving t)
  (mu4e-confirm-quit nil)
  (mu4e-completing-read-function 'ivy-read)
  (mu4e-compose-complete-only-after (format-time-string
                                     "%Y-%m-%d"
                                     (time-subtract (current-time) (days-to-time 150))))
  (mu4e-compose-context-policy 'ask-if-none)
  (mu4e-compose-dont-reply-to-self t)
  (mu4e-compose-format-flowed t)
  (mu4e-get-mail-command (format "mbsync -c '%s/isync/mbsyncrc' -a" xdg-config))
  (mu4e-headers-date-format "%F")
  (mu4e-headers-fields
   '((:account    . 10)
     (:human-date . 12)
     (:flags      . 6)
     (:from       . 22)
     (:subject    . nil)))
  (mu4e-headers-time-format "%R")
  (mu4e-html2text-command "iconv -c -t utf-8 | pandoc -f html -t plain")
  (mu4e-maildir "~/mails")
  (mu4e-org-contacts-file "~/.personal/agenda/contacts.org")
  (mu4e-update-interval (* 5 60))
  (mu4e-use-fancy-chars t)
  (mu4e-view-prefer-html t)
  (mu4e-view-show-addresses t)
  (mu4e-view-show-images t)
  :config
  (my/set-email-account "erroriamnotfound"
                        '((mu4e-drafts-folder . "/personal/erroriamnotfound/drafts")
                          (mu4e-refile-folder . "/personal/erroriamnotfound/all")
                          (mu4e-sent-folder   . "/personal/erroriamnotfound/sent")
                          (mu4e-trash-folder  . "/personal/erroriamnotfound/trash")
                          (mu4e-maildir-shortcuts . ((:maildir "/personal/erroriamnotfound/all"    :key ?a)
                                                     (:maildir "/personal/erroriamnotfound/inbox"  :key ?i)
                                                     (:maildir "/personal/erroriamnotfound/drafts" :key ?d)
                                                     (:maildir "/personal/erroriamnotfound/sent"   :key ?s)
                                                     (:maildir "/personal/erroriamnotfound/trash"  :key ?t)))
                          (smtpmail-smtp-user . "erroriamnotfound@gmail.com")
                          (smtpmail-smtp-server . "smtp.gmail.com")
                          (smtpmail-smtp-service . 465)
                          (smtpmail-stream-type . ssl)
                          (user-mail-address . "erroriamnotfound@gmail.com")
                          (user-full-name . "Who Cares")))
  (my/set-email-account "terencio.agozzino"
                        '((mu4e-drafts-folder . "/personal/terencio-agozzino/drafts")
                          (mu4e-refile-folder . "/personal/terencio-agozzino/all")
                          (mu4e-sent-folder   . "/personal/terencio-agozzino/sent")
                          (mu4e-trash-folder  . "/personal/terencio-agozzino/trash")
                          (mu4e-maildir-shortcuts . ((:maildir "/personal/terencio-agozzino/all"    :key ?a)
                                                     (:maildir "/personal/terencio-agozzino/inbox"  :key ?i)
                                                     (:maildir "/personal/terencio-agozzino/drafts" :key ?d)
                                                     (:maildir "/personal/terencio-agozzino/sent"   :key ?s)
                                                     (:maildir "/personal/terencio-agozzino/trash"  :key ?t)))
                          (smtpmail-smtp-user . "terencio.agozzino@gmail.com")
                          (smtpmail-smtp-server . "smtp.gmail.com")
                          (smtpmail-smtp-service . 465)
                          (smtpmail-stream-type . ssl)
                          (user-mail-address . "terencio.agozzino@gmail.com")
                          (user-full-name . "Terencio Agozzino")))
  (setq mu4e-headers-attach-mark    `("a" . ,(with-faicon "paperclip" "" 0.75 -0.05 "all-the-icons-lyellow"))
        mu4e-headers-draft-mark     `("D" . ,(with-octicon "pencil" "" 0.75 -0.05 "all-the-icons-lsilver"))
        mu4e-headers-encrypted-mark `("x" . ,(with-faicon "lock" "" 0.75 -0.05 "all-the-icons-lred"))
        mu4e-headers-flagged-mark   `("F" . ,(with-faicon "flag" "" 0.75 -0.05 "all-the-icons-maroon"))
        mu4e-headers-new-mark       `("N" . ,(with-faicon "check-circle" "" 0.75 -0.05 "all-the-icons-silver"))
        mu4e-headers-passed-mark    `("P" . ,(with-faicon "share" "" 0.75 -0.05 "all-the-icons-purple "))
        mu4e-headers-replied-mark   `("R" . ,(with-faicon "reply" "" 0.75 -0.05 "all-the-icons-lgreen"))
        mu4e-headers-seen-mark      `("S" . ,(with-octicon "check" "" 1 -0.05 "all-the-icons-lgreen"))
        mu4e-headers-signed-mark    `("s" . ,(with-faicon "key" "" 0.75 -0.05 "all-the-icons-cyan"))
        mu4e-headers-trashed-mark   `("T" . ,(with-faicon "trash" "" 0.75 -0.05 "all-the-icons-lred"))
        mu4e-headers-unread-mark    `("u" . ,(with-faicon "envelope" "" 0.75 -0.05 "all-the-icons-silver")))
  (add-to-list 'mu4e-header-info-custom
               '(:account
                 :name "Account"
                 :shortname "Account"
                 :help "Which account this email belongs to"
                 :function
                 (lambda (msg)
                   (let ((maildir (mu4e-message-field msg :maildir)))
                     (format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
  (add-to-list 'mu4e-headers-actions '("org-contact-add" . mu4e-action-add-org-contact) t)
  (add-to-list 'mu4e-view-actions '("org-contact-add" . mu4e-action-add-org-contact) t))

To edit my emails in org-mode, I use org-mime. It also to add a signature, automatically convert Org in HTML, and custom some HTML elements.

(use-package org-mime
  :after mu4e
  :hook (message-send . org-mime-htmlize)
  :bind (:map mu4e-compose-mode-map
              ("C-c '" . org-mime-edit-mail-in-org-mode))
  :config
  (add-hook 'org-mime-html-hook (lambda ()
                                  (goto-char (point-max))
                                  (insert "--<br>
                 <strong>Terencio Agozzino</strong><br>
                 Data Scientist
                 <br>
                 <span style='text-decoration:none; color:#000;'>(+32) 495 45 29 07</span>")))
  (add-hook 'org-mime-html-hook (lambda ()
                                  (org-mime-change-element-style "p" (format "color: %s" "#1a1a1a"))))

  (add-hook 'org-mime-html-hook (lambda ()
                                  (org-mime-change-element-style "strong" (format "color: %s" "#000"))))

  (add-hook 'org-mime-html-hook (lambda ()
                                  (org-mime-change-element-style
                                   "pre" "background: none repeat scroll 0% 0% rgb(61, 61, 61);
                                                 border-radius: 15px;
                                                 color: #eceff4;
                                                 font-family: Courier, 'Courier New', monospace;
                                                 font-size: small;
                                                 font-weight: 400;
                                                 line-height: 1.3em;
                                                 padding: 20px;
                                                 quotes: '«' '»';
                                                 width: 41%;")))
  (setq org-mime-export-options '(:preserve-breaks t
                                                   :section-numbers nil
                                                   :with-author nil
                                                   :with-toc nil)))

Being able to read mails is a good thing, but being notified when we receive mails is better! The following few lines allow you to receive desktop notifications and modeline display for mu4e:

(use-package mu4e-alert
  :hook ((after-init . mu4e-alert-enable-mode-line-display)
         (after-init . mu4e-alert-enable-notifications))
  :config (mu4e-alert-set-default-style 'libnotify))

Sending Mail

Before you can send mails, create the .authinfo file if it is not already done. Then add the following two lines replacing terencio.agozzino (which corresponds to my terencio.agozzino@gmail.com Gmail address without the domain name) and <password> by those that match your real information:

machine imap.gmail.com login terencio.agozzino@gmail.com password <password> port 993
machine smtp.gmail.com login terencio.agozzino@gmail.com password <password> port 465

Similar to IRC, if you want to store your password in a GPG file, you just need to specify a file priority list with auth-sources, to specify to GNU Emacs where to start looking for your password first. Then encrypt that file with gpg -c .authinfo and do not forget to delete the .authinfo file.

(use-package message
  :ensure nil
  :after mu4e
  :custom
  (message-citation-line-format "On %B %e, %Y at %l:%M %p, %f (%n) wrote:\n")
  (message-citation-line-function 'message-insert-formatted-citation-line)
  (message-kill-buffer-on-exit t)
  (message-send-mail-function 'smtpmail-send-it)
  (mml-secure-openpgp-signers '("208FCDBB98190562")))

All you need to do now is to test sending your mails with C-x m or directly from mu4e!

Money Management

Good money management is a skill to be acquired as soon as possible. Fortunately for us, Ledger allows you to have a double-entry accounting system directly from the UNIX command line. To use Ledger with GNU Emacs, you need to the ledger-mode package. However, do not forget to install the ledger package with your system package manager.

I discovered Ledger as a student and learning how to manage time and expenses at the same time was too much. I decided for the time being to briefly learn Ledger and focus more on time management. One issue I had with Ledger was the lack of support from my bank to export my transactions into a file type that Ledger could understand. Unfortunately, since I often made bank transfers, encoding each payment by hand was a pain. For the curious people, alternatives to Ledger exist, some of which are open-source and proprietary (e.g., YNAB).

NOTE: by default, Ledger uses the ISO 8601 format to write dates, which is the recommended format.

(use-package ledger-mode
  :mode ("\\.\\(dat\\|ledger\\)\\'")
  :preface
  (defun my/ledger-save ()
    "Clean the ledger buffer at each save."
    (interactive)
    (ledger-mode-clean-buffer)
    (save-buffer))
  :bind (:map ledger-mode-map
              ("C-x C-s" . my/ledger-save))
  :hook (ledger-mode . ledger-flymake-enable)
  :custom
  (ledger-clear-whole-transactions t)
  (ledger-reconcile-default-commodity "EUR")
  (ledger-reports
   '(("account statement" "%(binary) reg --real [[ledger-mode-flags]] -f %(ledger-file) ^%(account)")
     ("balance sheet" "%(binary) --real [[ledger-mode-flags]] -f %(ledger-file) bal ^assets ^liabilities ^equity")
     ("budget" "%(binary) --empty -S -T [[ledger-mode-flags]] -f %(ledger-file) bal ^assets:bank ^assets:receivables ^assets:cash ^assets:budget")
     ("budget goals" "%(binary) --empty -S -T [[ledger-mode-flags]] -f %(ledger-file) bal ^assets:bank ^assets:receivables ^assets:cash ^assets:'budget goals'")
     ("budget obligations" "%(binary) --empty -S -T [[ledger-mode-flags]] -f %(ledger-file) bal ^assets:bank ^assets:receivables ^assets:cash ^assets:'budget obligations'")
     ("budget debts" "%(binary) --empty -S -T [[ledger-mode-flags]] -f %(ledger-file) bal ^assets:bank ^assets:receivables ^assets:cash ^assets:'budget debts'")
     ("cleared" "%(binary) cleared [[ledger-mode-flags]] -f %(ledger-file)")
     ("equity" "%(binary) --real [[ledger-mode-flags]] -f %(ledger-file) equity")
     ("income statement" "%(binary) --invert --real -S -T [[ledger-mode-flags]] -f %(ledger-file) bal ^income ^expenses -p \"this month\""))
   (ledger-report-use-header-line nil)))

Finally, to provides a flychecker checker for Ledger files, you can use flycheck-ledger.

(use-package flycheck-ledger :after ledger-mode)

Package Menu

The package menu has a simplistic interface. The paradox package improves the display of this interface, provides different mode line information and other features.

(use-package paradox
  :defer 1
  :custom
  (paradox-column-width-package 25)
  (paradox-display-star-count nil)
  (paradox-execute-asynchronously t)
  (paradox-hide-wiki-packages t)
  :config (paradox-enable))

Parentheses

Managing parentheses can be painful. One of the first things you want to do is to change the appearance of the highlight of the parentheses pairs.

(use-package faces
  :ensure nil
  :custom (show-paren-delay 0)
  :config
  (set-face-background 'show-paren-match "#161719")
  (set-face-bold 'show-paren-match t)
  (set-face-foreground 'show-paren-match "#ffffff"))

Then, I also like to highlight brackets and other delimiters (e.g., brackets or braces) with a different color, depending on their depth. The rainbow-delimiters package allows such behavior.

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

Finally, to manage your parenthesis, I recommend to use smartparens which is in my opinion a better alternative to paredit or to the built-in electric-pair-mode package.

(use-package smartparens
  :delight
  :hook (prog-mode . smartparens-mode)
  :bind (("M-'" . sp-backward-sexp)
         ("M-\\" . sp-forward-sexp)
         ("M-(" . sp-wrap-round)
         ("M-[" . sp-wrap-curly))
  :custom (sp-escape-quotes-after-insert nil))

Paste

To get or provide help, it is important to be able to parse a whole buffer or a region of this buffer. The webpaste package allows you to parse your code using a pastebin-like service.

(use-package webpaste
  :defer 0.4
  :bind (("C-c C-p C-b" . webpaste-paste-buffer)
         ("C-c C-p C-p" . webpaste-paste-buffer-or-region)
         ("C-c C-p C-r" . webpaste-paste-region))
  :custom (webpaste-provider-priority '("dpaste.org" "dpaste.com" "ix.io")))

Similarly, the imgbb package allows you to select and upload images using the imgbb service.

(use-package imgbb
  :commands imgbb-upload
  :bind ("C-c C-p C-i" . imgbb-upload))

PDF

By default, opening PDFs under GNU Emacs sucks. Fortunately, the pdf-tools package provides pdf-view to offer a better support for PDFs.

NOTE: If pdf-tools fails to open a PDF, please execute the pdf-tools-install command.

(use-package pdf-tools
  :magic ("%PDF" . pdf-view-mode)
  :init (pdf-tools-install :no-query))

To be comfortable with PDFs, let’s customize pdf-view which is a major mode part of the pdf-tools package.

(use-package pdf-view
  :ensure nil
  :after pdf-tools
  :bind (:map pdf-view-mode-map
              ("C-s" . isearch-forward)
              ("d" . pdf-annot-delete)
              ("h" . pdf-annot-add-highlight-markup-annotation)
              ("t" . pdf-annot-add-text-annotation))
  :custom
  (pdf-view-display-size 'fit-page)
  (pdf-view-resize-factor 1.1)
  ;; Avoid searching for unicodes to speed up pdf-tools.
  (pdf-view-use-unicode-ligther nil)
  ;; Enable HiDPI support, at the cost of memory.
  (pdf-view-use-scaling t))

Project Interactions

To interact with your projects characterized by a folder with at least one special file (e.g., a VCS marker or a project descriptor file like pom.xml or Gemfile), the projectile package is a must on GNU Emacs.

(use-package projectile
  :diminish (projectile-mode)
  :custom
  (projectile-cache-file (expand-file-name (format "%s/emacs/projectile.cache" xdg-cache)))
  (projectile-enable-caching t)
  (projectile-keymap-prefix (kbd "C-c C-p"))
  (projectile-known-projects-file (expand-file-name (format "%s/emacs/projectile-bookmarks.eld" xdg-cache)))
  (projectile-mode-line '(:eval (projectile-project-name)))
  ;; Define the folder containing git repositories (e.g., ~/.local/share/git).
  (projectile-project-search-path '("~/.local/share/git"))
  (projectile-switch-project-action #'projectile-dired)
  :config (projectile-global-mode))

I also use projectile with consult through the counsult-projectile package.

(use-package consult-projectile
  :after (consult projectile)
  :straight (consult-projectile :type git :host gitlab :repo
                                "OlMon/consult-projectile" :branch "master")
  :commands (consult-projectile))

Finally, ibuffer-projectile is helpful to group the buffers in the ibuffer list by projectile project.

(use-package ibuffer-projectile
  :after ibuffer
  :preface
  (defun my/ibuffer-projectile ()
    (ibuffer-projectile-set-filter-groups)
    (unless (eq ibuffer-sorting-mode 'alphabetic)
      (ibuffer-do-sort-by-alphabetic)))
  :hook (ibuffer . my/ibuffer-projectile))

Snippets

(use-package yasnippet-snippets
  :after yasnippet
  :config (yasnippet-snippets-initialize))

(use-package yasnippet
  :delight yas-minor-mode "υ"
  :hook (yas-minor-mode . my/disable-yas-if-no-snippets)
  :config (yas-global-mode)
  :preface
  (defun my/disable-yas-if-no-snippets ()
    (when (and yas-minor-mode (null (yas--get-snippet-tables)))
      (yas-minor-mode -1))))

(use-package ivy-yasnippet :after yasnippet)
(use-package react-snippets :after yasnippet)

Spaces Around the Operators

To automatically add spacing around operators as you develop, the electric-operator package is there for that.

NOTE: I don’t use electric-operator for C-like languages which works a bit less well according to the author of the package.

(use-package electric-operator
  :hook ((css-mode java-mode js2-mode
                  python-mode sql-mode typescript-mode) . electric-operator-mode))

Recent Files

It could be useful to have easy access to recently modified files.

(use-package recentf
  :ensure nil
  :bind ("C-x C-r" . recentf-open-files)
  :init (recentf-mode)
  :custom
  (recentf-exclude (list "/scp:"
                         "/ssh:"
                         "/sudo:"
                         "/tmp/"
                         "~$"
                         "COMMIT_EDITMSG"))
  (recentf-max-menu-items 15)
  (recentf-max-saved-items 200)
  (recentf-save-file (expand-file-name (format "%s/emacs/recentf" xdg-cache)))
  ;; Save recent files every 5 minutes to manage abnormal output.
  :config (run-at-time nil (* 5 60) 'recentf-save-list))

Term Definitions

Occasionally, I would like to have a summary of a term directly on GNU Emacs, before that I would like to know more about this term. The wiki-summary package allows this behavior.

(use-package wiki-summary
  :commands (wiki-summary wiki-summary-insert)
  :bind ("C-c W" . wiki-summary)
  :preface
  (defun my/format-summary-in-buffer (summary)
    "Given a summary, sticks it in the *wiki-summary* buffer and displays
     the buffer."
    (let ((buf (generate-new-buffer "*wiki-summary*")))
      (with-current-buffer buf
        (princ summary buf)
        (fill-paragraph)
        (goto-char (point-min))
        (view-mode))
      (pop-to-buffer buf)))
  :config
  (advice-add 'wiki-summary/format-summary-in-buffer
              :override #'my/format-summary-in-buffer))

Try Packages

It happens that I want to try a GNU Emacs package without installing it. The try package allows this behavior.

(use-package try :commands try)

Version Control System

It is quite common to work on Git repositories, therefore it is important to have a configuration that suits us. To interact with Git, GNU Emacs has magit which is the best tool I have tested. With magit you can directly interact with your repositories through an interface.

(use-package magit
  :commands magit-status
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

To make sure that the summary and the body of the commits respect the conventions, the git-commit package from magit is perfect.

(use-package git-commit
  :ensure nil
  :preface
  (defun my/git-commit-auto-fill-everywhere ()
    "Ensure that the commit body does not exceed 72 characters."
    (setq fill-column 72)
    (setq-local comment-auto-fill-only-comments nil))
  :hook (git-commit-mode . my/git-commit-auto-fill-everywhere)
  :custom (git-commit-summary-max-length 50))

To manage diff3 conflicts with Git, the smerge-mode builtin package is helpful.

(use-package smerge-mode
  :after hydra
  :delight ""
  :commands smerge-mode
  :bind (:map smerge-mode-map
              ("M-g n" . smerge-next)
              ("M-g p" . smerge-prev))
  :hook (magit-diff-visit-file . hydra-merge/body))

Finally, I like to know the modified lines of a file while I edit it.

(use-package git-gutter
  :defer 0.3
  :delight
  :config (global-git-gutter-mode))

Whitespaces and Kill

It is annoying to see useless blank spaces at the end of a line or a file. Let’s get rid of these spaces. Most of the time, I also like to have justified texts.

(use-package simple
  :ensure nil
  :delight (auto-fill-function)
  :preface
  (defun my/kill-region-or-line ()
    "When called interactively with no active region, kill the whole line."
    (interactive)
    (if current-prefix-arg
        (progn
          (kill-new (buffer-string))
          (delete-region (point-min) (point-max)))
      (progn (if (use-region-p)
                 (kill-region (region-beginning) (region-end) t)
               (kill-region (line-beginning-position) (line-beginning-position
                                                       2))))))
  :hook ((before-save . delete-trailing-whitespace)
         ((prog-mode text-mode) . turn-on-auto-fill))
  :bind ("C-w" . my/kill-region-or-line)
  :custom (set-mark-command-repeat-pop t))

Finally, I also like is to be able to delete every consecutive space characters when a space character is deleted. The hungry-delete package allows this behavior.

(use-package hungry-delete
  :defer 0.7
  :delight
  :config (global-hungry-delete-mode))

Org Mode

Org Mode is one of my favorite modes in GNU Emacs. I mainly use it to write notes and manage my time to enjoy a better organization of my life, but you can do lots of things with it. It is like the sky, without limits.

First Steps

To use Org Mode, GNU Emacs already has the org built-in package. However, some extra packages are in the org-contrib package. It is therefore a good idea to install it.

(use-package org
  :ensure org-contrib
  :delight "Θ"
  :hook (org-mode . turn-off-auto-fill)
  :bind ("C-c i" . org-insert-structure-template)
  :preface
  (defun my/org-archive-done-tasks ()
    "Archive finished or cancelled tasks."
    (interactive)
    (org-map-entries
     (lambda ()
       (org-archive-subtree)
       (setq org-map-continue-from (outline-previous-heading)))
     "TODO=\"DONE\"|TODO=\"CANCELLED\"" (if (org-before-first-heading-p) 'file 'tree)))

  (defun my/org-jump ()
    "Jump to a specific task."
    (interactive)
    (let ((current-prefix-arg '(4)))
      (call-interactively 'org-refile)))

  (defun my/org-use-speed-commands-for-headings-and-lists ()
    "Activate speed commands on list items too."
    (or (and (looking-at org-outline-regexp) (looking-back "^\**"))
        (save-excursion (and (looking-at (org-item-re)) (looking-back "^[ \t]*")))))

  (defmacro ignore-args (fnc)
    "Returns function that ignores its arguments and invokes FNC."
    `(lambda (&rest _rest)
       (funcall ,fnc)))
  :hook ((after-save . my/config-tangle)
         (auto-save . org-save-all-org-buffers)
         (org-mode . visual-line-mode))
  :custom
  (org-archive-location "~/.personal/archives/%s::")
  (org-blank-before-new-entry '((heading . t)
                                (plain-list-item . t)))
  (org-confirm-babel-evaluate nil)
  (org-cycle-include-plain-lists 'integrate)
  (org-ellipsis "")
  (org-export-backends '(ascii beamer html icalendar latex man md org texinfo))
  (org-hide-emphasis-markers t)
  (org-log-done 'time)
  (org-log-into-drawer t)
  (org-modules '(org-crypt
                 org-habit
                 org-mouse
                 org-protocol
                 org-tempo))
  (org-refile-allow-creating-parent-nodes 'confirm)
  (org-refile-targets '((org-agenda-files :maxlevel . 1)
                        ("~/.personal/agenda/home.org" :maxlevel . 2)
                        ("~/.personal/agenda/work.org" :maxlevel . 2)))
  (org-refile-use-cache nil)
  (org-refile-use-outline-path nil)
  (org-startup-indented t)
  (org-startup-with-inline-images t)
  (org-tag-alist
   '((:startgroup . "Context")
     ("@errands" . ?e)
     ("@home" . ?h)
     ("@work" . ?w)
     (:endgroup)
     (:startgroup . "Difficulty")
     ("easy" . ?E)
     ("medium" . ?M)
     ("challenging" . ?C)
     (:endgroup)
     ("bug" . ?b)
     ("car" . ?v)
     ("future" . ?F)
     ("goal" . ?g)
     ("health" . ?H)
     ("house" . ?O)
     ("meeting" . ?m)
     ("planning" . ?p)
     ("phone" . ?0)
     ("purchase" . ?P)
     ("reading" . ?r)
     ("review" . ?R)
     ("study" . ?s)
     ("sport" . ?S)
     ("talk" . ?T)
     ("tech" . ?t)
     ("trip" . ?I)
     ("thinking" . ?i)
     ("update" . ?u)
     ("watch" . ?l)
     ("writing" . ?W)))
  (org-tags-exclude-from-inheritance '("crypt" "project"))
  (org-todo-keywords '((sequence "TODO(t)"
                                 "STARTED(s)"
                                 "NEXT(n)"
                                 "SOMEDAY(.)"
                                 "WAITING(w)""|" "DONE(x!)" "CANCELLED(c@)")))
  (org-use-effective-time t)
  (org-use-speed-commands 'my/org-use-speed-commands-for-headings-and-lists)
  (org-yank-adjusted-subtrees t)
  :config
  (add-to-list 'org-global-properties '("Effort_ALL". "0:05 0:15 0:30 1:00 2:00 3:00 4:00"))
  (add-to-list 'org-speed-commands '("$" call-interactively 'org-archive-subtree))
  (add-to-list 'org-speed-commands '("i" call-interactively 'org-clock-in))
  (add-to-list 'org-speed-commands '("o" call-interactively 'org-clock-out))
  (add-to-list 'org-speed-commands '("s" call-interactively 'org-schedule))
  (add-to-list 'org-speed-commands '("x" org-todo "DONE"))
  (add-to-list 'org-speed-commands '("y" org-todo-yesterday "DONE"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (advice-add 'org-deadline :after (ignore-args #'org-save-all-org-buffers))
  (advice-add 'org-schedule :after (ignore-args #'org-save-all-org-buffers))
  (advice-add 'org-store-log-note :after (ignore-args #'org-save-all-org-buffers))
  (advice-add 'org-refile :after 'org-save-all-org-buffers)
  (advice-add 'org-todo :after (ignore-args #'org-save-all-org-buffers))
  ;; Replace list hyphen with dot
  (font-lock-add-keywords 'org-mode
                          '(("^ *\\([-]\\) "
                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ""))))))
  (org-clock-persistence-insinuate)
  (org-load-modules-maybe t))

If like me you want to automatically update the tables of contents of your .org files, toc-org is the ideal package. To automate these tables of contents, you only need to use the :TOC: tag in the first heading of these tables of contents.

(use-package toc-org
  :after org
  :hook (org-mode . toc-org-enable))

Agenda

Nowadays, it is crucial to be organized. Even more than before. That is why it is important to take the time to make a configuration that is simple to use and that makes your life easier with an irreproachable organization. The org-agenda built-in package allows me to be organized in my daily tasks. As a result, I can use my time to the fullest.

NOTE: To synchronize and access my calendar via multiple devices (e.g., smartphone or other laptop), I synchronize my .org files with Syncthing

(use-package org-agenda
  :ensure nil
  :bind (:map org-agenda-mode-map
              ("C-n" . org-agenda-next-item)
              ("C-p" . org-agenda-previous-item)
              ("j" . org-agenda-goto)
              ("X" . my/org-agenda-mark-done-next)
              ("x" . my/org-agenda-mark-done))
  :preface
  (defun my/org-agenda-mark-done (&optional arg)
    "Mark the current TODO as done in org-agenda."
    (interactive "P")
    (org-agenda-todo "DONE"))

  (defun my/org-agenda-mark-done-next ()
    "Mark the current TODO as done and add another task after it."
    (interactive)
    (org-agenda-todo "DONE")
    (org-agenda-switch-to)
    (org-capture 0 "t"))
  :custom
  (org-agenda-category-icon-alist
   `(("home" ,(list (all-the-icons-faicon "home" :v-adjust -0.05)) nil nil :ascent center :mask heuristic)
     ("inbox" ,(list (all-the-icons-faicon "inbox" :v-adjust -0.1)) nil nil :ascent center :mask heuristic)
     ("people" ,(list (all-the-icons-material "people" :v-adjust -0.25)) nil nil :ascent center :mask heuristic)
     ("work" ,(list (all-the-icons-material "work" :v-adjust -0.25)) nil nil :ascent center :mask heuristic)
     ("routine" ,(list (all-the-icons-material "repeat" :v-adjust -0.25)) nil nil :ascent center :mask heuristic)
     ))
  (org-agenda-custom-commands
   '(("d" "Dashboard"
      ((agenda "" ((org-deadline-warning-days 7)))
       (todo "NEXT"
             ((org-agenda-overriding-header "Next Tasks")))
       (tags-todo "agenda/ACTIVE" ((org-agenda-overriding-header "Active Projects")))))

     ("n" "Next Tasks"
      ((agenda "" ((org-deadline-warning-days 7)))
       (todo "NEXT"
             ((org-agenda-overriding-header "Next Tasks")))))

     ("h" "Home Tasks" tags-todo "@home")
     ("w" "Work Tasks" tags-todo "@work")

     ("E" "Easy Tasks" tags-todo "easy")
     ("C" "Challenging Tasks" tags-todo "challenging")

     ("e" tags-todo "+TODO=\"NEXT\"+Effort<15&+Effort>0"
      ((org-agenda-overriding-header "Low Effort Tasks")
       (org-agenda-max-todos 20)
       (org-agenda-files org-agenda-files)))))
  (org-agenda-dim-blocked-tasks t)
  (org-agenda-files '("~/.personal/agenda"))
  (org-agenda-inhibit-startup t)
  (org-agenda-show-log t)
  (org-agenda-skip-deadline-if-done t)
  (org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled)
  (org-agenda-skip-scheduled-if-done t)
  (org-agenda-span 2)
  (org-agenda-start-on-weekday 6)
  (org-agenda-start-with-log-mode t)
  (org-agenda-sticky nil)
  (org-agenda-tags-column 90)
  (org-agenda-time-grid '((daily today require-timed)))
  (org-agenda-use-tag-inheritance t)
  (org-columns-default-format "%14SCHEDULED %Effort{:} %1PRIORITY %TODO %50ITEM %TAGS")
  (org-default-notes-file "~/.personal/agenda/inbox.org")
  (org-directory "~/.personal")
  (org-enforce-todo-dependencies t)
  (org-habit-completed-glyph ?✓)
  (org-habit-graph-column 80)
  (org-habit-show-habits-only-for-today nil)
  (org-habit-today-glyph ?‖)
  (org-track-ordered-property-with-tag t))

Finally, I use org-wild-notifier to get notifications about TODO items in org-agenda.

(use-package org-wild-notifier
  :after org
  :custom
  (alert-default-style 'libnotify)
  (org-wild-notifier-notification-title "Agenda Reminder")
  :config (org-wild-notifier-mode))

Bullets

Let’s add prettier bullets in Org Mode with org-bullets.

(use-package org-bullets
  :hook (org-mode . org-bullets-mode)
  :custom (org-bullets-bullet-list '("" "" "")))

Capture

org-capture templates saves you a lot of time when adding new entries. I use it to quickly record tasks, ledger entries, notes and other semi-structured information.

(use-package org-capture
  :ensure nil
  :preface
  (defvar my/org-active-task-template
    (concat "* NEXT %^{Task}\n"
            ":PROPERTIES:\n"
            ":Effort: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00}\n"
            ":CAPTURED: %<%Y-%m-%d %H:%M>\n"
            ":END:") "Template for basic task.")
  (defvar my/org-appointment
    (concat "* TODO %^{Appointment}\n"
            "SCHEDULED: %t\n") "Template for appointment task.")
  (defvar my/org-basic-task-template
    (concat "* TODO %^{Task}\n"
            ":PROPERTIES:\n"
            ":Effort: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00}\n"
            ":CAPTURED: %<%Y-%m-%d %H:%M>\n"
            ":END:") "Template for basic task.")
  (defvar my/org-contacts-template
    (concat "* %(org-contacts-template-name)\n"
            ":PROPERTIES:\n"
            ":BIRTHDAY: %^{YYYY-MM-DD}\n"
            ":END:") "Template for a contact.")
  :custom
  (org-capture-templates
   `(
     ("c" "Contact" entry (file+headline "~/.personal/agenda/contacts.org" "Inbox"),
      my/org-contacts-template
      :empty-lines 1)

     ("p" "People" entry (file+headline "~/.personal/agenda/people.org" "Tasks"),
      my/org-basic-task-template
      :empty-lines 1)
     ("a" "Appointment" entry (file+headline "~/.personal/agenda/people.org" "Appointments"),
      my/org-appointment
      :empty-lines 1)
     ("m" "Meeting" entry (file+headline "~/.personal/agenda/people.org" "Meetings")
      "* Meeting with %? :meeting:\n%U" :clock-in t :clock-resume t :empty-lines 1)
     ("P" "Phone Call" entry (file+headline "~/.personal/agenda/people.org" "Phone Calls")
      "* Phone %? :phone:\n%U" :clock-in t :clock-resume t)

     ("i" "New Item")
     ("ib" "Book" checkitem (file+headline "~/.personal/items/books.org" "Books")
      "- [ ] %^{Title} - %^{Author}\n  %U"
      :immediate-finish t)
     ("il" "Learning" checkitem (file+headline "~/.personal/items/learning.org" "Things")
      "- [ ] %^{Thing}\n  %U"
      :immediate-finish t)
     ("im" "Movie" checkitem (file+headline "~/.personal/items/movies.org" "Movies")
      "- [ ] %^{Title}\n  %U"
      :immediate-finish t)
     ("ip" "Purchase" checkitem (file+headline "~/.personal/items/purchases.org" "Purchases")
      "- [ ] %^{Item}\n  %U"
      :immediate-finish t)

     ("t" "New Task")
     ("ta" "Active" entry (file+headline "~/.personal/agenda/inbox.org" "Active"),
      my/org-active-task-template
      :empty-lines 1
      :immediate-finish t)
     ("tb" "Backlog" entry (file+headline "~/.personal/agenda/inbox.org" "Backlog"),
      my/org-basic-task-template
      :empty-lines 1
      :immediate-finish t))))

Clock

To estimate and track the time of my tasks, GNU Emacs provides the org-clock built-in package.

(use-package org-clock
  :ensure nil
  :after org
  :preface
  (defun my/org-mode-ask-effort ()
    "Ask for an effort estimate when clocking in."
    (unless (org-entry-get (point) "Effort")
      (let ((effort
             (completing-read
              "Effort: "
              (org-entry-get-multivalued-property (point) "Effort"))))
        (unless (equal effort "")
          (org-set-property "Effort" effort)))))
  :hook (org-clock-in-prepare-hook . my/org-mode-ask-effort)
  :custom
  (org-clock-clocktable-default-properties
   '(:block thisweek :maxlevel 2 :scope agenda :link t :compact t :formula %
            :step week :fileskip0 t :stepskip0 t :narrow 50
            :properties ("Effort" "CLOCKSUM" "TODO")))
  (org-clock-continuously nil)
  (org-clock-in-switch-to-state "STARTED")
  (org-clock-out-remove-zero-time-clocks t)
  (org-clock-persist t)
  (org-clock-persist-file (expand-file-name (format "%s/emacs/org-clock-save.el" xdg-cache)))
  (org-clock-persist-query-resume nil)
  (org-clock-report-include-clocking-task t)
  (org-show-notification-handler (lambda (msg) (alert msg))))

As a person with a short attention span, the Pomodoro technique allows me to maximize my concentration throughout the day. Personally, I do sessions of 25 minutes of concentrated work and 5 minutes of break. Finally, every two hours, I take a 30 minute break. To use the Pomodoro technique with GNU Emacs, the org-pomodoro package is suggested.

(use-package org-pomodoro
  :after org
  :custom
  (alert-user-configuration (quote ((((:category . "org-pomodoro")) libnotify nil))))
  (org-pomodoro-audio-player "/usr/bin/mpv")
  (org-pomodoro-finished-sound "~/audio/pomodoro_finished.mp3")
  (org-pomodoro-format " %s")
  (org-pomodoro-killed-sound "~/audio/pomodoro_killed.mp3")
  (org-pomodoro-long-break-sound "~/audio/pomodoro_long.mp3")
  (org-pomodoro-overtime-sound "~/audio/pomodoro_overtime.mp3")
  (org-pomodoro-short-break-sound "~/audio/pomodoro_short.mp3")
  (org-pomodoro-start-sound "~/audio/pomodoro_start.mp3")
  (org-pomodoro-start-sound-p t))

Contacts

Managing your business and personal contact information can quickly turn into a mess. Several solutions exist in GNU Emacs. Personally I use the org-contacts package available in the org-contrib package.

(use-package org-contacts
  :after org
  :ensure nil
  :custom (org-contacts-files '("~/.personal/agenda/contacts.org")))

Faces

Let’s change the foreground and the weight of each keyword.

(use-package org-faces
  :ensure nil
  :custom
  (org-todo-keyword-faces
   '(("DONE"    . (:foreground "#8abeb7" :weight bold))
     ("NEXT"    . (:foreground "#f0c674" :weight bold))
     ("SOMEDAY" . (:foreground "#b294bb" :weight bold))
     ("TODO"    . (:foreground "#b5bd68" :weight bold))
     ("WAITING" . (:foreground "#de935f" :weight bold)))))

Encryption / Decryption

To encrypt and decrypt GPG files with org-mode, you need to add org-crypt in your org-modules, install gnupg with your system manager package and create a GPG key pair with the public key specified to org-crypt.

(use-package org-crypt
  :ensure nil
  :init (org-crypt-use-before-save-magic)
  :custom (org-crypt-key "E9AADC36E94A672D1A07D49B208FCDBB98190562"))

(setq epa-file-encrypt-to "terencio.agozzino@gmail.com")
(setq epa-file-select-keys "auto")

Languages

Let’s specify languages extra to Org Mode to compile them directly into .org files.

(use-package jupyter :ensure nil :after org)
(use-package python :ensure nil :after org)
(use-package ob-C :ensure nil :after org)
(use-package ob-css :ensure nil :after org)
(use-package ob-dot :ensure nil :after org)
(use-package ob-ein :ensure nil :after org)
(use-package ob-emacs-lisp :ensure nil :after org)
(use-package ob-gnuplot :ensure nil :after org)
(use-package ob-java :ensure nil :after org)
(use-package ob-js :ensure nil :after org)
(use-package ob-latex
  :ensure nil
  :after org
  :custom (org-latex-compiler "xelatex"))
(use-package ob-ledger :ensure nil :after org)
(use-package ob-makefile :ensure nil :after org)
(use-package ob-org :ensure nil :after org)
(use-package ob-plantuml
  :ensure nil
  :after org
  :custom (org-plantuml-jar-path (expand-file-name (format "%s/plantuml.jar" xdg-lib))))
(use-package ob-python :ensure nil :after org)
(use-package ob-shell :ensure nil :after org)
(use-package ob-sql :ensure nil :after org)

Notes and Journal

Taking notes is a necessity in a world full of information. To take these notes and interconnect them, the org-roam package is great.

After using org-journal, I also decided to use org-roam to write my journal.

If you don’t already do so, I advise you to start writing your journal. If you haven’t done so yet, I suggest you start writing your journal. This journal would allow you, for example, to improve your mental clarity, follow your evolution, and facilitate your personal growth.

(use-package org-roam
  :after org
  :init
  (setq org-roam-v2-ack t)
  (setq my/daily-note-filename "%<%Y-%m-%d>.org.gpg"
        my/daily-note-header "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
  :custom
  (org-roam-capture-templates
   '(("d" "default" plain "%?"
      :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n")
      :unnarrowed t)))
  (org-roam-completion-everywhere t)
  (org-roam-dailies-directory "journal/")
  (org-roam-dailies-capture-templates
   `(("d" "default" plain
      "* %?"
      :if-new (file+head ,my/daily-note-filename
                         ,my/daily-note-header)
      :empty-lines 1)

     ("j" "journal" plain
      "** %<%I:%M %p>  :journal:\n\n%?\n\n"
      :if-new (file+head+olp ,my/daily-note-filename
                             ,my/daily-note-header
                             ("Journal"))
      :empty-lines 1)
     ("m" "meeting" entry
      "** %<%I:%M %p> - %^{Meeting Title}  :meeting:\n\n%?\n\n"
      :if-new (file+head+olp ,my/daily-note-filename
                             ,my/daily-note-header
                             ("Meetings"))
      :empty-lines 1)))
  (org-roam-directory "~/.personal/notes")
  :custom (org-roam-graph-viewer "/usr/bin/qutebrowser")
  :config (org-roam-setup))