Emacs Configuration

Emacs, the editor of a lifetime?

Of course I tried multiple editors over the years, from the TurboPascal, TurboC, Microsoft Visual editions, Atom, Notepad, Notepad++, Sublime, etc, and I can’t remember when the trigger happened to stick to Emacs.

I suspect, after a while, one is fade-up to always learn a new interface every couple of years and Emacs has been there for decades, is multi-platform, quite light with the daemon-mode, and does the job perfectly.

Sooner or later, we quickly find comfort in being able to perform most of the common actions by just starting a new buffer to read RSS, code, send an email, manage a project status or simply take notes.

Like every new beginner I started with M-x customize and slowly after becoming confident, I moved to the setup of my dot Emacs file.

Could it be that Emacs is the Editor of a/my lifetime?

My dot file ~/.config/emacs/init.el for Emacs 29.4

Let me share with you a part of my personal config and the customization of my top packages. It is published in the hope it might help someone to start or enhance his/her personal setup.

I prefer to use (setq ...) rather than Customize to change the value of a variable as I find it easier to replicate changes to a different machines.

Basic interface changes

(use-package emacs
  :ensure nil
  :init
  (global-font-lock-mode t)             ;; add colors
  (setq font-lock-maximum-decoration t) ;; add maximum coloration
  (setq column-number-mode t)           ;; add the column number
  (transient-mark-mode t)
  (setq tab-width 8)                    ;; set tab to 8 spaces
  (menu-bar-mode -1)                    ;; remove the menu bar
  (scroll-bar-mode -1)                  ;; remove the right scroll bar
  (tool-bar-mode -1)                    ;; remove the tool bar
  (setq inhibit-splash-screen t)        ;; Remove splash screen
  (set-default 'indicate-empty-lines t) ;; Explicitly show the end of a buffer

  ;;Display time in the bar
  (setq display-time-24hr-format t)
  (display-time)

  ;; with HiDPI => better enable textsize-mode
  (add-to-list 'default-frame-alist '(font . "Noto Sans Mono-11"))
  
  ;; Calendar
  (setq calendar-week-start-day 1)        ; Weeks start on monday
  (setq calendar-date-style 'european)

  ;; remap zap-to-char M-z to zap-up-to-char for not including the final char
  (global-set-key [remap zap-to-char] 'zap-up-to-char)

  ;; enable the copy-paste between X applications
  (setq x-select-enable-clipboard t)

  ;; place all files in a temporary directory
  (setq backup-directory-alist
        `(("." . ,(concat user-emacs-directory "tmp-backups"))))

  ;; make buffer uniques
  (require 'uniquify)
  (setq uniquify-buffer-name-style 'post-forward)

  ;; I-search
  ;; display numbers of the current and the other matches
  (setq isearch-lazy-count t)
  (setq lazy-count-prefix-format "(%s/%s) ")
  (setq lazy-count-suffix-format nil)
  ;; make regular Isearch interpret empty space as regex any char
  ;; between words. 
  (setq search-whitespace-regexp ".*?")

  (setq auth-sources '("~/.authinfo.gpg")) ; force encrypted content
  (setq epg-pinentry-mode 'loopback) ;; ask GPG password in the minibuffer
  
  ;; Man page select window directly.
  ;; so the cursor moves to the Man window
  (setq Man-notify-method 'aggressive)

  ;; add MELPA to the package manager
  (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

  ;; Permanently force Emacs to indent with spaces, never with TABs:
  (setq-default indent-tabs-mode nil)

  ;; replace the default list-buffer with the new ibuffer
  (defalias 'list-buffers 'ibuffer)
)

Package manager

use-package is part of Emacs 29.1. There is no need for a specific bootstrap.

Theme

These days I like to use the Modus-operandi from Protesilaos Stavrou https://protesilaos.com/emacs/modus-themes. The color contract is really very pleasant on the eyes.

(use-package modus-themes
  :ensure t
  :config
  (load-theme 'modus-operandi t)

Default font

I prefer the monospaced fonts when reading code. And the monospace style makes table alignment always perfect for text files because all characters have the same width.

Some noticeable fonts are:

(add-to-list 'default-frame-alist 
    '(font . "Noto Sans Mono-11"))

Magit to manage GIT repository.

This application is really amazing to manage GIT repositories.

(use-package magit
  :defer t
  :ensure t
  :bind ("C-x g" . magit-status))

(use-package magit-todos
   :ensure t)

Highlight TODOs in code.

Very convenient to highlight comments in the code for any part which needs enhancement or as to be reviewed.

(use-package hl-todo
  :defer t
  :ensure t
  :config
  (add-hook 'c-mode-hook #'hl-todo-mode))

Markdown

Useful to update Markdown files as well as exporting simple HTML pages.

(use-package markdown-mode
  :defer t
  :ensure t
  :mode (("README\\.md\\'" . gfm-mode))
  :config
  (setq markdown-italic-underscore t)
  (setq markdown-make-gfm-checkboxes-buttons t)
  (setq markdown-gfm-uppercase-checkbox t)
  (setq markdown-command "cmark-gfm -e footnotes -e table -e strikethrough -e autolink -e tagfilter -e tasklist")
  (setq markdown-css-paths '("https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.min.css"))
  (setq  markdown-xhtml-header-content "
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<style>
  html {
      max-width: 70ch;
      padding: 3em 1em;
      margin: auto;
      line-height: 1.75;
      font-size: 1.25em;
  }
  h1,h2,h3,h4,h5,h6 {
    margin: 3em 0 1em;
  }

  p,ul,ol {
    margin-bottom: 2em;
    color: #1d1d1d;
    font-family: sans-serif;
  }

  table {
    border-collapse: collapse;
    margin: 20px 0; 
  }

  thead th {
    font-weight: bold;
    border-bottom: 2px solid #000; 
    padding-right: 16px;
    padding-left: 16px;
  }
  tbody td {padding-right: 16px; padding-left: 16px;}
  tbody tr {padding-top: 2px; padding-bottom: 2px;}
  tbody tr:last-child {border-bottom: 4px solid #000;}
</style>
")
  (add-hook 'markdown-mode-hook 'turn-on-flyspell))


(use-package markdown-toc
  :ensure t 
  :diminish
  :config
  (add-hook 'markdown-mode-hook #'markdown-toc-mode))

Read ebook from within Emacs

;; read .epub file.
(use-package nov
  :defer t
  :ensure t
  :mode
  ("\\.epub$" . nov-mode))

Undo-tree to undo/redo changes

Enable easy visual rollback. This is a really powerful add-on which allows you to recover not only the previous state of a document, but also any previous ones.

;; enable undo-tree mode
(use-package undo-tree
  :ensure t
  :config
  (global-undo-tree-mode)
  (setq undo-tree-auto-save-history t)
  (setq undo-tree-visualizer-diff t)
  :bind (("C-z" . undo-tree-undo)
         ("C-S-z" . undo-tree-redo)))

And it can be done visually.

Auto complete with company

(use-package company
  :ensure t
  :config
  (setq company-idle-delay 0)
  (setq company-minimum-prefix-length 2)

  (setq company-backends
        '(company-capf
          company-yasnippet
          company-files
          (company-dabbrev-code company-gtags company-keywords)
          company-dabbrev))
  (global-company-mode t))

Coding in C

(use-package cc-mode
  :ensure nil
  :defer
  :config
  (setq c-default-style "gnu")
  (setq indent-tabs-mode t)
  (setq show-trailing-whitespace t)
  (setq display-line-numbers 'relative)

  (setq-default c-doc-comment-style
                '((c-mode . doxygen)))
  
  (add-hook 'c-mode-common-hook #'hs-minor-mode)
)

(global-set-key [f9] 'compile)
(setq compilation-scroll-output 'first-error)
;; 2024-Nov-17 replaced by eglot
(setenv "GTAGSLIBPATH" (concat (file-truename "~/.gtags/")
                               ":"
                               (file-truename "~/proj2")
                               ":"
                               (file-truename "~/proj1")))

(use-package ggtags
  :disabled
  :config
  (add-hook 'c-ts-mode-hook  #'ggtags-mode)
)

Eglot the easy-to-use Emacs Client for Language Server Protocol (LSP) servers, it has for now replaced ggtags.

(use-package eglot
  :ensure t
  :config
  (add-hook 'c-ts-mode-hook #'eglot-ensure)
  (add-to-list 'eglot-server-programs
               '((c-mode c-ts-mode) . ("ccls")))
)

Clean the extra spaces

Used to clean the extra spaces at the end of the line and on new “empty” lines.

(use-package whitespace-cleanup-mode
      :ensure t
      :config
      (add-hook 'LaTeX-mode-hook #'whitespace-cleanup-mode)
      (add-hook 'org-mode-hook #'whitespace-cleanup-mode)
      (add-hook 'c-mode-hook #'whitespace-cleanup-mode)
      (add-hook 'c-ts-mode-hook #'whitespace-cleanup-mode))

Checking and Correcting Spelling

Useful to check the spelling of a single highlighted word with C-M-i

(use-package ispell
  :config
  (setq ispell-program-name "aspell")
  (setq ispell-list-command "list") ; for flyspell
  (setq ispell-extra-args '("--sug-mode=fast")) ; can be fast or ultra
  (setq ispell-local-dictionary "english"))

Here is an example of `Hello’ with a typo. typo in hello

Org-mode configuration

It is said that lots of users switch to Emacs for Org-mode. It is true this is an amazing add-on for daily production. I use it to track my TO-DOs, time spend on a task and take notes.

(use-package orgalist
  :ensure t)
  
(use-package org
  :ensure t
  :mode ("\\.org\\'" . org-mode)
  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture))
  :config
  ;; org-mode agenda file for todo
  (setq org-agenda-files (list "~/file-to-agenda/agenda.org"))
  (setq org-log-done 'time) ;; capture timestamp when done
  (setq org-todo-keywords
        '((sequence "TODO(t)" "INPROGRESS(p)" "DONE(d)")))
  (setq org-todo-keyword-faces
        '(("INPROGRESS" . (:foreground "light blue" :weight bold))))
  (setq org-src-fontify-natively t)  ;; fontify code in code blocks

  (setq org-clock-persist 'history)
  (org-clock-persistence-insinuate)

  (setq org-agenda-archives-mode t)
  (setq org-use-speed-commands t)

  (setq org-capture-templates
        '(("t" "todo" entry (file "~/Documents/refile.org")
           "* TODO %?\nSCHEDULED: %t" :empty-lines 1 :prepend t)

          ("a" "activity" entry (file+olp+datetree  "~/Documents/journal.org")
           "* INPROGRESS %^{Description} %^G\nSCHEDULED: %t\nAdded: %U\n%?"
           :empty-lines 1
           :clock-in t
           :clock-keep t
           :tree-type week)
          ))


  ;; when using org Motion C-c C-j quickly jump to headline
  ;; C-u C-c C-j will use the other interface
  (setq org-outline-path-complete-in-steps nil)
  (setq org-goto-interface 'outline-path-completion)

  (setq org-clock-idle-time 15)
  (setq org-agenda-clock-consistency-checks
        '(:max-duration "4:00" :min-duration 0 :max-gap 0 :gap-ok-around ("4:00")))

  (setq org-agenda-span 'day)
  (setq org-agenda-archives-mode t)
  (setq org-use-speed-commands t)

  (setq org-refile-targets '((nil :maxlevel . 9)
                             (org-agenda-files :maxlevel . 2))))

Org time computation

Rather than the absolute time between 2 dates, I prefer to compute the working time in working hours. Of course one would adjust on what is the number of hours composing a working day.

(use-package org-duration
  :config
    (setq org-duration-units
        `(("min" . 1)
          ("h" . 60)
          ("d" . ,(* 60 8))
          ("w" . ,(* 60 8 5))
          ("m" . ,(* 60 8 5 4))
          ("y" . ,(* 60 8 5 4 10))))
  (org-duration-set-regexps))

Note taking with Org-roam

Used for my plain-text knowledge management system, which is of course a GIT repository synced between my different devices.

So far, with the few notes I have, the research is fast and it always comes handy to look for information.

(use-package org-roam
  :ensure t
  :after org
  :init
  (setq org-roam-v2-ack t)
  :config
  (setq org-roam-directory "~/roam-notes")
  (setq org-roam-completion-everywhere t)

  (setq org-roam-capture-templates
        '(("d" "default" plain
           "* Summary\n\n%?\n\n* References:\n\n"
           :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
           :unnarrowed t)))

  (org-roam-setup)
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n t" . org-roam-tag-add)
         :map org-mode-map
         ("C-M-i" . completion-at-point)))

File management with dired

a will open the file or the directory in the same buffer.

(use-package dired
  :config
  (put 'dired-find-alternate-file 'disabled nil)
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq dired-dwim-target t)
  (setq dired-listing-switches "-alh") ;; human readable file size
  )

Expands the directories inline as subtree. This eliminates the need to open new buffers for nested directories.

(use-package dired-subtree
  :ensure t
  :after dired
  :bind
  ( :map dired-mode-map
    ("<tab>" . dired-subtree-toggle)
    ("TAB" . dired-subtree-toggle)
    ("<backtab>" . dired-subtree-remove)
    ("S-TAB" . dired-subtree-remove))
  :config
  (setq dired-subtree-use-backgrounds nil))

dired-subtree

Dired-git-info augments Dired mode with inline GIT status information alongside the file listing. This helps to have a quick view of the last comments and commit date when browsing GIT directories.

(use-package dired-git-info
    :ensure t
    :bind (:map dired-mode-map
                (")" . dired-git-info-mode)))

dired-git-info

Parenthesis highlighting

Highlights the parent parenthesis when inside of a group.

(show-paren-mode 1)

And automatically insert the matching pair for (), [], {}

(setq skeleton-pair t) 

Latex with AUCTex

Latex is a powerful markup language used to publish amazing professional documentation or publications.

I find it indispensable for large projects as it allows, similarly with writing a program, a split of the documentation into sections, references and style.

An excellent way to focus on the content rather than the style or fighting with lists or numbering, compared to other word processor applications.

(use-package reftex
  :ensure t
  :defer t
  :config
  (setq reftex-plug-into-AUCTeX t
        reftex-toc-split-windows-horizontally t
        reftex-toc-include-file-boundaries t))

(use-package tex-site
  :ensure auctex
  :config
  ;; some-config-here
  (setq TeX-auto-save t)
  (setq TeX-parse-self t)

  (use-package latex
    :defer t
    :bind
    (:map LaTeX-mode-map
          ("<C-tab>" . outline-toggle-children))
    :config
    (setq TeX-PDF-mode t)
    (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
    (add-hook 'LaTeX-mode-hook 'turn-on-flyspell)
    (add-hook 'LaTeX-mode-hook 'outline-minor-mode)))

Highlight the line my cursor is on

It is easy to loose the small blinking cursor and the highlight of the full line always helps me.

(global-hl-line-mode 1)

Quickly insert date

When not in org-mode and especially for quick updates to be added to a changelog, I have the convenient lisp function to insert the date in 3 different formats.

;; Usage:
;;   ‘C-c d’: 2004-Apr-13
;;   ‘C-u C-c d’: 13.04.2004
;;   ‘C-u C-u C-c d’: Sunday, 13. April 2004

(defun insert-date (prefix)
  "Insert the current date. With prefix-argument, use ISO format. With
   two prefix arguments, write out the day and month name."
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%Y-%b-%d")
                 ((equal prefix '(4)) "%d.%m.%Y")
                 ((equal prefix '(16)) "%A, %d. %B %Y")))
        (system-time-locale "en_GB"))
    (insert (format-time-string format))))

(global-set-key (kbd "C-c d") 'insert-date)

Snippets

As soon as I have to provide similar content or generate a custom boilerplate text multiple times, I create a new snippet for the major mode I am working with.

(use-package yasnippet
  :ensure t
  :diminish yas-minor-mode
  ;; (add-hook 'message-mode-hook 'yas-minor-mode)
  :config
  (use-package yasnippet-snippets
    :ensure t)

Quickly switch between windows

A handy shortcut to switch directly to a specific window without having to use the command C-x o or M-x other-window multiple times in a cyclic order.

M-[ will display the window number in the top left corner of each window.

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

But it does way more if needed …
You can find more info on the official site of abo-abo https://github.com/abo-abo/ace-window.

Quickly move the cursor to a word

Rather than clicking with the mouse or using multiple times C-n, C-p … the avy package helps to jump right at the work position.

(use-package avy
  :ensure t
  :bind ("C-:" . avy-goto-char-2))

Minibuffer Completion with vertico, consult, marginalia, orderless

Vertico is a minimalist completion framework. It makes easier to navigate and select from suggestions in the different contexts such as command execution, file paths, etc

This is for me a big plus.

(use-package vertico
  :ensure t
  :init
  (vertico-mode)
  (setq vertico-scroll-margin 0)
  (setq vertico-count 10)
  (setq vertico-resize nil)
  (setq vertico-cycle t))

Consult helps for navigation, search and completion

(use-package consult
  :ensure t
  :bind (("M-s s" . #'consult-line)
         ("M-s i" . #'consult-info)
         ("M-i" . #'consult-imenu)
         ("M-g M-g" . #'consult-goto-line)
         ("M-s l" . #'consult-locate)
         ("M-s g" . #'consult-grep)
         ("C-x b" . #'consult-buffer)
         ("C-x r b" . #'consult-bookmark)
        ;;  ("C-y" . #'consult-yank-pop)
         
         :map minibuffer-local-map
         ("C-r" . consult-history)
         :map org-mode-map
         ("M-i" . consult-org-heading)))

Orderless, allows matching input in any order, using multiple patterns separated by spaces. this makes it easier to narrow down the completion candidates.

(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion)))))

While Marginalia displays helpful annotations alongside completion candidates.

(use-package marginalia
  :ensure t
  :bind (("M-A" . marginalia-cycle)
         :map minibuffer-local-map
         ("M-A" . marginalia-cycle))
  :init
  (marginalia-mode))

RSS new reader

Elfeed is perfect to quickly read news like newsgroups without having to switch application or go on the web.

(use-package elfeed
  :ensure t
  :defer t
  :config
  (setq elfeed-use-curl t)
  (setq elfeed-sort-order 'ascending)
  (setq elfeed-feeds '(("https://xkcd.com/atom.xml" comics)
                       ("https://planet.emacslife.com/atom.xml" news))))

Quickly highlight a text block

(use-package boxquote
  :ensure t)

Example:

 ,----[ title ]
 | Suspendisse metus libero, tempus id luctus in, 
 | lobortis elementum ipsum. Sed erat leo, 
 | porttitor eu maximus sit amet, rutrum eu dolor.
 | Mauris ac neque facilisis augue maximus placerat.
 `----

tree-sitter

Emacs29+ integrates tree-sitter support, a powerful library to enhance major mode with more precise syntax highlighting, indentation, Imenu function jump.

Emacs29+ ships already defined major modes for tree-sitter, such as: C, C++, Java, Rust, Go, Python, Javascript, Typescript, JSON, YAML, TOML, CSS, Bash, Dockerfile, …

(use-package treesit
  :init
  (setq treesit-language-source-alist
        '((bash . ("https://github.com/tree-sitter/tree-sitter-bash"))
          (c . ("https://github.com/tree-sitter/tree-sitter-c"))
          (cpp . ("https://github.com/tree-sitter/tree-sitter-cpp"))
          (css . ("https://github.com/tree-sitter/tree-sitter-css"))
          (html . ("https://github.com/tree-sitter/tree-sitter-html"))  ; emacs 30.1
          (toml . ("https://github.com/tree-sitter/tree-sitter-toml"))
          (json . ("https://github.com/tree-sitter/tree-sitter-json"))
          (go . ("https://github.com/tree-sitter/tree-sitter-go"))
          (gomod . ("https://github.com/camdencheek/tree-sitter-go-mod"))
          (yaml . ("https://github.com/ikatyang/tree-sitter-yaml"))))

  :config
  (add-to-list 'major-mode-remap-alist '(bash-mode . bash-ts-mode))
  (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))
  (add-to-list 'major-mode-remap-alist '(css-mode . css-ts-mode))
  (add-to-list 'major-mode-remap-alist '(json-mode . json-ts-mode))
  (add-to-list 'major-mode-remap-alist '(toml-mode . toml-ts-mode))
  (add-to-list 'major-mode-remap-alist '(html-mode . html-ts-mode))
)

Once defined, the language grammars can be installed with

M-x treesit-install-language-grammar

or

(mapc #'treesit-install-language-grammar (mapcar #'car treesit-language-source-alist))

The compiled libraries then get installed in the .config/emacs/tree-sitter folder:

.config/emacs/tree-sitter/
|-- libtree-sitter-bash.so
|-- libtree-sitter-c.so
...
|-- libtree-sitter-toml.so
`-- libtree-sitter-yaml.so

It is possible to check the grammar was correctly installed with

(treesit-language-available-p 'c)
t

External references

  1. https://www.gnu.org/software/emacs/download.html