dotfiles/org/config/emacs.org

267 KiB
Raw Blame History

Emacs Configuration

Introduction

After a couple of years using Spacemacs and a failed attempt at switching to DoomEmacs, Im finally switching back to a vanilla configuration! Why? Because I got tired of the framework getting in my way when I wanted to do stuff. Im sure this is more applicable to Spacemacs than DoomEmacs since the latter has nice macros written to easily add new packages and configure them, such as package!, after!, and others. But ultimately, I wanted to have a system I designed entirely, with the keybinds I want, the packages I want.

Aso, why Emacs? You know this famous quote:

Emacs is a great operating system, it just lacks a good text editor.

Its actually pretty true in my opinion. Emacs is basically a Lisp machine with a default text editor, programmed with EmacsLisp, a general-purpose programming language. Therefore, if you want to do something in Emacs, with enough Elisp you can do it — if its not in Emacs already, that is.

Dammit Emacs…
XKCD n°378: Real Programmers

Basic configuration

Early Init

The early init file is the file loaded before anything else in Emacs. This is where I put some options in order to disable as quickly as possible some built-in features of Emacs before they can be even loaded, speeding Emacs up a bit.

(setq package-enable-at-startup nil
      inhibit-startup-message   t
      frame-resize-pixelwise    t  ; fine resize
      package-native-compile    t) ; native compile packages
(scroll-bar-mode -1)               ; disable scrollbar
(tool-bar-mode -1)                 ; disable toolbar
(tooltip-mode -1)                  ; disable tooltips
(set-fringe-mode 10)               ; give some breathing room
(menu-bar-mode -1)                 ; disable menubar
(blink-cursor-mode 0)              ; disable blinking cursor

Emacs Behavior

Editing Text in Emacs

I never want to keep trailing spaces in my files, which is why Im doing this:

(add-hook 'before-save-hook #'whitespace-cleanup)

I dont understand why some people add two spaces behind a full stop, I sure dont. Lets tell Emacs.

(setq-default sentence-end-double-space nil)

There is a minor mode in Emacs which allows to have a finer way of jumping from word to word: global-subword-mode. It detects if what Emacs usually considers a word can be understood as several words, as in camelCase words, and allows us to jump words on this finer level.

(global-subword-mode 1)

Lastly, I want the default mode for Emacs to be Emacs Lisp.

(setq-default initial-major-mode 'emacs-lisp-mode)
Indentation

I dont like tabs. They rarely look good, and if I need it I can almost always tell Emacs to use them through a .dir-locals.el file or through the config file of my code formatter. So by default, lets disable them:

(setq-default indent-tabs-mode nil)
(add-hook 'prog-mode-hook (lambda () (setq indent-tabs-mode nil)))

Just to go on a little tangent here: I dont exactly hate tabs, but I find them really annoying when your text editor knows only them. Sure, for indentation they work great, and they allow different people getting different settings in their text editor depending on their preferred tastes —some may prefer 2 spaces tabs, some may prefer 4 spaces tabs, some deranged people prefer 8 spaces tabs, and some monsters prefer 3!

But the thing is, once you indented your code and then you need alignment, tabs dont work anymore! Or they may on your text editor but not on your coworkers! (Hes the one using 3 spaces tabs by the way).

So, is the answer to use spaces instead of tabs, and screw peoples preferences in terms of tabs width? No, I say the answer is more moderate than that, and it might frighten or anger some of you at first: use both spaces and tabs. Now, before you lynch me on the main avenue in front of everyone, let me tell you absolutely no one should ever be mixing spaces and tabs for indentation, that would be absolutely terrible and would bring the worst of both worlds. Whats the best of both worlds then?

Tabs for indentation

Spaces for alignment

I havent found a way to automate that in Emacs yet aside from formatters config file, and tabs look bat in EmacsLisp anyway, so Ill stick with spaces by default and change it where needed.

Programming Modes

First off, my definition of what makes a “programming mode” doesnt exactly fit mine, so on top of prog-mode, lets add a few other modes.

Modes
prog-mode
latex-mode
(mapconcat (lambda (mode) (format "%s-hook" (car mode)))
           modes
           " ")
Line Number

Since version 26, Emacs has a built-in capacity of displaying line numbers on the left-side of the buffer. This is a fantastic feature that should actually be the default for all programming modes.

(dolist (mode '(<<prog-modes-gen()>>))
  (add-hook mode #'display-line-numbers-mode))
Folding code

Most programming languages can usually have their code folded, be it code between curly braces, chunks of comments or code on another level of indentation (Python, why…?). The minor-mode that enables that is hs-minor-mode, lets enable it for all of these programming modes:

(dolist (mode '(<<prog-modes-gen()>>))
  (add-hook mode #'hs-minor-mode))

Stay Clean, Emacs!

As nice as Emacs is, it isnt very polite or clean by default: open a file, and it will create backup files in the same directory. But then, when you open your directory with your favorite file manager and see almost all of your files duplicated with a ~ appended to the filename, it looks really uncomfortable! This is why I prefer to tell Emacs to keep its backup files to itself in a directory it only will access.

(setq backup-directory-alist `(("." . ,(expand-file-name ".tmp/backups/"
                                                         user-emacs-directory))))

It also loves to litter its init.el with custom variables here and there, but the thing is: I regenerate my init.el each time I tangle this file! How can I keep Emacs from adding stuff that will be almost immediately lost? Did someone say custom file?

(setq-default custom-file (expand-file-name ".custom.el" user-emacs-directory))
(when (file-exists-p custom-file) ; Dont forget to load it, we still need it
  (load custom-file))

If we delete a file, we want it moved to the trash, not simply deleted.

(setq delete-by-moving-to-trash t)

Finally, the scatch buffer always has some message at its beginning, I dont want it!

(setq-default initial-scratch-message nil)

Stay Polite, Emacs!

When asking for our opinion on something, Emacs loves asking us to answer by “yes” or “no”, but in full! Thats very rude! Fortunately, we can fix this.

(defalias 'yes-or-no-p 'y-or-n-p)

This will make Emacs ask us for either hitting the y key for “yes”, or the n key for “no”. Much more polite!

It is also very impolite to keep a certain version of a file in its buffer when said file has changed on disk. Lets change this behavior:

(global-auto-revert-mode 1)

Much more polite! Note that if the buffer is modified and its changes havent been saved, it will not automatically revert the buffer and your unsaved changes wont be lost. Very polite!

Misc

Lets raise Emacs undo memory to 10MB, and make Emacs auto-save our files by default.

(setq undo-limit        100000000
      auto-save-default t)
(setq window-combination-resize t) ; take new window space from all other windows

Personal Information

Emacs needs to know its master! For various reasons by the way, some packages rely of these variables to know who it is talking to or dealing with, such as mu4e which will guess who you are if you havent set it up correctly.

(setq user-full-name       "Lucien Cartier-Tilet"
      user-real-login-name "Lucien Cartier-Tilet"
      user-login-name      "phundrak"
      user-mail-address    "lucien@phundrak.com")

Visual Configuration

The first visual setting in this section will activate the visible bell. What it does is I get a visual feedback each time I do something Emacs doesnt agree with, like tring to go up a line when Im already at the top of the buffer.

(setq visible-bell t)

It is nicer to see a cursor cover the actual space of a character.

(setq x-stretch-cursor t)

When text is ellipsed, I want the ellipsis marker to be a single character of three dots. Lets make it so:

(setq truncate-string-ellipsis "…")

Thanks to this fork of Emacs, it is now possible to set some transparency to the background of Emacs only and not to the entire frame.

(add-hook 'server-after-make-frame-hook (lambda ()
                                          (set-frame-parameter (selected-frame)
                                                               'alpha-background 0.7)))

Modeline Modules

I sometimes use Emacs in fullscreen, meaning my usual taskbar will be hidden. This is why I want the current date and time to be displayed, in an ISO-8601 style, although not exactly ISO-8601 (this is the best time format, fight me).

(setq display-time-format "%Y-%m-%d %H:%M")
(display-time-mode 1) ; display time in modeline

Something my taskbar doesnt have is a battery indicator. However, I want it enabled only if I am on a laptop or if a battery is available.

(let ((battery-str (battery)))
  (unless (or (equal "Battery status not available" battery-str)
              (string-match-p (regexp-quote "N/A") battery-str))
    (display-battery-mode 1)))

This isnt a modeline module per se, but we have an indicator of the current line in Emacs. And although it is useful, I also often wish to know which column Im on. This can be activated like so:

(column-number-mode)

The following code is, as will several chunks of code in this config, borrowed from TECs configuration. It hides the encoding information of the file if the file itself is a regular UTF-8 file with \n line ending. Be aware the doom-modeline-buffer-encoding variable is usabel here only because I use the Doom modeline as seen below.

(defun modeline-contitional-buffer-encoding ()
  "Hide \"LF UTF-8\" in modeline.

It is expected of files to be encoded with LF UTF-8, so only show
the encoding in the modeline if the encoding is worth notifying
the user."
  (setq-local doom-modeline-buffer-encoding
              (unless (and (memq (plist-get (coding-system-plist buffer-file-coding-system) :category)
                                 '(coding-category-undecided coding-category-utf-8))
                           (not (memq (coding-system-eol-type buffer-file-coding-system) '(1 2))))
                t)))

Now, lets automate the call to this function in order to apply the modifications to the modeline each time we open a new file.

(add-hook 'after-change-major-mode-hook #'modeline-contitional-buffer-encoding)

Fonts

I dont like the default font I usually have on my machines, I really dont. I prefer Cascadia Code, as it also somewhat supports the IPA.

(defvar phundrak/default-font-size 90
  "Default font size.")

(defvar phundrak/default-font-name "Cascadia Code"
  "Default font.")

(add-hook 'server-after-make-frame-hook
          (lambda ()
            (when (find-font (font-spec :name phundrak/default-font-name))
              (set-face-attribute 'default nil
                                  :font phundrak/default-font-name
                                  :height phundrak/default-font-size))))

Frame Title

This is straight-up copied from TECs configuration. See their comment on the matter.

(setq frame-title-format
      '(""
        "%b"
        (:eval
         (let ((project-name (projectile-project-name)))
           (unless (string= "-" project-name)
             (format (if (buffer-modified-p) " ◉ %s" "  ●  %s - Emacs") project-name))))))

Nice Macros From Doom-Emacs

Doom-Emacs has some really nice macros that can come in really handy, but since I prefer to rely on my own configuration, Ill instead just copy their code here. First we get the after! macro:

(require 'cl-lib)
(defmacro after! (package &rest body)
  "Evaluate BODY after PACKAGE have loaded.

PACKAGE is a symbol or list of them. These are package names, not modes,
functions or variables. It can be:

- An unquoted package symbol (the name of a package)
    (after! helm BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
  and git-gutter have loaded)
    (after! (magit git-gutter) BODY...)
- An unquoted, nested list of compound package lists, using any combination of
  :or/:any and :and/:all
    (after! (:or package-a package-b ...)  BODY...)
    (after! (:and package-a package-b ...) BODY...)
    (after! (:and package-a (:or package-b package-c) ...) BODY...)
  Without :or/:any/:and/:all, :and/:all are implied.

This is a wrapper around `eval-after-load' that:

1. Suppresses warnings for disabled packages at compile-time
2. Supports compound package statements (see below)
3. Prevents eager expansion pulling in autoloaded macros all at once"
  (declare (indent defun) (debug t))
  (if (symbolp package)
      (list (if (or (not (bound-and-true-p byte-compile-current-file))
                    (require package nil 'noerror))
                #'progn
              #'with-no-warnings)
            ;; We intentionally avoid `with-eval-after-load' to prevent eager
            ;; macro expansion from pulling (or failing to pull) in autoloaded
            ;; macros/packages.
            `(eval-after-load ',package ',(macroexp-progn body)))
    (let ((p (car package)))
      (cond ((not (keywordp p))
             `(after! (:and ,@package) ,@body))
            ((memq p '(:or :any))
             (macroexp-progn
              (cl-loop for next in (cdr package)
                       collect `(after! ,next ,@body))))
            ((memq p '(:and :all))
             (dolist (next (cdr package))
               (setq body `((after! ,next ,@body))))
             (car body))))))

Custom Elisp

Dired functions

phundrak/open-marked-files

This function allows the user to open all marked files from a dired buffer as new Emacs buffers.

(defun phundrak/open-marked-files (&optional files)
  "Open all marked FILES in dired buffer as new Emacs buffers."
  (interactive)
  (let* ((file-list (if files
                        (list files)
                      (if (equal major-mode "dired-mode")
                          (dired-get-marked-files)
                        (list (buffer-file-name))))))
   (mapc (lambda (file-path)
           (find-file file-path))
         (file-list))))

xah/open-in-external-app

(defun xah/open-in-external-app (&optional file)
  "Open FILE or dired marked FILE in external app.
The app is chosen from the users OS preference."
  (interactive)
  (let ((file-list (if file
                       (list file)
                     (if (equal major-mode "dired-mode")
                         (dired-get-marked-files)
                       (list (buffer-file-name)))))
        (do-it-p   (if (<= (length file-list) 5)
                       t
                     (y-or-n-p "Open more than 5 files? "))))
    (when do-it-p
      (mapc (lambda (file-path)
              (let ((process-connection-type nil))
                (start-process "" nil "xdg-open" file-path)))
            file-list))))

xah/dired-sort

(defun xah/dired-sort ()
  "Sort dired dir listing in different ways.
Prompt for a choice."
  (interactive)
  (let (sort-by arg)
    (setq sort-by (completing-read "Sort by:" '("name" "size" "date" "extension")))
    (pcase sort-by
      ("name" (setq arg "-ahl --group-directories-first"))
      ("date" (setq arg "-ahl -t --group-directories-first"))
      ("size" (setq arg "-ahl -S --group-directories-first"))
      ("extension" (setq arg "ahlD -X --group-directories-first"))
      (otherwise (error "Dired-sort: unknown option %s" otherwise)))
    (dired-sort-other arg)))

Switch between buffers

Two default shortcuts I really like from Spacemacs are SPC b m and SPC b s, which bring the user directly to the *Messages* buffer and the *scratch* buffer respectively. These functions do exactly this.

(defun switch-to-messages-buffer ()
  "Switch to Messages buffer."
  (interactive)
  (switch-to-buffer (messages-buffer)))

(defun switch-to-scratch-buffer ()
  "Switch to Messages buffer."
  (interactive)
  (switch-to-buffer "*scratch*"))

Screenshots

Since Emacs27, it is possible for Emacs to take screenshots of itself in various formats. Im mainly interested by the SVG and PNG format, so Ill only write functions for these. It isnt really redundant with the screenshot.el package used here since these functions take a screenshot of Emacs as a whole rather than of a code snippet.

First, we have a general function which is a slight modification of the function shared by Alphapapa in this Reddit comment. It has been modified so it is possible to pass the function an argument for the format the screenshot will be taken as, and if type is nil the user can still chose it.

(defun self-screenshot (&optional type)
  "Save a screenshot of type TYPE of the current Emacs frame.
As shown by the function `', type can weild the value `svg',
`png', `pdf'.

This function will output in /tmp a file beginning with \"Emacs\"
and ending with the extension of the requested TYPE."
  (interactive)
  (let* ((type (if type type
                 (intern (completing-read "Screenshot Type: "
                                          '(png svg pdf postscript)))))
         (extension (pcase type
                      ('png        ".png")
                      ('svg        ".svg")
                      ('pdf        ".pdf")
                      ('postscript ".ps")
                      (otherwise (error "Cannot export screenshot of type %s" otherwise))))
         (filename (make-temp-file "Emacs-" nil extension))
         (data     (x-export-frames nil type)))
    (with-temp-file filename
      (insert data))
    (kill-new filename)
    (message filename)))

For conveniences sake, Ill also write two functions dedicated to taking a screenshot in the SVG format and the PNG format respectively.

(defun self-screenshot-svg ()
  "Save an SVG screenshot of Emacs.
See `self-screenshot'."
  (interactive)
  (self-screenshot 'svg))

(defun self-screenshot-png ()
  "Save a PNG screenshot of Emacs.
See `self-screenshot'."
  (interactive)
  (self-screenshot 'png))

These functions were used to take the screenshots you can see in this document.

Org Functions

Handle new windows

The two functions below allow the user to not only create a new window to the right or below the current window (respectively), but also to focus the new window immediately.

(defun split-window-right-and-focus ()
  "Spawn a new window right of the current one and focus it."
  (interactive)
  (split-window-right)
  (windmove-right))

(defun split-window-below-and-focus ()
  "Spawn a new window below the current one and focus it."
  (interactive)
  (split-window-below)
  (windmove-down))

(defun kill-buffer-and-delete-window ()
  "Kill the current buffer and delete its window."
  (interactive)
  (progn
    (kill-this-buffer)
    (delete-window)))

phundrak/toggle-org-src-window-split

(defun phundrak/toggle-org-src-window-split ()
  "This function allows the user to toggle the behavior of
`org-edit-src-code'. If the variable `org-src-window-setup' has
the value `split-window-right', then it will be changed to
`split-window-below'. Otherwise, it will be set back to
`split-window-right'"
  (interactive)
  (if (equal org-src-window-setup 'split-window-right)
      (setq org-src-window-setup 'split-window-below)
    (setq org-src-window-setup 'split-window-right))
  (message "Org-src buffers will now split %s"
           (if (equal org-src-window-setup 'split-window-right)
               "vertically"
             "horizontally")))

Package Management

Repositories

By default, only GNUs repositories are available to the package managers of Emacs. I also want to use Melpa and org-modes repository, so lets add them! Note that the Elpa repository has been renamed to the gnu repository due to the addition of another Elpa repository, nongnu, which will hosts packages that do not conform to the FSFs copyright assignment. Both the gnu and the nonfree repositories are Elpa repositories now, and they were renamed in order to avoid any confusion between the two of them.

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

Straight

For my package management, I prefer to use straight (Github). This is due to its capacity of integrating nicely with use-package, which also supports general which I use for my keybindings (see below), but also because with it I can specify where to retrieve packages that are not on MELPA or ELPA but on Github and other online Git repositories too. First, lets bootstrap straight.

(defvar bootstrap-version)
(defvar comp-deferred-compilation-deny-list ()) ; workaround, otherwise straight shits itself
(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))

Now, we can refresh our package list in order to be able to install stuff.

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

From time to time, I fork some packages either because Im trying to implement something new in said package, or because the package is unmaintained and I want to continue developing it a bit more. Straight provides a nice feature for using forks of a package with its :fork option. If set to t, then straight will attempt to retrieve the package with the same name but with a different username on the same host. This username is retrieved through the following variable:

(setq straight-host-usernames
      '((github . "Phundrak")
        (gitlab . "Phundrak")))

The huge advantage of straight is it clones through git the packages it installs. This means development can be done directly on the downloaded package. However, Forge (a Magit extension for interacting with websites such as Github, Gitlab, and such) interacts by default with the forge described by the origin remote, which isnt necessarily the one I want Forge to interact with by default. Therefore, all default remotes are named straight so it wont collide with my regular development flow.

(setq straight-vc-git-default-remote-name "straight")

We finally come to the use-package installation. This is done like so:

(straight-use-package '(use-package :build t))
(setq use-package-always-ensure t)

Keybinding Management

Which-key

(use-package which-key
  :straight (:build t)
  :defer t
  :init (which-key-mode)
  :diminish which-key-mode
  :config
  (setq which-key-idle-delay 1))

General

General is an awesome package for managing keybindings. Not only is it oriented towards keychords by default (which I love), but it also provides some integration with evil so that we can declare keybindings for certain states only! This is a perfect replacement for define-key, evil-define-key, and any other function for defining keychords. And it is also possible to declare a prefix for my keybindings! By default, all keybinds will be prefixed with SPC and keybinds related to a specific mode (often major modes) will be prefixed by a comma , (and by C-SPC and M-m respectively when in insert-mode or emacs-mode). You can still feel some influence from my Spacemacs years here.

(use-package general
  :defer t
  :straight (:build t)
  :init
  (general-auto-unbind-keys)
  :config
  (general-create-definer phundrak/undefine
    :keymaps 'override
    :states '(normal emacs))
  (general-create-definer phundrak/evil
    :states '(normal))
  (general-create-definer phundrak/leader-key
    :states '(normal insert visual emacs)
    :keymaps 'override
    :prefix "SPC"
    :global-prefix "C-SPC")
  (general-create-definer phundrak/major-leader-key
    :states '(normal insert visual emacs)
    :keymaps 'override
    :prefix ","
    :global-prefix "M-m"))
(mapconcat (lambda (line)
             (let* ((key      (car line))
                    (function (cadr line))
                    (comment  (caddr line)))
               (format "\"%s%s\" %s"
                       prefix
                       key
                       (if (string= "" comment)
                           (if (or (string= "" function)
                                   (string= "nil" function))
                               "nil"
                             (concat "#'" function))
                         (format "'(%s :wk %s)"
                                 (if (or (string= "" function)
                                         (string= "nil" function))
                                     ":ignore t"
                                   function)
                                 (if (or (string= "none" comment)
                                         (string= "nil" comment))
                                     "t"
                                   (concat "\"" comment "\"")))))))
           table
           "\n")

Evil

(use-package evil
  :straight (:build t)
  :init
  (progn
    (setq evil-want-integration t
          evil-want-keybinding nil
          evil-want-C-u-scroll t
          evil-want-C-i-jump nil)
    (require 'evil-vars)
    (evil-set-undo-system 'undo-tree))
  :config
  (general-define-key
   :keymaps 'evil-motion-state-map
   "SPC" nil
   ","   nil)
  (general-define-key
   :keymaps 'evil-insert-state-map
   "C-t" nil)
  (general-define-key
   :keymaps 'evil-insert-state-map
   "U"   nil
   "C-a" nil
   "C-y" nil
   "C-e" nil)
  (evil-mode 1)
  (setq evil-want-fine-undo t) ; more granular undo with evil
  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal)

  ;; Use visual line motions even outside of visual-line-mode buffers
  (evil-global-set-key 'motion "t" 'evil-next-visual-line)
  (evil-global-set-key 'motion "s" 'evil-previous-visual-line)

  (dolist (key '("c" "C" "t" "T" "s" "S" "r" "R" "h" "H" "j" "J" "k" "K" "l" "L"))
    (general-define-key :states 'normal key nil))

  (general-define-key
   :states 'motion
   "h" 'evil-replace
   "H" 'evil-replace-state
   "j" 'evil-find-char-to
   "J" 'evil-find-char-to-backward
   "k" 'evil-substitute
   "K" 'evil-smart-doc-lookup
   "l" 'evil-change
   "L" 'evil-change-line

   "c" 'evil-backward-char
   "C" 'evil-window-top
   "t" 'evil-next-line
   "T" 'evil-join
   "s" 'evil-previous-line
   "S" 'evil-lookup
   "r" 'evil-forward-char
   "R" 'evil-window-bottom))
(use-package evil-collection
  :after evil
  :straight (:build t)
  :config
  ;; bépo conversion
  (defun my/bépo-rotate-evil-collection (_mode mode-keymaps &rest _rest)
    (evil-collection-translate-key 'normal mode-keymaps
      ;; bépo ctsr is qwerty hjkl
      "c" "h"
      "t" "j"
      "s" "k"
      "r" "l"
      ;; add back ctsr
      "h" "c"
      "j" "t"
      "k" "s"
      "l" "r"))

  (add-hook 'evil-collection-setup-hook #'my/bépo-rotate-evil-collection)

  (evil-collection-init))
(use-package undo-tree
  :defer t
  :straight (:build t)
  :init
  (global-undo-tree-mode))

Hydra

Hydra is a simple menu creator for keybindings.

(use-package hydra
  :straight (:build t)
  :defer t)

Hydras

The following hydra allows me to quickly zoom in and out in the current buffer.

(defhydra hydra-zoom ()
  "
^Zoom^                 ^Other
^^^^^^^--------------------------
[_t_/_s_] zoom in/out  [_q_] quit
[_0_]^^   reset zoom
"
  ("t" text-scale-increase "zoom in")
  ("s" text-scale-decrease "zoom out")
  ("0" text-scale-adjust "reset")
  ("q" nil "finished" :exit t))

Similarly, this one is also inspired from Spacemacs and allows the user to interact with the width of the buffer in writeroom.

(defhydra writeroom-buffer-width ()
  "
^Width^        ^Other
^^^^^^^^-----------------------
[_t_] enlarge  [_r_/_0_] adjust
[_s_] shrink   [_q_]^^   quit
"
  ("q" nil :exit t)
  ("t" writeroom-increase-width "enlarge")
  ("s" writeroom-decrease-width "shrink")
  ("r" writeroom-adjust-width   "adjust")
  ("0" writeroom-adjust-width   "adjust"))

Another similar one is for mu4e-view-mode that allows me to shrink or grow the mu4e-headers buffer when viewing an email.

(defhydra mu4e-headers-split-adjust-width ()
  "
^Zoom^                        ^Other
^^^^^^^---------------------------------
[_t_/_s_] shrink/enlarge view [_q_] quit
"
  ("q" nil :exit t)
  ("t" mu4e-headers-split-view-shrink "shrink")
  ("s" mu4e-headers-split-view-grow "enlarge"))

Similarly still, this one allows me to manage the size my Emacs windows.

(defhydra windows-adjust-size ()
  "
^Zoom^                                ^Other
^^^^^^^-----------------------------------------
[_t_/_s_] shrink/enlarge vertically   [_q_] quit
[_c_/_r_] shrink/enlarge horizontally
"
  ("q" nil :exit t)
  ("c" shrink-window-horizontally)
  ("t" enlarge-window)
  ("s" shrink-window)
  ("r" enlarge-window-horizontally))

Packages Configuration

Autocompletion

Code Autocompletion

(use-package company
  :straight (:build t)
  :defer t
  :hook (company-mode . evil-normalize-keymaps)
  :init (global-company-mode)
  :config
  (setq company-minimum-prefix-length     2
        company-toolsip-limit             14
        company-tooltip-align-annotations t
        company-require-match             'never
        company-global-modes              '(not erc-mode message-mode help-mode gud-mode)
        company-frontends
        '(company-pseudo-tooltip-frontend ; always show candidates in overlay tooltip
          company-echo-metadata-frontend) ; show selected candidate docs in echo area

        ;; Buffer-local backends will be computed when loading a major
        ;; mode, so only specify a global default here.
        company-backends '(company-capf)

        ;; These auto-complete the current selection when
        ;; `company-auto-complete-chars' is typed. This is too
        ;; magical. We already have the much more explicit RET and
        ;; TAB.
        company-auto-commit         nil
        company-auto-complete-chars nil

        ;; Only search the current buffer for `company-dabbrev' (a
        ;; backend that suggests text you open buffers). This prevents
        ;; Company from causing lag once you have a lot of buffers
        ;; open.
        company-dabbrev-other-buffers nil

        ;; Make `company-dabbrev' fully case-sensitive, to improve UX
        ;; with domai-specific words with particular casing.
        company-dabbrev-ignore-case nil
        company-dabbrev-downcase    nil))
(use-package company-dict
  :after company
  :straight (:build t)
  :config
  (setq company-dict-dir (expand-file-name "dicts" user-emacs-directory)))
(use-package company-box
  :straight (:build t)
  :after (company all-the-icons)
  :config
  (setq company-box-show-single-candidate t
        company-box-backends-colors       nil
        company-box-max-candidates        50
        company-box-icons-alist           'company-box-icons-all-the-icons
        company-box-icons-all-the-icons
        (let ((all-the-icons-scale-factor 0.8))
          `((Unknown       . ,(all-the-icons-material "find_in_page"             :face 'all-the-icons-purple))
            (Text          . ,(all-the-icons-material "text_fields"              :face 'all-the-icons-green))
            (Method        . ,(all-the-icons-material "functions"                :face 'all-the-icons-red))
            (Function      . ,(all-the-icons-material "functions"                :face 'all-the-icons-red))
            (Constructor   . ,(all-the-icons-material "functions"                :face 'all-the-icons-red))
            (Field         . ,(all-the-icons-material "functions"                :face 'all-the-icons-red))
            (Variable      . ,(all-the-icons-material "adjust"                   :face 'all-the-icons-blue))
            (Class         . ,(all-the-icons-material "class"                    :face 'all-the-icons-red))
            (Interface     . ,(all-the-icons-material "settings_input_component" :face 'all-the-icons-red))
            (Module        . ,(all-the-icons-material "view_module"              :face 'all-the-icons-red))
            (Property      . ,(all-the-icons-material "settings"                 :face 'all-the-icons-red))
            (Unit          . ,(all-the-icons-material "straighten"               :face 'all-the-icons-red))
            (Value         . ,(all-the-icons-material "filter_1"                 :face 'all-the-icons-red))
            (Enum          . ,(all-the-icons-material "plus_one"                 :face 'all-the-icons-red))
            (Keyword       . ,(all-the-icons-material "filter_center_focus"      :face 'all-the-icons-red))
            (Snippet       . ,(all-the-icons-material "short_text"               :face 'all-the-icons-red))
            (Color         . ,(all-the-icons-material "color_lens"               :face 'all-the-icons-red))
            (File          . ,(all-the-icons-material "insert_drive_file"        :face 'all-the-icons-red))
            (Reference     . ,(all-the-icons-material "collections_bookmark"     :face 'all-the-icons-red))
            (Folder        . ,(all-the-icons-material "folder"                   :face 'all-the-icons-red))
            (EnumMember    . ,(all-the-icons-material "people"                   :face 'all-the-icons-red))
            (Constant      . ,(all-the-icons-material "pause_circle_filled"      :face 'all-the-icons-red))
            (Struct        . ,(all-the-icons-material "streetview"               :face 'all-the-icons-red))
            (Event         . ,(all-the-icons-material "event"                    :face 'all-the-icons-red))
            (Operator      . ,(all-the-icons-material "control_point"            :face 'all-the-icons-red))
            (TypeParameter . ,(all-the-icons-material "class"                    :face 'all-the-icons-red))
            (Template      . ,(all-the-icons-material "short_text"               :face 'all-the-icons-green))
            (ElispFunction . ,(all-the-icons-material "functions"                :face 'all-the-icons-red))
            (ElispVariable . ,(all-the-icons-material "check_circle"             :face 'all-the-icons-blue))
            (ElispFeature  . ,(all-the-icons-material "stars"                    :face 'all-the-icons-orange))
            (ElispFace     . ,(all-the-icons-material "format_paint"             :face 'all-the-icons-pink))))))

Ivy

My main menu package is ivy which I use as much as possible Ive noticed helm can be slow, very slow in comparison to ivy so Ill use the latter as much as possible. Actually, only ivy is installed for now. I could have used ido too, but I find it to be a bit too restricted in terms of features compared to ivy.

(use-package ivy
  :straight (:build t)
  :defer t
  :diminish
  :bind (("C-s" . swiper)
         :map ivy-minibuffer-map
         ("TAB" . ivy-alt-done)
         ("C-l" . ivy-alt-done)
         ("C-t" . ivy-next-line)
         ("C-s" . ivy-previous-line)
         ("C-u" . ivy-scroll-up-command)
         ("C-d" . ivy-scroll-down-command)
         :map ivy-switch-buffer-map
         ("C-t" . ivy-next-line)
         ("C-s" . ivy-previous-line)
         ("C-l" . ivy-done)
         ("C-d" . ivy-switch-buffer-kill)
         :map ivy-reverse-i-search-map
         ("C-t" . ivy-next-line)
         ("C-s" . ivy-previous-line)
         ("C-d" . ivy-reverse-i-search-kill))
  :config
  (ivy-mode 1)
  (setq ivy-wrap                        t
        ivy-height                      17
        ivy-sort-max-size               50000
        ivy-fixed-height-minibuffer     t
        ivy-read-action-functions       #'ivy-hydra-read-action
        ivy-read-action-format-function #'ivy-read-action-format-columns
        projectile-completion-system    'ivy
        ivy-on-del-error-function       #'ignore
        ivy-use-selectable-prompt       t))

There is also prescient.el that offers some nice features when coupled with ivy, guess what was born out of it? ivy-prescient, of course!

(use-package ivy-prescient
  :after ivy
  :straight (:build t))

I warned you Id use too much all-the-icons, I did!

(use-package all-the-icons-ivy
  :straight (:build t)
  :after (ivy all-the-icons)
  :hook (after-init . all-the-icons-ivy-setup))

A buffer popping at the bottom of the screen is nice and all, but have you considered a floating buffer in the center of your frame?

(use-package ivy-posframe
  :defer t
  :after (:any ivy helpful)
  :hook (ivy-mode . ivy-posframe-mode)
  :straight (:build t)
  :init
  (ivy-posframe-mode 1)
  :config
  (setq ivy-fixed-height-minibuffer nil
        ivy-posframe-border-width   10
        ivy-posframe-parameters
        `((min-width  . 90)
          (min-height . ,ivy-height))))

Finally, lets make ivy richer:

(use-package ivy-rich
  :straight (:build t)
  :after ivy
  :init
  (ivy-rich-mode 1))

Counsel

(use-package counsel
  :straight (:build t)
  :after recentf
  :defer t
  :after ivy
  :bind (("M-x"     . counsel-M-x)
         ("C-x b"   . counsel-ibuffer)
         ("C-x C-f" . counsel-find-file)
         :map minibuffer-local-map
         ("C-r" . 'counsel-minibuffer-history)))

Yasnippet

(use-package yasnippet
  :defer t
  :straight (:build t)
  :init
  (yas-global-mode)
  :hook ((prog-mode . yas-minor-mode)
         (text-mode . yas-minor-mode)))
(use-package yasnippet-snippets
  :defer t
  :after yasnippet
  :straight (:build t))
(use-package yatemplate
  :defer t
  :after yasnippet
  :straight (:build t))
(use-package ivy-yasnippet
  :defer t
  :after (ivy yasnippet)
  :straight (:build t))
(phundrak/leader-key)
  :infix "i"
  :packages 'ivy-yasnippet
  "y" #'ivy-yasnippet

Applications

Docker

Docker is an awesome tool for reproducible development environments. Due to this, I absolutely need a mode for editing Dockerfiles.

(use-package dockerfile-mode
  :defer t
  :straight (:build t)
  :hook (dockerfile-mode . lsp-deferred)
  :init
  (put 'docker-image-name 'safe-local-variable #'stringp)
  :mode "Dockerfile\\'")

The docker package also provides interactivity with Docker and docker-compose from Emacs.

(use-package docker
  :defer t
  :straight (:build t))

Elfeed

Elfeed is a nice Atom and RSS reader for Emacs. The only thing I want to change for now is the default search filter: I want to see not only unread news but read news as well, a bit like my emails; and where the database is to be stored.

(use-package elfeed
  :defer t
  :straight (:build t)
  :config
  <<elfeed-open-youtube-with-mpv>>
  :custom
  ((elfeed-search-filter "@6-months-ago")
   (elfeed-db-directory  (expand-file-name ".elfeed-db"
                                           user-emacs-directory))))

<<elfeed-mpv>>I dont want YouTube videos to be open with my web browser when I invoke elfeed-show-visit, so Ill advise this function so I can modify the behavior of said function.

(defun my/elfeed-filter-youtube-videos (orig-fun &rest args)
  "Open with mpv the video leading to PATH"
  (let ((link (elfeed-entry-link elfeed-show-entry)))
    (when link
      (if (string-match-p ".*youtube\.com.*watch.*" link)
          ;; This is a YouTube video, open it with mpv
          (async-shell-command (format "mpv \"%s\"" link) (current-buffer) (current-buffer))
        (apply orig-fun args)))))

(advice-add 'elfeed-show-visit :around #'my/elfeed-filter-youtube-videos)

A future improvement to be made is to let the user chose the resolution of the video before it is launched. I may not always have the best internet connection, and viewing 4K videos on a 1080p display is not something very useful.

Elfeed-goodies is a package which enhances the Elfeed experience. Aside from running its setup command as soon as possible, I also set in this code block all my keybinds for Elfeed here.

(use-package elfeed-goodies
  :defer t
  :after elfeed
  :commands elfeed-goodies/setup
  :straight (:build t)
  :init
  (elfeed-goodies/setup)
  :general
  (phundrak/undefine
    :keymaps '(elfeed-show-mode-map elfeed-search-mode-map)
    :packages 'elfeed
    "DEL" nil
    "s"   nil)
  (phundrak/evil
    :keymaps 'elfeed-show-mode-map
    :packages 'elfeed
    <<general-keybindings-gen(table=elfeed-keybinds-show-mode)>>)
  (phundrak/evil
    :keymaps 'elfeed-search-mode-map
    :packages 'elfeed
    <<general-keybindings-gen(table=elfeed-keybinds-search-mode)>>)
  (phundrak/major-leader-key
    :keymaps 'elfeed-search-mode-map
    :packages 'elfeed
    <<general-keybindings-gen(table=elfeed-keybinds-search-mode-prefixed)>>))

Last but not least, my Elfeed configuration is stored in an org file thanks to elfeed-org.

(use-package elfeed-org
  :defer t
  :after elfeed
  :straight (:build t)
  :init
  (elfeed-org)
  :config
  (setq rmh-elfeed-org-files '("~/org/elfeed.org")))
Keybinds

First, here are the keybinds for Elfeeds elfeed-show-mode. They arent prefixed by SPC like most of my keybinds, a direct keypress will directly launch the function.

Key Function Comment
+ elfeed-show-tag
- elfeed-show-untag
« elfeed-show-prev
» elfeed-show-next
b elfeed-show-visit
C elfeed-kill-link-url-at-point
d elfeed-show-save-enclosure
l elfeed-show-next-link
o elfeed-goodies/show-ace-link
q elfeed-kill-buffer
S elfeed-show-new-live-search
u elfeed-show-tagunread
y elfeed-show-yank

Same thing, different mode, here are my keybinds for elfeed-search-mode.

Key Function Comment
« elfeed-search-first-entry
» elfeed-search-last-entry
b elfeed-search-browse-url
f filter
fc elfeed-search-clear-filter
fl elfeed-search-live-filter
fs elfeed-search-set-filter
u update
us elfeed-search-fetch
uS elfeed-search-update
uu elfeed-update
uU elfeed-search-updateforce
y elfeed-search-yank

I have some additional keybinds for elfeed-search-mode, but these one are prefixed with , (and M-m).

Key Function Comment
c elfeed-db-compact
t tag
tt elfeed-search-tag-all-unread
tu elfeed-search-untag-all-unread
tT elfeed-search-tag-all
tU elfeed-search-untag-all

Email

Basic configuration

As seen below, I use org-msg to compose my emails, which includes by default my signature. Therefore, there is no need for Emacs itself to know about it since I dont want it to include it a second time after org-msg already did.

(setq message-signature nil
      mail-signature    nil)
Mu4e

Mu4e is a very eye-pleasing email client for Emacs, built around mu and which works well with mbsync (found in Archs isync package). For me, the main advantage of mu4e is it has a modern interface for emailing, and quite straightforward. I tried a couple of other email clients for Emacs, and I even was for some time a Gnus user, but in the end, mu4e really works best for me. Below youll find my configuration for the mu4e package itself.

(use-package mu4e
  :after all-the-icons
  :straight (:build t :location site)
  :commands mu4e mu4e-compose-new
  :init
  (setq mu4e-completing-read-function 'completing-read
        mu4e-use-fancy-chars          t
        mu4e-view-show-images         t
        message-kill-buffer-on-exit   t
        mu4e-org-support              nil)
  (let ((dir "~/Downloads/mu4e"))
    (when (file-directory-p dir)
      (setq mu4e-attachment-dir dir)))
  (defmacro mu4e-view-mode--prepare ()
    `(lambda () (visual-line-mode 1)))
  :gfhook ('mu4e-view-mode-hook (mu4e-view-mode--prepare))
  :config
  (with-eval-after-load "mm-decode"
    (add-to-list 'mm-discouraged-alternatives "text/html")
    (add-to-list 'mm-discouraged-alternatives "text-richtext"))

  (add-hook 'mu4e-view-mode-hook (lambda () (setq truncate-lines nil)))
  (add-hook 'mu4e-headers-mode-hook (lambda () (setq truncate-lines t)))

  <<mu4e-keybindings-undef>>
  <<mu4e-keybindings-view>>
  <<mu4e-keybindings-view-no-prefix>>
  <<mu4e-keybindings-header>>
  <<mu4e-keybindings-header-no-leader>>
  <<mu4e-keybindings-message>>

  <<mu4e-mail-service>>
  <<mu4e-mail-on-machine>>
  <<mu4e-no-signature>>
  <<mu4e-bookmarks>>

  (when (fboundp 'imagemagick-register-types)
    (imagemagick-register-types))

  (add-to-list 'mu4e-view-actions '("PDF view" . mu4e-action-open-as-pdf)     t)

  (require 'gnus-dired)
  (setq gnus-dired-mail-mode 'mu4e-user-agent)

  (add-hook 'mu4e-compose-mode-hook (lambda () (use-hard-newlines t 'guess)))
  (add-hook 'mu4e-compose-mode-hook 'mml-secure-message-sign-pgpmime)

  ;; writeroom for plaintext emails
  (add-hook 'mu4e-view-mode-hook
            (lambda ()
              (unless (mu4e~message-use-html-p (mu4e-message-at-point) nil)
                (writeroom-mode))))

  (setq mu4e-change-filenames-when-moving t
        mu4e-update-interval              60
        mu4e-compose-format-flowed        t
        mu4e-view-show-addresses          t
        mu4e-sent-messages-behaviour      'sent
        mu4e-hide-index-messages          t
        mu4e-view-show-images             t                     ; try to show images
        mu4e-view-image-max-width         600
        message-send-mail-function        #'smtpmail-send-it    ; how to send an email
        smtpmail-stream-type              'starttls
        message-kill-buffer-on-exit       t                     ; close after sending
        mu4e-context-policy               'pick-first           ; start with first (default) context
        mu4e-compose-context-policy       'ask-if-none          ; compose with current context, or ask
        mu4e-completing-read-function     #'ivy-completing-read ; use ivy
        mu4e-confirm-quit                 t                     ; no need to ask
        mu4e-header-fields                '((:account    . 12)
                                            (:human-date . 12)
                                            (:flags      . 4)
                                            (:from       . 25)
                                            (:subject)))

  ;; set mail user agent
  (setq mail-user-agent 'mu4e-user-agent)

  <<mu4e-fancy-marks>>
  <<mu4e-vertical-split>>
  <<mu4e-headers-mode>>

  (defun mu4e-action-open-as-pdf (msg)
    "Export and open MSG as pdf."
    (let* ((date    (mu4e-message-field msg :date))
           (infile  (mu4e~write-body-to-html msg))
           (outfile (format-time-string "/tmp/%Y-%m-%d-%H-%M-%S.pdf" date)))
      (with-temp-buffer
        (shell-command
         (format "wkhtmltopdf %s %s" infile outfile) t))
      (find-file outfile))))

Quick sidenote: on ArchLinux, youll need to install either mu or mu-git from the AUR in order to use mu4e. I also have a .desktop file so I can open mu4e directly from my program picker. It uses the shell script emacsmail Ive written here.

[Desktop Entry]
Name=Mu4e
GenericName=Mu4e
Comment=Maildir Utils for Emacs
MimeType=x-scheme-handler/mailto;
Exec=/home/phundrak/.local/bin/emacsmail %U
Icon=emacs
Type=Application
Terminal=false
Categories=Network;Email;TextEditor
StartupWMClass=Gnus
Keywords=Text;Editor;
Basic configuration

First, lets inform Emacs how it can send emails, using which service and how. In my case, I use my own mail server.

(setq smtpmail-smtp-server       "mail.phundrak.com"
      smtpmail-smtp-service      587
      smtpmail-stream-type       'starttls
      message-send-mail-function 'smtpmail-send-it)

We also need to inform it on where my emails are stored on my machine, and how to retrieve them.

(setq mu4e-get-mail-command "mbsync -a"
      mu4e-root-maildir     "~/Mail"
      mu4e-trash-folder     "/Trash"
      mu4e-refile-folder    "/Archive"
      mu4e-sent-folder      "/Sent"
      mu4e-drafts-folder    "/Drafts")

In the same vein of this bit of configuration, I do not want mu4e to insert my mail signature, org-msg already does that.

(setq mu4e-compose-signature nil)
Bookmarks

In mu4e, the main focus isnt really mail directories such as your inbox, your sent messages and such, but instead you manipulate bookmarks which will show you emails depending on tags. This mean you can create some pretty customized bookmarks that go way beyound your simple inbox, outbox and all. Actually, four of my bookmarks have a couple of filtering:

  • anything in my inbox linked to my university
  • the emacs-doctor mailing list (French Emacs mailing list)
  • the conlang mailing lists
  • and my inbox for any mail not caught by any of these filters

And all of them will have the requirement not to display any trashed email. Actually, all of my bookmarks will have this requirement, except for the bookmark dedicated to them as well as my sent emails. Ill add these latter requirements later.

Here are the requirements for my university bookmark. The regex matches any email address which contains either up8.edu or univ-paris8, which can be found in email addresses from the University Paris 8 (my university).

(mapconcat (lambda (list)
             (let ((address (string-replace (regexp-quote "~") "" (car list))))
               (mapconcat (lambda (flag)
                            (concat flag ":" address))
                          '("list" "t" "f")
                          " OR ")))
           lists
           " OR ")
(let ((regex "/.*up8\\.edu|.*univ-paris8.*/"))
  <<mu4e-bookmarks-from-copy-to-gen>>)
f:/.*up8\.edu|.*univ-paris8.*/ OR c:/.*up8\.edu|.*univ-paris8.*/ OR t:/.*up8\.edu|.*univ-paris8.*/

Next I need an inbox dedicated to the association Im part of.

(let ((regex "/.*supran\\.fr/"))
  <<mu4e-bookmarks-from-copy-to-gen>>)
f:/.*supran\.fr/ OR c:/.*supran\.fr/ OR t:/.*supran\.fr/

As for the Emacs-doctor list, I need to match both the current, modern mailing list address but also its old address. The same applies for the emacs-devel mailing list as well as Github emails related to my package eshell-info-banner.el (see here). Here are the addresses to match:

  • /ateliers.*emacs.*/
  • /emacs-.*@gnu.org/
  • /.*eshell-info-banner.*/
"<<mu4e-bookmarks-mailing-lists(lists=mu4e-emacs-mailing-lists)>>"
list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/

Another bookmark I wish to have is one dedicated to emails related to issues and PRs from Github.

  • /.*\\.github\\.com/
"<<mu4e-bookmarks-mailing-lists(lists=mu4e-github-mailing-lists)>>"
list:/.*\.github\.com/ OR t:/.*\.github\.com/ OR f:/.*\.github\.com/

When it comes to the conlang mailing list, lets not match anything from or to them. Ill also include the auxlang mailing list Im not subscribed anymore, but itll keep my inbox clean.

  • /.*LANG@LISTSERV.BROWN.EDU/
"<<mu4e-bookmarks-mailing-lists(lists=mu4e-conlanging-mailing-lists)>>"
list:/.*LANG@LISTSERV.BROWN.EDU/ OR t:/.*LANG@LISTSERV.BROWN.EDU/ OR f:/.*LANG@LISTSERV.BROWN.EDU/

As I said earlier, something that will often come back in my bookmarks is the emails must not be trashed to appear. I want also to display junk emails, so I end up with the following rule:

(mapconcat #'identity
           `("NOT flag:trashed"
             ,(format "(%s)" (mapconcat (lambda (maildir) (concat "maildir:" maildir))
                                        '("/Inbox" "/Junk")
                                        " OR ")))
            " AND ")
NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk)

And for the last string-generating code, lets describe my main inbox:

(mapconcat #'identity
           (cons "<<mu4e-bookmarks-default-filter()>>"
                 `(,(format "(%s)"
                            <<mu4e-bookmarks-filter-conlang-list>>)
                   ,(format "(%s)" "<<mu4e-bookmarks-filter-asso()>>")
                   ,(format "(%s)"
                            <<mu4e-bookmarks-filter-emacs-list>>)
                   ,(format "(%s)"
                            <<mu4e-bookmarks-filter-github-list>>)
                   ,(format "(%s)"
                            <<mu4e-bookmarks-filter-uni>>)))
           " AND NOT ")
NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (list:/.*LANG@LISTSERV.BROWN.EDU/ OR t:/.*LANG@LISTSERV.BROWN.EDU/ OR f:/.*LANG@LISTSERV.BROWN.EDU/) AND NOT (f:/.*supran.fr/ OR c:/.*supran.fr/ OR t:/.*supran.fr/) AND NOT (list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/) AND NOT (list:/.*\.github\.com/ OR t:/.*\.github\.com/ OR f:/.*\.github\.com/) AND NOT (f:/.*up8\.edu|.*univ-paris8.*/ OR c:/.*up8\.edu|.*univ-paris8.*/ OR t:/.*up8\.edu|.*univ-paris8.*/)

We can finally define our bookmarks! The code reads as follows:

(setq mu4e-bookmarks
      `((:name "Inbox"
         :key ?i
         :query ,(format "%s"
                         <<mu4e-bookmarks-inbox-filters>>))
        (:name "University"
         :key ?u
         :query ,(format "%s AND %s"
                         "<<mu4e-bookmarks-default-filter()>>"
                         "<<mu4e-bookmarks-filter-uni()>>"))
        (:name "Supran"
         :key ?S
         :query ,(format "%s AND %s"
                         "<<mu4e-bookmarks-default-filter()>>"
                         "<<mu4e-bookmarks-filter-asso()>>"))
        (:name "Emacs"
         :key ?e
         :query ,(format "%s AND %s"
                         "<<mu4e-bookmarks-default-filter()>>"
                         <<mu4e-bookmarks-filter-emacs-list>>))
        (:name "Github"
         :key ?g
         :query ,(format "%s AND (%s)"
                         "<<mu4e-bookmarks-default-filter()>>"
                         "<<mu4e-bookmarks-filter-github-list()>>"))
        (:name "Linguistics"
         :key ?l
         :query ,(format "(%s) AND (%s)"
                     "<<mu4e-bookmarks-default-filter()>>"
                     <<mu4e-bookmarks-filter-conlang-list>>))
        (:name "Sent"       :key ?s :query "maildir:/Sent")
        (:name "All Unread" :key ?U :query "flag:unread AND NOT flag:trashed")
        (:name "Today"      :key ?t :query "date:today..now AND NOT flag:trashed")
        (:name "This Week"  :key ?w :query "date:7d..now    AND NOT flag:trashed")
        (:name "This Month" :key ?m :query "date:1m..now    AND NOT flag:trashed")
        (:name "This Year"  :key ?y :query "date:1y..now    AND NOT flag:trashed")))
Dealing with spammers

Im sure you have received at least one email recently from a sketchy email address asking you something that might be completely unrelated to what you do, or at least somewhat related. Fortunately, we have a hero! Now, let me write a function that will insert their pre-written text at point so I dont have to go back to their Twitter thread each time I want to shut spammers up.

(defun reply-to-bill ()
  (interactive)
  (insert "Please forward this email to bill@noprocurement.com,
and delete my email, as Ill be changing jobs soon, and this
email address will no longer be active.

Bill Whiskoney is a senior partner at Nordic Procurement
Services, and he handles our budget and will help you further or
introduce you to someone who can."))

If you want the full story, make sure to read the whole thread, I guarantee it, its worth your time! And in case the Twitter thread disappear in the future, here is a backup.

Getting Fancy

Im not a huge fan of mu4es default icons marking my emails, so Ill redefine them as follows. Be aware the name of these icons are from faicon in the package all-the-icons.

Mark Flag Icon
draft D pencil
flagged F flag
new N rss
passed P check
replied R reply
seen S eye
unread u eye-slash
trashed T trash
attach a paperclip
encrypted x lock
signed s certificate
(mapconcat (lambda (line)
             (let ((mark (car line))
                   (flag (cadr line))
                   (icon (caddr line)))
               (format "mu4e-headers-%s-mark `(\"%s\" . ,(all-the-icons-faicon \"%s\" :height 0.8))"
                       mark
                       flag
                       icon)))
           table
           "\n")
mu4e-headers-draft-mark `("D" . ,(all-the-icons-faicon "pencil" :height 0.8))
mu4e-headers-flagged-mark `("F" . ,(all-the-icons-faicon "flag" :height 0.8))
mu4e-headers-new-mark `("N" . ,(all-the-icons-faicon "rss" :height 0.8))
mu4e-headers-passed-mark `("P" . ,(all-the-icons-faicon "check" :height 0.8))
mu4e-headers-replied-mark `("R" . ,(all-the-icons-faicon "reply" :height 0.8))
mu4e-headers-seen-mark `("S" . ,(all-the-icons-faicon "eye" :height 0.8))
mu4e-headers-unread-mark `("u" . ,(all-the-icons-faicon "eye-slash" :height 0.8))
mu4e-headers-trashed-mark `("T" . ,(all-the-icons-faicon "trash" :height 0.8))
mu4e-headers-attach-mark `("a" . ,(all-the-icons-faicon "paperclip" :height 0.8))
mu4e-headers-encrypted-mark `("x" . ,(all-the-icons-faicon "lock" :height 0.8))
mu4e-headers-signed-mark `("s" . ,(all-the-icons-faicon "certificate" :height 0.8))

Lets enable them and set them:

(setq mu4e-use-fancy-chars t
      <<mu4e-fancy-marks-gen()>>)
(defun my/set-mu4e-headers-width ()
  (let ((width    (window-body-width))
        (threshold (+ 120 80)))
    (setq mu4e-split-view (if (> width threshold)
                              'vertical
                            'horizontal))
    (message "Window width: %d\tthreshold: %d\nSplit: %S"
             width
             threshold
             mu4e-split-view)))

(setq mu4e-headers-visible-columns 120
      mu4e-headers-visible-lines   15)
(add-hook 'mu4e-headers-mode-hook #'my/set-mu4e-headers-width)
Headers mode
(add-hook 'mu4e-headers-mode-hook (lambda () (visual-line-mode -1)))
(add-hook 'mu4e-headers-mode-hook (lambda () (toggle-truncate-lines -1)))
Keybindings

By default, Evil has some pretty annoying keybindings for users of the bépo layout: hjkl becomes ctsr for us. Lets undefine some of these:

(phundrak/undefine
  :keymaps 'mu4e-view-mode-map
  :packages 'mu4e
  "S"   nil
  "r"   nil
  "c"   nil
  "gu"  nil)

(phundrak/undefine
  :keymaps '(mu4e-view-mode-map mu4e-headers-mode-map)
  :packages 'mu4e
  "s"   nil)

Now, lets define some keybindings for mu4es view mode, that is when we are viewing an email. All these keybindings will reside between the major-mode specific leader key , and most of these keybindings can be described with a simple function:

Keybinding Function Description
& mu4e-view-pipe
. mu4e-headers-split-adjust-width/body mu4e-headers width
a nil attachments
a& mu4e-view-pipe-attachment
aa mu4e-view-attachment-action
ao mu4e-view-open-attachment
aO mu4e-view-open-attachment-with
c nil compose
cc mu4e-compose-new
ce mu4e-compose-edit
cf mu4e-compose-forward
cr mu4e-compose-reply
cR mu4e-compose-resend
l mu4e-show-log
m nil mark
md mu4e-view-mark-for-trash
mD mu4e-view-mark-for-delete
mm mu4e-view-mark-for-move
mr mu4e-view-mark-for-refile
mR mu4e-view-mark-for-read
mu mu4e-view-mark-for-unread
mU mu4e-view-mark-for-unmark
t mu4e-view-mark-thread mark thread
T nil toggle
Tc mu4e-view-toggle-hide-cited
Th mu4e-view-toggle-html
n mu4e-view-headers-next
N mu4e-view-headers-next-unread
p mu4e-view-headers-prev
P mu4e-view-headers-prev-unread
u nil url
uf mu4e-view-go-to-url
uF mu4e-view-fetch-url
(phundrak/major-leader-key
  :keymaps 'mu4e-view-mode-map
  :packages 'mu4e
  <<general-keybindings-gen(table=mu4e-keybindings-view-tbl)>>)

Two other keybinds are added without a prefix, just for the sake of convenience.

(phundrak/evil
  :keymaps 'mu4e-view-mode-map
  :packages 'mu4e
  "«" #'mu4e-view-headers-prev
  "»" #'mu4e-view-headers-next)

Ill also declare two keybinds for mu4es headers mode.

(phundrak/major-leader-key
  :keymaps 'mu4e-headers-mode-map
  :packages 'mu4e
  "t" '(mu4e-view-mark-thread :which-key "mark thread")
  "s" 'swiper)

I will also redefine without a leader key ctsr in order to be able to move freely (remember, bépo layout for me).

Key Function Comment
c evil-backward-char
t evil-next-line
s evil-previous-line
r evil-forward-char
(phundrak/evil
  :keymaps 'mu4e-headers-mode-map
  :packages 'mu4e
  <<general-keybindings-gen(table=mu4e-keybindings-header-no-leader-table)>>)

Finally, lets declare a couple of keybindings for when we are composing a message. This time, all my keybindings are prefixed with the major-mode leader and call a simple function.

Key Function Description
, message-send-and-exit
c message-send-and-exit
a message-kill-buffer
k message-kill-buffer
s message-dont-send
f mml-attach-file
(phundrak/major-leader-key
  :keymaps 'message-mode-map
  :packages 'mu4e
  <<general-keybindings-gen(table=mu4e-keybindings-message-tbl)>>)
Composing messages

Org mime is cool and all, you can write some org-mode and then export it so you can send an HTML email. BUT, have you considered skipping the export part and write your emails directly in org-mode?

(use-package org-msg
  :after (org mu4e)
  :straight (:build t)
  :hook ((mu4e-compose-pre . org-msg-mode))
  :custom-face
  (mu4e-replied-face ((t (:weight normal :foreground "#b48ead"))))
  :config
  (defun my/org-msg-signature-convert (orig-fun &rest args)
    "Tweak my signature when replying as plain/text only."
    (let ((res (apply orig-fun args)))
      (when (equal (cadr args) '(text))
        (setf (alist-get 'signature res)
              (replace-regexp-in-string "\n+" "\n" org-msg-signature)))
      res))
  (advice-add 'org-msg-composition-parameters
              :around 'my/org-msg-signature-convert)
  (add-hook 'mu4e-headers-mode (lambda () (toggle-truncate-lines -1)))
  (setq org-msg-startup              "inlineimages"
        org-msg-default-alternatives '((new           . (text html))
                                       (reply-to-html . (text html))
                                       (reply-to-text . (text)))
        org-msg-convert-citation     t
        org-msg-greeting-name-limit  3
        org-msg-signature            (format "\n--\n#+begin_signature\n%s\n#+end_signature"
                                             (string-trim
                                              (replace-regexp-in-string
                                               (regexp-quote "\n")
                                               "\n\n"
                                               (with-temp-buffer
                                                 (insert-file-contents mail-signature-file)
                                                 (buffer-string))))))
  :general
  (phundrak/major-leader-key
    :keymaps 'org-msg-edit-mode-map
    :packages 'org-msg
    <<general-keybindings-gen(table=org-msg-edit-mode-keybinds)>>))

The keybinds are relatively simple org-msg-edit-mode:

Key Function Description
, message-send-and-exit
c message-send-and-exit
a message-kill-buffer
k message-kill-buffer
s message-dont-send
f org-msg-attach
Email alerts

There is also a package for mu4e which generates desktop notifications when new emails are received. By default, I want to be notified by all messages in my inbox and junk folder. Also, Ill use Emacs default notification system, and Ill activate the modeline notification.

(use-package mu4e-alert
  :straight (:build t)
  :after mu4e
  :defer t
  :init
  (add-hook 'after-init-hook #'mu4e-alert-enable-notifications)
  (add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)
  (mu4e-alert-set-default-style 'notifications)
  :config
  (setq mu4e-alert-interesting-mail-query "flag:unread"))

EMMS and Media

EMMS, also known as the Emacs MultiMedia System, allows the user to interact through Emacs with multimedia elements such as music and videos. My main use for it will be for music with MPD (see its configuration here).

(use-package emms
  :defer t
  :after all-the-icons
  :straight (:build t)
  :init
  (require 'emms-setup)
  (require 'emms-mark)
  (emms-all)
  (add-to-list 'emms-info-functions 'emms-info-mpd)
  (add-to-list 'emms-player-list    'emms-player-mpd)
  (emms-player-mpd-sync-from-mpd)
  (emms-player-mpd-connect)

  <<emms-fd-name>>
  <<emms-fd-function>>
  <<emms-search-set-variable>>

  <<emms-media-hydra>>
  (defun emms-player-toggle-pause ()
    (interactive)
    (shell-command-and-echo "mpc toggle"))
  :custom
  ((emms-source-file-default-directory (expand-file-name "~/Music"))
   (emms-player-mpd-server-name "localhost")
   (emms-player-mpd-server-port "6600")
   (emms-player-mpd-music-directory (expand-file-name "~/Music"))
   (emms-browser-thumbnail-small-size 64)
   (emms-browser-thumbnail-medium-size 128)
   (emms-browser-covers #'emms-browser-cache-thumbnail-async)
   (emms-playlist-default-major-mode 'emms-mark-mode))
  :general
  (phundrak/undefine
    :keymaps 'emms-browser-mode-map
    :packages 'emms
    "s"   nil
    "r"   nil)
  (phundrak/evil
    :keymaps 'emms-browser-mode-map
    :packages 'emms
    "a"  #'emms-browser-add-tracks
    "A"  #'emms-browser-add-tracks-and-play
    "b"  '(:ignore t :which-key "browse by")
    "bA" #'emms-browse-by-album
    "ba" #'emms-browse-by-artist
    "bg" #'emms-browse-by-genre
    "bs" #'emms-smart-browse
    "by" #'emms-browse-by-year
    "c"  #'emms-browser-clear-playlist
    "S"  '(:ignore t :which-key "search")
    "SA" '(emms-browser-search-by-album  :which-key "by album")
    "Sa" '(emms-browser-search-by-artist :which-key "by artist")
    "Ss" '(emms-browser-search-by-names  :which-key "by name")
    "St" '(emms-browser-search-by-names  :which-key "by title")
    "q"  #'kill-this-buffer)
  (phundrak/evil
    :keymaps 'emms-playlist-mode-map
    :packages 'emms
    "d" #'emms-playlist-mode-kill-track
    "p" #'emms-playlist-mode-play-smart
    "q" #'kill-this-buffer)
  (phundrak/leader-key
    :infix "m"
    :packages 'emms
    ""   '(:ignore t :which-key "media")
    "."  #'hydra-media/body
    "«"  #'emms-player-mpd-previous
    "»"  #'emms-player-mpd-next
    "c"  #'emms-player-mpd-clear
    "e"  '(:ignore t :which-key "emms")
    "eb" #'emms-browser
    "ep" #'emms-playlist-mode-go
    "es" #'emms-player-mpd-show
    "p"  '((lambda ()
             (interactive)
             (shell-command-and-echo "mpc toggle"))
           :which-key "mpc toggle")
    "s"  #'emms-stop
    "u"  '(:ignore t :which-key "update")
    "um" #'emms-player-mpd-update-all
    "uc" #'emms-cache-set-from-mpd-all))
Finding files from EMMS

EMMS has two default ways of finding files: either a built-in function relatively slow but portable, or with find which is arguably faster but less portable. Honestly, I dont care about portability, Ill always use this Emacs config on Linux, but I dont want to use find either since there is something even faster: fd.

First well declare a variable that can hold the path to the fd executable:

(defvar emms-source-file-fd (executable-find "fd"))

Then, we need to declare a new function that will use fd to find files. The function, as specified by the documentation of emms-source-file-directory-tree-function, receives two arguments dir and regex. We can work with that!

(defun emms-source-file-directory-tree-fd (dir regex)
  "Return a list of all files under DIR that match REGEX.
This function uses the external fd utility. The name for fd may
be supplied using `emms-source-file-fd'."
  (with-temp-buffer
    (call-process emms-source-file-fd
                  nil t nil
                  "-L" ; follow symlinks
                  regex
                  "-t f"
                  (expand-file-name dir))
    (delete ""
            (split-string (buffer-substring (point-min)
                                          (point-max))
                          "\n"))))

We can finally set this function as our search function.

(setq emms-source-file-directory-tree-function #'emms-source-file-directory-tree-fd)
Keybinds

I also want to create a small hydra for manipulating MPD:

(defun shell-command-and-echo (command &optional echo prefix)
  "Run COMMAND and display the result of ECHO prefixed by PREFIX.

Run COMMAND as a shell command.

If ECHO is non nil, display the result of its execution as a
shell command to the minibuffer through `MESSAGE'.

If PREFIX is non nil, it will prefix the output of ECHO in the
minibuffer, both separated by a single space."
  (progn
    (with-temp-buffer
      (shell-command command
                     (current-buffer)
                     (current-buffer))
      (when echo
        (message "%s%s"
                 (if prefix
                     (concat prefix " ")
                   "")
                 (string-trim
                  (shell-command-to-string "mpc volume")))))))

(defhydra hydra-media (:hint nil)
  "
     %s(all-the-icons-material \"volume_up\" :height 1.0 :v-adjust -0.2)
     [_s_]
« [_c_] _p_ [_r_] » [_S_] %s(all-the-icons-material \"stop\")
     [_t_]
     %s(all-the-icons-material \"volume_down\" :height 1.0 :v-adjust -0.2)
"
  ("c" emms-player-mpd-previous)
  ("r" emms-player-mpd-next)
  ("t" (shell-command-and-echo "mpc volume -2" "mpc volume" "mpc"))
  ("s" (shell-command-and-echo "mpc volume +2" "mpc volume" "mpc"))
  ("p" (shell-command-and-echo "mpc toggle"))
  ("S" emms-player-mpd-stop)
  ("q" nil :exit t))

Nov

Nov is a major-mode for reading EPUB files within Emacs. Since I have it, I dont need any other Epub reader on my computer! Plus this one is customizable and programmable, why would I use any other EPUB reader?

(use-package nov
  :straight (:build t)
  :defer t
  :mode ("\\.epub\\'" . nov-mode)
  :general
  (phundrak/evil
    :keymaps 'nov-mode-map
    :packages 'nov
    "c"   #'nov-previous-document
    "t"   #'nov-scroll-up
    "C-d" #'nov-scroll-up
    "s"   #'nov-scroll-down
    "C-u" #'nov-scroll-down
    "r"   #'nov-next-document
    "gm"  #'nov-display-metadata
    "gn"  #'nov-next-document
    "gp"  #'nov-previous-document
    "gr"  #'nov-render-document
    "gt"  #'nov-goto-toc
    "gv"  #'nov-view-source
    "gV"  #'nov-view-content-source)
  :config
  (setq nov-text-width 95))

PDF Tools

pdf-tools enables PDF support for Emacs, much better than its built-in support with DocView. Aside from the classical settings such as keybinds, I also enable the midnight colors by default; think of it as an equivalent of Zathuras recolor feature which kind of enables a dark mode for PDFs.

(use-package pdf-tools
  :defer t
  :magic ("%PDF" . pdf-view-mode)
  :straight (:build t)
  :mode (("\\.pdf\\'" . pdf-view-mode))
  :hook (pdf-tools-enabled . pdf-view-midnight-minor-mode)
  :general
  (phundrak/evil
    :keymaps 'pdf-view-mode-map
    :packages 'pdf-tools
    "y"   #'pdf-view-kill-ring-save
    "t"   #'evil-collection-pdf-view-next-line-or-next-page
    "s"   #'evil-collection-pdf-view-previous-line-or-previous-page)
  (phundrak/major-leader-key
    :keymaps 'pdf-view-mode-map
    :packages 'pdf-tools
    "a"  '(:ignore t :which-key "annotations")
    "aD" #'pdf-annot-delete
    "at" #'pdf-annot-attachment-dired
    "ah" #'pdf-annot-add-highlight-markup-annotation
    "al" #'pdf-annot-list-annotations
    "am" #'pdf-annot-markup-annotation
    "ao" #'pdf-annot-add-strikeout-markup-annotation
    "as" #'pdf-annot-add-squiggly-markup-annotation
    "at" #'pdf-annot-add-text-annotation
    "au" #'pdf-annot-add-underline-markup-annotation

    "f"  '(:ignore t :which-key "fit")
    "fw" #'pdf-view-fit-width-to-window
    "fh" #'pdf-view-fit-height-to-window
    "fp" #'pdf-view-fit-page-to-window

    "s"  '(:ignore t :which-key "slice/search")
    "sb" #'pdf-view-set-slice-from-bounding-box
    "sm" #'pdf-view-set-slice-using-mouse
    "sr" #'pdf-view-reset-slice
    "ss" #'pdf-occur

    "o"  'pdf-outline
    "m"  'pdf-view-midnight-minor-mode)
  :config
  (with-eval-after-load 'pdf-view
    (setq pdf-view-midnight-colors '("#d8dee9" . "#2e3440"))))

One thing pdf-tools doesnt handle is restoring the PDF to the last point it was visited — in other words, open the PDF where I last left it.

(use-package pdf-view-restore
  :after pdf-tools
  :defer t
  :straight (:build t)
  :hook (pdf-view-mode . pdf-view-restore-mode)
  :config
  (setq pdf-view-restore-filename (expand-file-name ".tmp/pdf-view-restore"
                                                    user-emacs-directory)))

Project Management

Magit

Magit is an awesome wrapper around Git for Emacs! Very often, I go from disliking to really hating Git GUI clients because they often obfuscate which Git commands are used to make things happen. Such a thing doesnt happen with Magit, its pretty transparent but it still provides some awesome features and visualizations of what you are doing and what Git is doing! In short, I absolutely love it!

(use-package magit
  :straight (:build t)
  :defer t
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
  :config
  (setq magit-clone-default-directory "~/fromGIT/")
  :general
  (:keymaps '(git-rebase-mode-map)
   :packages 'magit
   "C-t" #'evil-next-line
   "C-s" #'evil-previous-line)
  (phundrak/major-leader-key
    :keymaps 'git-rebase-mode-map
    :packages 'magit
    "," #'with-editor-finish
    "k" #'with-editor-cancel
    "a" #'with-editor-cancel)
  (phundrak/leader-key
    :infix   "g"
    :packages 'magit
    ""   '(:ignore t :wk "git")
    "b"  #'magit-blame
    "c"  #'magit-clone
    "d"  #'magit-dispatch
    "i"  #'magit-init
    "s"  #'magit-status
    "y"  #'my/yadm
    "S"  #'magit-stage-file
    "U"  #'magit-unstage-file
    "f"  '(:ignore t :wk "file")
    "fd" #'magit-diff
    "fc" #'magit-file-checkout
    "fl" #'magit-file-dispatch
    "fF" #'magit-find-file))

Alphapapa also created an awesome package for Magit: magit-todos which display in the Magit buffer a list of TODOs found in the current project to remind you of what to do next.

But first, lets setup our todo keywords with hl-todo. A good few todo keywords are already defined in the hl-todo-keyword-faces variable. Why not use them? hl-todo-mode enables fontlock highlight of these keywords in a buffer. Lets enable this mode globally.

(use-package hl-todo
  :defer t
  :straight (:build t)
  :init (global-hl-todo-mode 1)
  :general
  (phundrak/leader-key
    :packages '(hl-todo)
    :infix "c"
    ""  '(:ignore t :which-key "todos")
    "n" #'hl-todo-next
    "p" #'hl-todo-previous))

We can now configure properly magit-todos.

(use-package magit-todos
  :straight (:build t)
  :after (magit hl-todo)
  :config
  (setq magit-todos-ignore-case t))

Finally, it is also possible to use Gitflows framework with Magit with magit-gitflow:

(use-package magit-gitflow
  :defer t
  :after magit
  :straight (magit-gitflow :build t
                           :type git
                           :host github
                           :repo "jtatarik/magit-gitflow")
  :hook (magit-mode . turn-on-magit-gitflow))
Forge

NOTE: Make sure to configure a GitHub token before using this package!

(use-package forge
  :after magit
  :straight (:build t))
Projectile
(use-package ripgrep
  :straight (:build t)
  :defer t)
(use-package projectile
  :straight (:build t)
  :diminish projectile-mode
  :config (projectile-mode)
  :custom ((projectile-completion-system 'ivy))
  :bind-keymap
  ("C-c p" . projectile-command-map)
  :init
  (setq projectile-switch-project-action #'projectile-dired)
  :config
  (add-to-list 'projectile-ignored-projects "~/"))
(use-package counsel-projectile
  :straight (:build t)
  :after (counsel projectile)
  :config (counsel-projectile-mode))
Recentf

The built-in package recentf keeps track of recently opened files. But by default, it only follows the twenty most recent files, that not nearly enough for me, so I raise it to two hundred. I also dont want recentf to follow the Elfeed database, so I add it to the list of excluded files.

(use-package recentf
  :straight (:build t :type built-in)
  :custom ((recentf-max-saved-items 200))
  :init
  (add-to-list 'recentf-exclude (rx (* any)
                                    "elfeed-db"
                                    (* any))))

Screenshot

(use-package screenshot
  :defer t
  :straight (screenshot :build t
                        :type git
                        :host github
                        :repo "tecosaur/screenshot")
  :config (load-file (locate-library "screenshot.el"))
  :general
  (phundrak/leader-key
    :infix "a"
    :packages '(screenshot)
    "S" #'screenshot))

Shells

Shell-pop

Shell-pop allows the user to easily call for a new shell in a pop-up buffer.

(use-package shell-pop
  :defer t
  :straight (:build t)
  :custom
  (shell-pop-default-directory "/home/phundrak")
  (shell-pop-shell-type (quote ("eshell" "*eshell*" (lambda () (eshell shell-pop-term-shell)))))
  (shell-pop-window-size 30)
  (shell-pop-full-span nil)
  (shell-pop-window-position "bottom")
  (shell-pop-autocd-to-working-dir t)
  (shell-pop-restore-window-configuration t)
  (shell-pop-cleanup-buffer-at-process-exit t))
VTerm
(use-package vterm
  :defer t
  :straight t
  :config
  (setq vterm-shell "/usr/bin/fish"))

XWidgets Webkit Browser

I used to use the xwidgets webkit browser in order to view or preview HTML files from Emacs, but it seems the Cairo background transparency patch breaks it. So while this isnt patched, I will disable Xwidgets in my Emacs build, and these keybinds will not be tangled.

(phundrak/evil
 :keymaps 'xwidget-webkit-mode-map
 "<mouse-4>" #'xwidget-webkit-scroll-down-line
 "<mouse-5>" #'xwidget-webkit-scroll-up-line
 "c"         #'xwidget-webkit-scroll-backward
 "t"         #'xwidget-webkit-scroll-up-line
 "s"         #'xwidget-webkit-scroll-down-line
 "r"         #'xwidget-webkit-scroll-forward
 "h"         #'xwidget-webkit-goto-history
 "C"         #'xwidget-webkit-back
 "R"         #'xwidget-webkit-forward
 "C-r"       #'xwidget-webkit-reload
 "j"         nil
 "k"         nil
 "l"         nil
 "H"         nil
 "L"         nil
 "C-d"       #'xwidget-webkit-scroll-up
 "C-u"       #'xwidget-webkit-scroll-down)

Wttr.in

(use-package wttrin
  :defer t
  :straight (wttrin :build t
                    :local-repo "~/fromGIT/emacs-packages/emacs-wttrin"
                    :type git)
                    ;; :host github
                    ;; :repo "Phundrak/emacs-wttrin"
  :config
  (setq wttrin-default-cities '("Aubervilliers" "Paris" "Lyon" "Nonières" "Saint Agrève")
        wttrin-use-metric t))

Editing

First, Ill define some keybindings for easily inserting pairs when editing text.

(general-define-key
 :states 'visual
 "M-["  #'insert-pair
 "M-{"  #'insert-pair
 "M-<"  #'insert-pair
 "M-'"  #'insert-pair
 "M-`"  #'insert-pair
 "M-\"" #'insert-pair)

Atomic Chrome

Why write in your browser when you could write with Emacs? Despite its name, this package isnt only geared towards Chrome/Chromium-based browsers but also towards Firefox since its 2.0 version. I find it a bit unfortunate Chromes name stuck in the packages name though.

(use-package atomic-chrome
  :straight (:build t)
  :init
  (atomic-chrome-start-server))

Editorconfig

(use-package editorconfig
  :defer t
  :straight (:build t)
  :diminish editorconfig-mode
  :config
  (editorconfig-mode t))

Evil Nerd Commenter

Emacs default commenting system is nice, but I dont find it smart enough for me.

(use-package evil-nerd-commenter
  :after evil
  :straight (:build t))

Iedit

Iedit is a powerful text editing tool that can be used to refactor code through the edition of multiple regions at once, be it in a region or in a whole buffer.

(use-package iedit
  :defer t
  :straight (:build t)
  :general
  (phundrak/leader-key
    :infix "r"
    :packages 'iedit
    "" '(:ignore t :which-key "refactor")
    "i" #'iedit-mode))

Since Im using evil, Ill also use a compatibility package that adds states for iedit.

(use-package evil-iedit-state
  :after iedit
  :defer t
  :straight (:build t)
  :commands (evil-iedit-state evil-iedit-state/iedit-mode)
  :init
  (setq iedit-curent-symbol-default     t
        iedit-only-at-symbol-boundaries t
        iedit-toggle-key-default        nil))

Parinfer

Dont let the name of the package fool you! parinfer-rust-mode is not a parinfer mode for rust-mode, but a mode for parinfer-rust. parinfer was a project for handling parenthesis and other double markers in a much more intuitive way when writing Lisp code. However, it is now out of date (last commit was on January 2nd, 2019) and the repository has since been archived. New implementations then appeared, one of them is parinfer-rust, obviously written in Rust, around which parinfer-rust-mode is built. Enabling parinfer-rust-mode should also automatically disable smartparens-mode in order to avoid conflicting behavior.

(use-package parinfer-rust-mode
  :defer t
  :straight (:build t)
  :diminish parinfer-rust-mode
  :hook emacs-lisp-mode common-lisp-mode scheme-mode
  :init
  (setq parinfer-rust-auto-download     t
        parinfer-rust-library-directory (concat user-emacs-directory
                                                "parinfer-rust/"))
  (add-hook 'parinfer-rust-mode-hook
            (lambda () (smartparens-mode -1)))
  :general
  (phundrak/major-leader-key
    :keymaps 'parinfer-rust-mode-map
    "m" #'parinfer-rust-switch-mode
    "M" #'parinfer-rust-toggle-disable))

Smartparens

smartparens is a package similar to parinfer, but while the latter is more specialized for Lisp dialects, smartparens works better with other programming languages that still uses parenthesis, but not as much as Lisp dialects; think for example C, C++, Rust, Javascript, and so on.

(use-package smartparens
  :defer t
  :straight (:build t)
  :hook (prog-mode . smartparens-mode))

string-edit

string-edit is a cool package that allows the user to write naturally a string and get it automatically escaped for you. No more manually escaping your strings!

(use-package string-edit
  :defer t
  :straight (:build t))

Writeroom

On the other hand, writeroom allows the user to enter a distraction-free mode of Emacs, and I like that! But the default width is a bit too small for me, and I prefer not to go fullscren.

(use-package writeroom-mode
  :defer t
  :straight (:build t)
  :config
  (setq writeroom-width             100
        writeroom-fullscreen-effect nil
        writeroom-maximize-window   nil
        writeroom-mode-line         t))

Emacs built-ins

Dired

Dired is Emacs built-in file manager. Its really great, and replaces any graphical file manager for me most of the time because:

  • I am not limited to x tabs or panes
  • All actions can be done with keybindings
  • I get a consistent behavior between Dired and Emacs, since its the same thing.

However, the only thing I lack still is thumbnails. In any case, I need still to make some tweaks in order to make it usable for me.

(use-package dired
  :straight (:type built-in)
  :defer t
  :hook (dired-mode . turn-on-gnus-dired-mode)
  :general
  (phundrak/evil
    :keymaps 'dired-mode-map
    :packages 'dired
    "(" #'dired-hide-details-mode
    "n" #'evil-next-line
    "p" #'evil-previous-line)
  :config
  (setq dired-dwim-target       t
        dired-recursive-copies  t
        dired-recursive-deletes t
        dired-listing-switches  "-ahl --group-directories-first"))

Note that I am activating by default gnus-dired-mode. This is just for the sake of convenience, since Im not penalized with activating this mode when it is not needed, but I dont have to manually activate it each time I need it.

Dired-x stands for “dired extra” which provides a couple more features that are for some reason not included in dired yet.

(use-package dired-x
  :straight (:type built-in)
  :after dired
  :commands (dired-jump dired-jump-other-window dired-omit-mode)
  :general
  (phundrak/evil
    :keymaps 'dired-mode-map
    :packages 'dired-x
    "«" #'dired-omit-mode))

dired-du provides the user with the option to be able to see the size of directories as they are, rather than just the size of their inode. However, I will not enable it by default as it can take some time to get the actual size of a directory.

(use-package dired-du
  :after dired
  :straight (:build t)
  :general
  (phundrak/evil
   :keymaps 'dired-mode-map
   :packages 'dired-du
   "»" #'dired-du-mode))

This package on the other hand provides Git information to the user in the current dired buffer in a similar way to how Github and Gitea display the latest commit message and its age about a file in the file tree. And by default, I want dired-git-info to hide file details.

(use-package dired-git-info
  :after (dired)
  :straight (:build t)
  :hook (dired-after-reading . dired-git-info-auto-enable)
  :general
  (phundrak/evil
    :keymaps 'dired-mode-map
    :packages 'dired-git-info
    ")" #'dired-git-info-mode)
  :config
  (setq dgi-auto-hide-details-p t))

Diredfl makes dired colorful, and much more readable in my opinion.

(use-package diredfl
  :after (dired all-the-icons)
  :straight (:build t)
  :init
  (diredfl-global-mode 1))

And lets add some fancy icons in dired!

(use-package all-the-icons-dired
  :after (all-the-icons)
  :straight (:build t)
  :defer t
  :hook (dired-mode . all-the-icons-dired-mode))
(use-package image-dired+
  :after (image-dired)
  :straight (:build t)
  :init (image-diredx-adjust-mode 1))

Compilation mode

After reading about a blog article, I found out it is possible to run quite a few things through compilation-mode, so why not? First, lets redefine some keybinds for this mode. Ill also define a general keybind in order to re-run my programs from other buffers than the compilation-mode buffer. I also want to follow the output of the compilation buffer, as well as enable some syntax highlighting.

(use-package compile
  :defer t
  :straight (compile :type built-in)
  :hook (compilation-mode . colorize-compilation-buffer)
  :init
  (require 'ansi-color)
  (defun colorize-compilation-buffer ()
    (let ((inhibit-read-only t))
      (ansi-color-apply-on-region (point-min) (point-max))))
  :general
  (phundrak/evil
    :keymaps 'compilation-mode-map
    "g" nil
    "r" nil
    "R" #'recompile
    "h" nil)
  (phundrak/leader-key
    "R" #'recompile)
  :config
  (setq compilation-scroll-output t))

Eshell

Eshell is a built-in shell available from Emacs which I use almost as often as fish. Some adjustments are necessary to make it fit my taste though.

(use-package eshell
  :defer t
  :straight (:type built-in :build t)
  :general
  (phundrak/evil
    :keymaps 'eshell-mode-map
    "c" #'evil-backward-char
    "t" #'evil-next-line
    "s" #'evil-previous-line
    "r" #'evil-forward-char)
  (general-define-key
   :keymaps 'eshell-mode-map
   :states 'insert
   "C-a" #'eshell-bol
   "C-e" #'end-of-line)
  :config
  <<eshell-alias-file>>
  <<eshell-concat-shell-command>>
  <<eshell-alias-open>>
  <<eshell-alias-buffers>>
  <<eshell-alias-emacs>>
  <<eshell-alias-mkcd>>)
Aliases

First, lets declare our list of “dumb” aliases well use in Eshell. You can find them here.

(setq eshell-aliases-file (expand-file-name "eshell-aliases" user-emacs-directory))

A couple of other aliases will be defined through custom Elisp functions, but first Ill need a function for concatenating a shell command into a single string:

(defun phundrak/concatenate-shell-command (&rest command)
  "Concatenate an eshell COMMAND into a single string.
All elements of COMMAND will be joined in a single
space-separated string."
  (mapconcat #'identity command " "))

Ill also declare some aliases here, such as open and openo that respectively allow me to open a file in Emacs, and same but in another window.

(defalias 'open #'find-file)
(defalias 'openo #'find-file-other-window)

The default behavior of eshell/clear is not great at all, although it clears the screen it also scrolls all the way down. Therefore, lets alias it to eshell/clear-scrollback which has the correct behavior.

(defalias 'eshell/clear #'eshell/clear-scrollback)

As you see, these were not declared in my dedicated aliases file but rather were declared programmatically. This is because I like to keep my aliases file for stuff that could work too with other shells were the syntax a bit different, and aliases related to Elisp are kept programmatically. Ill also declare list-buffers an alias of ibuffer because naming it that way kind of makes more sense to me.

(defalias 'list-buffers 'ibuffer)

I still have some stupid muscle memory telling me to open emacs, vim or nano in Eshell, which is stupid: Im already inside Emacs and I have all its power available instantly. So, lets open each file passed to these commands.

(defun eshell/emacs (&rest file)
  "Open each FILE and kill eshell.
Old habits die hard."
  (when file
    (dolist (f (reverse file))
      (find-file f t))))

Finally, Ill declare mkcd which allows the simultaneous creation of a directory and moving into this newly created directory. And of course, it will also work if the directory also exists or if parent directories dont, similarly to the -p option passed to mkdir.

(defun eshell/mkcd (dir)
  "Create the directory DIR and move there.
If the directory DIR doesnt exist, create it and its parents
if needed, then move there."
  (mkdir dir t)
  (cd dir))
Autosuggestion

I really like fishs autosuggestion feature, so lets reproduce it here!

(use-package esh-autosuggest
  :defer t
  :after eshell
  :straight (:build t)
  :hook (eshell-mode . esh-autosuggest-mode)
  :general
  (:keymaps 'esh-autosuggest-active-map
   "C-e" #'company-complete-selection))
Commands

When Im in Eshell, sometimes I wish to open multiple files at once in Emacs. For this, when I have several arguments for find-file, I want to be able to open them all at once. Lets modify find-file like so:

(defadvice find-file (around find-files activate)
  "Also find all files within a list of files. This even works recursively."
  (if (listp filename)
      (cl-loop for f in filename do (find-file f wildcards))
    ad-do-it))

I also want to be able to have multiple instances of Eshell opened at once. For that, I declared the function eshell-new that does exactly that.

(defun eshell-new ()
  "Open a new instance of eshell."
  (interactive)
  (eshell 'N))

A very useful command I use often in fish is z, a port from bashs and zshs command that allows to jump around directories based on how often we go in various directories.

(use-package eshell-z
  :defer t
  :after eshell
  :straight (:build t)
  :hook (eshell-mode . (lambda () (require 'eshell-z))))
Environment Variables

Some environment variables need to be correctly set so Eshell can correctly work. I would like to set two environment variables related to Dart development: the DART_SDK and ANDROID_HOME variables.

(setenv "DART_SDK" "/opt/dart-sdk/bin")
(setenv "ANDROID_HOME" (concat (getenv "HOME") "/Android/Sdk/"))

The EDITOR variable also needs to be set for git commands, especially the yadm commands.

(setenv "EDITOR" "emacsclient -c -a emacs")

Finally, for some specific situations I need SHELL to be set to something more standard than fish:

(setenv "SHELL" "/bin/sh")
Visual configuration

I like to have at quick glance some information about my machine when I fire up a terminal. I havent found anything that does that the way I like it, so Ive written a package! Its actually available on MELPA, but since Im the main dev of this package, Ill keep track of the git repository.

(use-package eshell-info-banner
  :after (eshell)
  :defer t
  :straight (eshell-info-banner :build t
                                :type git
                                :host github
                                :protocol ssh
                                :repo "phundrak/eshell-info-banner.el")
  :hook (eshell-banner-load . eshell-info-banner-update-banner)
  :config
  (setq eshell-info-banner-width 80
        eshell-info-banner-partition-prefixes '("/dev" "zroot" "tank")))

Another feature I like is fish-like syntax highlight, which brings some more colors to Eshell.

(use-package eshell-syntax-highlighting
  :after (esh-mode eshell)
  :defer t
  :straight (:build t)
  :config
  (eshell-syntax-highlighting-global-mode +1))

Powerline prompts are nice, git-aware prompts are even better! eshell-git-prompt is nice, but I prefer to write my own package for that.

(use-package powerline-eshell
  :load-path "~/fromGIT/emacs-packages/powerline-eshell.el/"
  :after eshell)

Eww

Since Emacs 29, it is possible to automatically rename eww buffers to a more human-readable name, see Prots blog post on the matter.

(use-package eww
  :defer t
  :straight (:type built-in)
  :config
  (setq eww-auto-rename-buffer 'title))

Info

Lets define some more intuitive keybinds for info-mode.

(use-package info
  :defer t
  :straight (info :type built-in :build t)
  :general
  (phundrak/evil
    :keymaps 'Info-mode-map
    "c" #'Info-prev
    "t" #'evil-scroll-down
    "s" #'evil-scroll-up
    "r" #'Info-next)
  (phundrak/major-leader-key
    :keymaps 'Info-mode-map
    "?" #'Info-toc
    "b" #'Info-history-back
    "f" #'Info-history-forward
    "m" #'Info-menu
    "t" #'Info-top-node
    "u" #'Info-up))

Tramp

Tramp is an Emacs built-in package that allows the user to connect to various hosts using various protocols, such as ssh and rsync. However, I have some use-case for Tramp which are not supported natively. I will describe them here.

(require 'tramp)
(use-package tramp
  :straight (tramp :type built-in :build t)
  :init
  <<tramp-add-yadm>>)
Yadm

yadm is a git wrapper made to easily manage your dotfiles. It has loads of features I dont use (the main one I like but dont use is its Jinja-like host and OS-aware syntax), but unfortunately Magit doesnt play nice with it. Tramp to the rescue, and this page explains how! Lets just insert in my config this code snippet:

(add-to-list 'tramp-methods
                   '("yadm"
                     (tramp-login-program "yadm")
                     (tramp-login-args (("enter")))
                     (tramp-login-env (("SHELL") ("/bin/sh")))
                     (tramp-remote-shell "/bin/sh")
                     (tramp-remote-shell-args ("-c"))))

Ill also create a fuction for connecting to this new Tramp protocol:

(defun my/yadm ()
  "Manage my dotfiles through TRAMP."
  (interactive)
  (magit-status "/yadm::"))

EXWM

So, Im finally slowly getting back to EXWM. I tried it a couple of years ago, but that was with the SpacemacsOS layer on Spacemacs, on a laptop which got accidentaly formatted before I could save my config and all… So it got me some time to come back. Im still a bit worried about Emacs being single threaded, so if I get one blocking function blocking Emacs, my whole desktop will hang, but for now I havent had this issue.

First, I need to install the X protocol Emacs Lisp Bindings. It doesnt seem to be available in any repo, so Ill install it directly from Git.

(use-package xelb
  :straight (xelb :build t
                  :type git
                  :host github
                  :repo "emacs-straight/xelb"
                  :fork "ch11ng/xelb"))

Next is a function Ive stolen copied from Daviwils desktop configuration. This allows to launch software in the background easily.

(defun exwm/run-in-background (command &optional once)
  (let ((command-parts (split-string command " +")))
    (apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts)))))

In order to launch Emacs with EXWM with startx, I need a xinit file. This one is exported to $HOME/.xinitrc.emacs.

#!/bin/sh

xhost +SI:localuser:$USER

# Set fallback cursor
xsetroot -cursor_name left_ptr

# If Emacs is started in server mode, `emacsclient` is a convenient way to edit
# files in place (used by e.g. `git commit`)
export VISUAL=emacsclient
export EDITOR="$VISUAL"

# in case Java applications display /nothing/
# wmname LG3D
# export _JAVA_AWT_WM_NONREPARENTING=1

exec emacs --with-exwm

EXWM itself

Now we come to the plat de résistance. Like with xelb, Im using its Git source to install it to make sure I get the right version — the version available on the GNU ELPA is from the same source, true, but I dont know at which rate it is updated. And more packages down the line will depend on this Git repository, so I might as well just clone it right now.

As you can see, in the :config secion I added to two hooks functions so buffers are accurately renamed. While the average X window will simply get the name of the current X window, I want Firefox and Qutebrowser to be prefixed with the name of the browser. Actually, all these will be renamed this way:

  • Kitty
  • Qutebrowser
(format "%s\n%S"
        (mapconcat (lambda (buffer)
                     (let ((buffer-name (car buffer)))
                       (format "(\"%s\" %S)"
                               (downcase buffer-name)
                               `(exwm-workspace-rename-buffer (concat ,buffer-name
                                                                      " - "
                                                                      exwm-title)))))
                   buffers
                   "\n")
        '(_otherwise (exwm-workspace-rename-buffer exwm-title)))
("kitty" (exwm-workspace-rename-buffer (concat "EXWM: " "Kitty" " - " exwm-title)))
("qutebrowser" (exwm-workspace-rename-buffer (concat "EXWM: " "Qutebrowser" " - " exwm-title)))
(_otherwise (exwm-workspace-rename-buffer exwm-title))
(add-hook 'exwm-update-class-hook
          (lambda () (exwm-workspace-rename-buffer exwm-class-name)))

(add-hook 'exwm-update-title-hook
          (lambda ()
            (pcase exwm-class-name
              <<exwm-gen-buffers-rename()>>)))

As you can see below, in the :config section I added two advices and one hook in order to correctly integrate evil with EXWM. When Im in an X window, I want to be in insert-mode so I can type however I want. However, when I exit one, I want to default back to normal-mode.

(add-hook 'exwm-manage-finish-hook (lambda () (call-interactively #'exwm-input-release-keyboard)))
(advice-add #'exwm-input-grab-keyboard :after (lambda (&optional id) (evil-normal-state)))
(advice-add #'exwm-input-release-keyboard :after (lambda (&optional id) (evil-insert-state)))

Secondly, I add i, C-SPC, and M-m as exwm prefix keys so they arent sent directly to the X windows but caught by Emacs (and EXWM). Ill use the i key in normal-mode to enter insert-mode and have Emacs release the keyboard so the X window can grab it. Initially, I had s-<escape> as a keybind for grabbing back the keyboard from an X window, as if I were in insert mode and wanted to go back to normal mode, and I had s-I to toggle keyboard grabbing. But I found myself more than once trying to use s-<escape> to toggle this state, s-I completely forgotten. So I removed s-I and made s-<escape> behave like s-I once did.

(general-define-key
 :keymaps 'exwm-mode-map
 :states 'normal
 "i" #'exwm-input-release-keyboard)

(exwm-input-set-key (kbd "s-<escape>") #'exwm-input-toggle-keyboard)

(push ?\i exwm-input-prefix-keys)
(push (kbd "C-SPC") exwm-input-prefix-keys)
(push (kbd "M-m") exwm-input-prefix-keys)

As stated a couple of times in my different configuration files, Im using the bépo layout, which means the default keys in the number row are laid as follow:

(defconst exwm-workspace-keys '("\"" "«" "»" "(" ")" "@" "+" "-" "/" "*"))

With this, we can create keybinds for going or sending X windows to workspaces 0 to 9.

(setq exwm-input-global-keys
      `(,@exwm-input-global-keys
        ,@(mapcar (lambda (i)
                    `(,(kbd (format "s-%s" (nth i exwm-workspace-keys))) .
                      (lambda ()
                        (interactive)
                        (exwm-workspace-switch-create ,i))))
                  (number-sequence 0 9))
        ,@(mapcar (lambda (i)
                    `(,(kbd (format "s-%d" i)) .
                      (lambda ()
                        (interactive)
                        (exwm-workspace-move-window ,(let ((index (1- i)))
                                                       (if (< index 0)
                                                           (- 10 index)
                                                         ;; FIXME: does not work with s-0
                                                         index))))))
                  (number-sequence 0 9))))

You can then see the list of the keybinds I have set for EXWM, which are all prefixed with SPC x in normal mode (and C-SPC x in insert mode), with the exception of s-RET which opens an eshell terminal.

(exwm-input-set-key (kbd "s-<return>") (lambda ()
                                         (interactive)
                                         (eshell)))

(phundrak/leader-key
  :infix "x"
  ""   '(:ignore t :which-key "EXWM")
  "k"  #'exwm-input-send-next-key
  "l"  '((lambda ()
           (interactive)
           (start-process "" nil "plock"))
         :which-key "lock")
  "r"  '(:ignore t :wk "rofi")
  "rr" '((lambda () (interactive)
           (with-temp-buffer
             (shell-command "rofi -show drun" (current-buffer) (current-buffer))))
         :wk "drun")
  "rw" '((lambda () (interactive)
           (with-temp-buffer "rofi -show window" (current-buffer) (current-buffer)))
         :wk "windows")
  "R"  '(:ignore t :wk "restart")
  "Rr" #'exwm-reset
  "RR" #'exwm-restart
  "t"  '(:ignore t :which-key "toggle")
  "tf" #'exwm-layout-toggle-fullscreen
  "tF" #'exwm-floating-toggle-floating
  "tm" #'exwm-layout-toggle-mode-line
  "w"  '(:ignore t :which-key "workspaces")
  "wa" #'exwm-workspace-add
  "wd" #'exwm-workspace-delete
  "ws" #'exwm-workspace-switch
  "x"  '((lambda ()
           (interactive)
           (let ((command (string-trim (read-shell-command "RUN: "))))
             (start-process command nil command)))
         :which-key "run")
  "RET" #'eshell-new)

A couple of commands are also automatically executed:

mpc stop
stops any music played by MPD (if any) when Emacs is launched.
pumopm
my power manager, you can see its code source here.
xss-lock plock
automatically lock the desktop with my script plock.
xrdb -merge $HOME/.Xresources
use the settings from my xresources file
(mapconcat (lambda (command)
             (let* ((whitespace  (rx (+ space)))
                    (raw-command (car (split-string (car command) "::" t whitespace)))
                    (command     (replace-regexp-in-string (regexp-quote "~") "" raw-command)))
               (format "%S" `(exwm/run-in-background ,command))))
           autostarts
           "\n")
(exwm/run-in-background "mpc stop")
(exwm/run-in-background "pumopm")
(exwm/run-in-background "xss-lock plock")
(exwm/run-in-background "xrdb -merge $HOME/.Xresources")

Finally, lets only initialize and start EXWM once functions from exwm-randr ran, because otherwise having multiple monitors dont work.

(with-eval-after-load 'exwm-randr
  (exwm-init))

The complete configuration for the exwm package can be found below.

(use-package exwm
  :straight (exwm :build t
                  :type git
                  :host github
                  :repo "ch11ng/exwm")
  :custom
  (use-dialog-box nil "Disable dialog boxes since they are unusable in EXWM")
  (exwm-input-line-mode-passthrough t "Pass all keypresses to emacs in line mode.")
  :init
  (require 'exwm-config)
  (setq exwm-workspace-number 6)
  :config
  <<exwm-randr>>

  <<exwm-buffers-name>>

  <<exwm-advices-evil>>
  <<exwm-prefix-keys>>

  <<exwm-bepo-number-row>>
  <<exwm-workspace-keybinds>>

  <<exwm-keybinds>>

  <<exwm-generate-autostarts()>>

  <<exwm-init>>)

EXWM-Evil integration

(use-package evil-exwm-state
  :defer t
  :after exwm
  :straight (evil-exwm-state :build t
                             :type git
                             :host github
                             :repo "domenzain/evil-exwm-state"))

Multimonitor support

(require 'exwm-randr)
(exwm/run-in-background "xwallpaper --zoom \"${cat $HOME/.cache/wallpaper}\"")
(start-process-shell-command
 "xrandr" nil "xrandr --output eDP1 --mode 1920x1080 --pos 0x0 --rotate normal --output HDMI1 --primary --mode 2560x1080 --pos 1920x0 --rotate normal --output VIRTUAL1 --off --output DP-1-0 --off --output DP-1-1 --off")
(exwm-randr-enable)
(setq exwm-randr-workspace-monitor-plist '(3 "eDP1"))

Keybinds for a desktop environment

(use-package desktop-environment
  :defer t
  :straight (desktop-environment :build t
                                 :type git
                                 :host github
                                 :repo "DamienCassou/desktop-environment")
  :after exwm
  :diminish t
  :config
  (add-hook 'exwm-init-hook #'desktop-environment-mode)
  (setq desktop-environment-update-exwm-global-keys :prefix
        exwm-layout-show-al-buffers t)

  (setq desktop-environment-bluetooth-command "bluetoothctl"

        desktop-environment-brightness-get-command      "xbacklight -get"
        desktop-environment-brightness-get-regexp       (rx line-start (group (+ digit)))
        desktop-environment-brightness-set-command      "xbacklight %s"
        desktop-environment-brightness-normal-increment "-inc 5"
        desktop-environment-brightness-normal-decrement "-dec 5"
        desktop-environment-brightness-small-increment  "-inc 2"
        desktop-environment-brightness-small-decrement  "-dec 2"

        desktop-environment-volume-normal-decrement "5%-"
        desktop-environment-volume-normal-increment "5%+"
        desktop-environment-volume-small-decrement  "2%-"
        desktop-environment-volume-small-increment  "2%+"
        desktop-environment-volume-set-command      "amixer -q Master %s unmute"

        desktop-environment-screenshot-directory "~/Pictures/Screenshots"
        desktop-environment-screenlock-command   "plock"

        desktop-environment-music-toggle-command   "mpc toggle"
        desktop-environment-music-previous-command "mpc prev"
        desktop-environment-music-next-command     "mpc next"
        desktop-environment-music-stop-command     "mpc stop")

  (general-define-key
   "<XF86AudioPause>" (lambda () (interactive)
                        (with-temp-buffer
                           (shell-command "mpc pause" (current-buffer) (current-buffer)))))

  (desktop-environment-mode))

Bluetooth

(defvar bluetooth-command "bluetoothctl")
(defun bluetooth-turn-on ()
  (interactive)
  (let ((process-connection-type nil))
    (start-process "" nil bluetooth-command "power" "on")))
(defun bluetooth-turn-off ()
  (interactive)
  (let ((process-connection-type nil))
    (start-process "" nil bluetooth-command "power" "off")))
(defun create-bluetooth-device (raw-name)
  "Create a bluetooth device cons from RAW NAME.
The cons will hold first the MAC address of the device, then its
human-friendly name."
  (let ((split-name (split-string raw-name " " t)))
    `(,(mapconcat #'identity (cddr split-name) " ") . ,(cadr split-name))))
(require 'dbus)
(defun bluetooth-get-devices ()
  (let ((bus-list (dbus-introspect-get-node-names :system "org.bluez" "/org/bluez/hci0")))
    (mapcar (lambda (device)
              `(,(dbus-get-property :system
                                    "org.bluez"
                                    (concat "/org/bluez/hci0/" device)
                                    "org.bluez.Device1"
                                    "Alias")
                . ,device))
            bus-list)))
(defun bluetooth-connect-device ()
  (interactive)
  (progn
    (bluetooth-turn-on)
    (let* ((devices (bluetooth-get-devices))
           (device  (alist-get (completing-read "Device: " devices)
                               devices nil nil #'string=)))
      (dbus-call-method-asynchronously
       :system "org.bluez"
       (concat "/org/bluez/hci0" device)
       "org.bluez.Device1"
       "Connect"
       (lambda (&optional msg)
         (when msg (message "%s" msg)))))))

Making my life easier

Bufler

Bufler is a package that organizes and lists buffers in a much better way than how they are usually sorted. You can easily and quickly find buffers by their group, not only by their name, and THIS is great news! Also, no helm please! And for some reasons the keybindings are borked by default, so lets redefine them, and lets also rebind SPC to p since it would conflict with my main general prefix.

(use-package bufler
  :straight (bufler :build t
                    :files (:defaults (:exclude "helm-bufler.el")))
  :defer t
  :general
  (phundrak/evil
   :keymaps  'bufler-list-mode-map
   :packages 'bufler
   "?"   #'hydra:bufler/body
   "g"   #'bufler
   "f"   #'bufler-list-group-frame
   "F"   #'bufler-list-group-make-frame
   "N"   #'bufler-list-buffer-name-workspace
   "k"   #'bufler-list-buffer-kill
   "p"   #'bufler-list-buffer-peek
   "s"   #'bufler-list-buffer-save
   "RET" #'bufler-list-buffer-switch))

Helpful

As the name tells, helpful is a really helpful package which greatly enhances a couple of built-in functions from Emacs, namely:

Vanilla Emacs Function Helpful Function Comment
describe-function helpful-callable Only interactive functions
describe-function helpful-function Only actual functions (including interactive)
describe-function helpful-macro
describe-command helpful-command
describe-key helpful-key
describe-variable helpful-variable
(use-package helpful
  :straight (:build t)
  :after (counsel ivy)
  :custom
  (counsel-describe-function-function #'helpfull-callable)
  (counsel-describe-variable-function #'helpfull-variable)
  :bind
  ([remap describe-function] . counsel-describe-function)
  ([remap describe-command]  . helpful-command)
  ([remap describe-variable] . counsel-describe-variable)
  ([remap describe-key]      . helpful-key))

LaTeX

(use-package auctex
  :defer t
  :straight (:build t)
  :hook (tex-mode . lsp-deferred)
  :hook (latex-mode . lsp-deferred)
  :init
  (setq TeX-command-default   (if (executable-find "latexmk") "LatexMk" "LaTeX")
        TeX-engine            (if (executable-find "xetex")   'xetex    'default)
        TeX-auto-save                     t
        TeX-parse-self                    t
        TeX-syntactic-comment             t
        TeX-auto-local                    ".auctex-auto"
        TeX-style-local                   ".auctex-style"
        TeX-source-correlate-mode         t
        TeX-source-correlate-method       'synctex
        TeX-source-correlate-start-server nil
        TeX-electric-sub-and-superscript  t
        TeX-fill-break-at-separators      nil
        TeX-save-query                    t)
  :config
  <<latex-fontification>>
  (setq TeX-master t)
  (setcar (cdr (assoc "Check" TeX-command-list)) "chktex -v6 -H %s")
  (add-hook 'TeX-mode-hook (lambda ()
                             (setq ispell-parser          'tex
                                   fill-nobreak-predicate (cons #'texmathp fill-nobreak-predicate))))
  (add-hook 'TeX-mode-hook #'visual-line-mode)
  (add-hook 'TeX-update-style-hook #'rainbow-delimiters-mode)
  :general
  (phundrak/major-leader-key
    :packages 'auctex
    :keymaps  '(latex-mode-map LaTeX-mode-map)
    "v" '(TeX-view            :which-key "View")
    "c" '(TeX-command-run-all :which-key "Compile")
    "m" '(TeX-command-master  :which-key "Run a command")))

From Doom Emacs configuration:

(setq font-latex-match-reference-keywords
      '(;; BibLaTeX.
        ("printbibliography" "[{") ("addbibresource" "[{")
        ;; Standard commands.
        ("cite" "[{")       ("citep" "[{")
        ("citet" "[{")      ("Cite" "[{")
        ("parencite" "[{")  ("Parencite" "[{")
        ("footcite" "[{")   ("footcitetext" "[{")
        ;; Style-specific commands.
        ("textcite" "[{")   ("Textcite" "[{")
        ("smartcite" "[{")  ("Smartcite" "[{")
        ("cite*" "[{")      ("parencite*" "[{")
        ("supercite" "[{")
        ;; Qualified citation lists.
        ("cites" "[{")      ("Cites" "[{")
        ("parencites" "[{") ("Parencites" "[{")
        ("footcites" "[{")  ("footcitetexts" "[{")
        ("smartcites" "[{") ("Smartcites" "[{")
        ("textcites" "[{")  ("Textcites" "[{")
        ("supercites" "[{")
        ;; Style-independent commands.
        ("autocite" "[{")   ("Autocite" "[{")
        ("autocite*" "[{")  ("Autocite*" "[{")
        ("autocites" "[{")  ("Autocites" "[{")
        ;; Text commands.
        ("citeauthor" "[{") ("Citeauthor" "[{")
        ("citetitle" "[{")  ("citetitle*" "[{")
        ("citeyear" "[{")   ("citedate" "[{")
        ("citeurl" "[{")
        ;; Special commands.
        ("fullcite" "[{")
        ;; Cleveref.
        ("cref" "{")          ("Cref" "{")
        ("cpageref" "{")      ("Cpageref" "{")
        ("cpagerefrange" "{") ("Cpagerefrange" "{")
        ("crefrange" "{")     ("Crefrange" "{")
        ("labelcref" "{")))

(setq font-latex-match-textual-keywords
      '(;; BibLaTeX brackets.
        ("parentext" "{") ("brackettext" "{")
        ("hybridblockquote" "[{")
        ;; Auxiliary commands.
        ("textelp" "{")   ("textelp*" "{")
        ("textins" "{")   ("textins*" "{")
        ;; Subcaption.
        ("subcaption" "[{")))

(setq font-latex-match-variable-keywords
      '(;; Amsmath.
        ("numberwithin" "{")
        ;; Enumitem.
        ("setlist" "[{")     ("setlist*" "[{")
        ("newlist" "{")      ("renewlist" "{")
        ("setlistdepth" "{") ("restartlist" "{")
        ("crefname" "{")))
(use-package smartparens-latex
  :after auctex
  :defer t
  :straight (:build t)
  :config
  (let ((modes '(tex-mode plain-tex-mode latex-mode LaTeX-mode)))
    ;; From DoomEmacs
    ;; We have to use lower case modes here, because `smartparens-mode' uses
    ;; the same during configuration.
    (dolist (open '("\\left(" "\\left[" "\\left\\{" "\\left|"
                    "\\bigl(" "\\biggl(" "\\Bigl(" "\\Biggl(" "\\bigl["
                    "\\biggl[" "\\Bigl[" "\\Biggl[" "\\bigl\\{" "\\biggl\\{"
                    "\\Bigl\\{" "\\Biggl\\{"
                    "\\lfloor" "\\lceil" "\\langle"
                    "\\lVert" "\\lvert" "`"))
      (sp-local-pair modes open nil :actions :rem))
    (sp-local-pair-modes $``$ nil :unless '(:add sp-in-math-p))))
(after! latex
  (setq LaTeX-section-hook
        '(LaTeX-section-heading
          LaTeX-section-title
          LaTeX-section-toc
          LaTeX-section-section
          LaTeX-section-label)
        LaTeX-fill-break-at-separators nil
        LaTeX-item-indent              0))
(after! preview
  (add-hook 'LaTeX-mode-hook #'LaTeX-preview-setup)
  (setq-default preview-scale 1.4
                preview-scale-function
                (lambda () (* (/ 10.0 (preview-document-pt)) preview-scale)))
  (setq preview-auto-cache-preamble nil)
  (phundrak/major-leader-key
    :packages 'auctex
    :keymaps '(latex-mode-map LaTeX-mode-map)
    "p" #'preview-at-point
    "P" #'preview-clearout-at-point))
(use-package cdlatex
  :defer t
  :after auctex
  :straight (:build t)
  :hook (LaTeX-mode . cdlatex-mode)
  :hook (org-mode   . org-cdlatex-mode)
  :config
  (setq cdlatex-use-dollar-to-ensure-math nil)
  :general
  (phundrak/major-leader-key
    :packages 'cdlatex
    :keymaps 'cdlatex-mode-map
    "$" nil
    "(" nil
    "{" nil
    "[" nil
    "|" nil
    "<" nil
    "^" nil
    "_" nil
    [(control return)] nil))
(use-package adaptive-wrap
  :defer t
  :after auctex
  :straight (:build t)
  :hook (LaTeX-mode . adaptative-wrap-prefix-mode)
  :init (setq-default adaptative-wrap-extra-indent 0))
(use-package auctex-latexmk
  :after auctex
  :defer t
  :straight (:build t)
  :init
  (setq auctex-latexmk-inherit-TeX-PDF-mode t)
  (add-hook 'LaTeX-mode (lambda () (setq TeX-command-default "LatexMk")))
  :config
  (auctex-latexmk-setup))
(use-package company-auctex
  :defer t
  :after (company auctex)
  :straight (:build t)
  :config
  (company-auctex-init))
(use-package company-math
  :defer t
  :straight (:build t)
  :after (company auctex)
  :config
  (defun my-latex-mode-setup ()
    (setq-local company-backends
                (append '((company-math-symbols-latex company-latex-commands))
                        company-backends)))
  (add-hook 'TeX-mode-hook #'my-latex-mode-setup))

Org-mode

Since recently, in order to make org-cite compile properly, we need the citeproc package, a citation processor.

(use-package citeproc
  :after (org)
  :defer t
  :straight (:build t))

Org is the main reason I am using Emacs. It is an extremely powerfu tool when you want to write anything that is not necessarily primarily programming-related, though it absolutely can be! Org can be a replacement for anything similar to LibreOffice Writer, LibreOffice Calc, and LibreOffice Impress. It is a much more powerful (and older) version of Markdown which can be exported to LaTeX and HTML at least, rendering writing web pages and technical, scientific documents much simpler than writing manually HTML and LaTeX code, especially when a single document source is meant to be exported for both formats. And since org is an Emacs package, that also means it can be greatly extended however we like!

(use-package org
  :straight t
  :defer t
  :commands (orgtbl-mode)
  :hook ((org-mode . visual-line-mode)
         (org-mode . org-num-mode))
  :custom-face
  (org-macro ((t (:foreground "#b48ead"))))
  :init
  (auto-fill-mode -1)
  :config
  <<org-hydra-babel>>
  (require 'ox-beamer)
  (setq org-hide-leading-stars             nil
        org-hide-macro-markers             t
        org-ellipsis                       " ⤵"
        org-image-actual-width             600
        org-redisplay-inline-images        t
        org-display-inline-images          t
        org-startup-with-inline-images     "inlineimages"
        org-pretty-entities                t
        org-fontify-whole-heading-line     t
        org-fontify-done-headline          t
        org-fontify-quote-and-verse-blocks t
        org-startup-indented               t
        org-startup-align-all-tables       t
        org-use-property-inheritance       t
        org-list-allow-alphabetical        t
        org-M-RET-may-split-line           nil
        org-src-window-setup               'split-window-below
        org-src-fontify-natively           t
        org-src-tab-acts-natively          t
        org-src-preserve-indentation       t
        org-log-done                       'time
        org-directory                      "~/org"
        org-default-notes-file             (expand-file-name "notes.org" org-directory))
  <<org-agenda-files>>
  <<org-behavior-electric>>
  <<org-capture-target-files>>
  <<org-capture-templates>>
  <<org-babel-load-languages>>
  <<org-use-sub-superscripts>>
  <<org-latex-compiler>>
  <<org-latex-listings>>
  <<org-latex-default-packages>>
  <<org-export-latex-hyperref-format>>
  <<org-export-latex-minted-options>>
  <<org-latex-pdf-process>>
  <<org-latex-logfiles-add-extensions>>
  <<org-re-reveal>>
  <<org-html-validation>>
  <<org-latex-classes>>
  <<org-publish-projects>>
  <<org-mode-visual-prettify-symbols>>
  :general
  (phundrak/evil
    :keymaps 'org-mode-map
    :packages 'org
    "RET" 'org-open-at-point)
  (phundrak/major-leader-key
    :keymaps 'org-mode-map
    :packages 'org
    <<general-keybindings-gen(table=org-keybinds-various)>>
    <<general-keybindings-gen(table=org-keybinds-babel)>>
    <<general-keybindings-gen(table=org-keybinds-dates)>>
    <<general-keybindings-gen(table=org-keybinds-insert)>>
    <<general-keybindings-gen(table=org-keybinds-jump)>>
    <<general-keybindings-gen(table=org-keybinds-tables)>>
    <<general-keybindings-gen(table=org-keybinds-toggles)>>)
  <<org-capture-keybinds>>
  (phundrak/major-leader-key
    :packages 'org
    :keymaps 'org-src-mode-map
    "'" #'org-edit-src-exit
    "k" #'org-edit-src-abort))

The main feature from evil-org that I love is how easy it is to modify some keybindings for keyboards layouts that do not have hjkl, such as the bépo layout (or Dvorak or Colemak if you are into that). But it also adds a ton of default keybindings which are just much more comfortable than the default ones you get with evil and org naked.

(use-package evil-org
  :straight (:build t)
  :after (org)
  :hook (org-mode . evil-org-mode)
  :config
  (setq-default evil-org-movement-bindings
                '((up    . "s")
                  (down  . "t")
                  (left  . "c")
                  (right . "r")))
  (evil-org-set-key-theme '(textobjects navigation calendar additional shift operators))
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

This package is a small package Ive written that helps me when writing conlanging documents, with features such as creating syntax trees, converting translitterated text to its native script, etc…

(use-package conlanging
  :straight (conlanging :build t
                        :type git
                        :repo "https://labs.phundrak.com/phundrak/conlanging.el")
  :after org
  :defer t)

Since very recently, the contrib/lisp/ directory of org moved out of the main repository to this repository. On the other hand, contrib/scripts/ moved to the worg repository, but I dont need it. The main reason I want org-contrib is due to ox-extra that allow the usage of the :ignore: tag in org.

(use-package org-contrib
  :after (org)
  :defer t
  :straight (:build t)
  :init
  (require 'ox-extra)
  (ox-extras-activate '(latex-header-blocks ignore-headlines)))

Agenda

(setq-default org-agenda-files (list "~/org/agenda" "~/org/notes.org"))

Babel

One of the amazing features of org-mode is its literary programming capacities by running code blocks from within Org-mode itself. But for that, only a couple of languages are supported directly by Org-mode itself, and they need to be activated. Here are the languages I activated in my Org-mode configuration:

C
emacs-lisp
gnuplot
latex
makefile
sass
shell
'((C . t)
  (emacs-lisp . t)
  (gnuplot . t)
  (latex . t)
  (makefile . t)
  (sass . t)
  (shell . t))

The corresponding code is as follows:

(org-babel-do-load-languages
 'org-babel-load-languages
 <<org-babel-languages-gen()>>)

A package I use from time to time is ob-latex-as-png which allows me to easily convert a LaTeX snippet into a PNG, regardless of the exporter I use afterwards. Its installation is pretty simple:

(use-package ob-latex-as-png
  :after org
  :straight (:build t))
(use-package ob-restclient
  :straight (:build t)
  :defer t
  :after (org ob)
  :init
  (add-to-list 'org-babel-load-languages '(restclient . t)))

Behavior

A useful package I like is toc-org which creates automatically a table of contents. My main usage for this however is not just to create a table of content of my files to quickly jump around my file (I have counsel-org-goto for that), but it is for creating table of contents for org files that will be hosted and viewable on Github.

(use-package toc-org
  :after (org markdown-mode)
  :straight (:build t)
  :init
  (add-to-list 'org-tag-alist '("TOC" . ?T))
  :hook (org-mode . toc-org-enable)
  :hook (markdown-mode . toc-org-enable))

electric-mode also bothers me a lot when editing org files, so lets deactivate it:

(add-hook 'org-mode-hook (lambda ()
                           (interactive)
                           (electric-indent-local-mode -1)))

As explained in my blog post, org-mode is terrible with coming up with meaningful IDs for its headings. I actually wrote a package for this!

(use-package org-unique-id
  :straight (org-unique-id :build t
                           :type git
                           :host github
                           :repo "Phundrak/org-unique-id")
  :defer t
  :after org
  :init
  (add-hook 'org-mode-hook
     (lambda ()
       (add-hook 'before-save-hook
                 (lambda ()
                   (when (and (eq major-mode       'org-mode)
                              (eq buffer-read-only nil))
                     (org-unique-id)))))))

Capture

Org capture is an amazing tool for taking quick notes, be it simple text, links, resources, or reminders. They are all organised is specified org files which are described below.

(defvar org-conlanging-file "~/org/conlanging.org")
(defvar org-notes-file "~/org/notes.org")
(defvar org-journal-file "~/org/journal.org")
(defvar org-linguistics-file "~/org/linguistics.org")
(defvar org-novel-file "~/org/novel.org")
(defvar org-agenda-file "~/org/agenda/private.org")
(defvar org-school-file "~/org/agenda/school.org")
(defvar org-worldbuilding-file "~/org/worldbuilding.org")

Let me describe a keybind to invoke org-capture from anywhere within Emacs.

(phundrak/leader-key
  :packages 'org
  :infix "o"
  ""  '(:ignore t :which-key "org")
  "c" #'org-capture)

When org-capture is invoked, it will ask which template we wish to use. In the table /phundrak/dotfiles/src/commit/cb37cb72080f5378be500b7250636dad21792fbe/org/config/org-capture-shortcuts-table, the key column represents which keychord we need to hit, titled with name, we need to hit in order to use the template, inserted in the designated file in the manner described by insertion mode.

Shortcut Name Title Insertion mode file template
e Email
ew Write Email Emails file+headline org-default-notes-file email.orgcaptmpl
j Journal file+datetree org-journal-file journal.orgcaptmpl
l Link
ll General file+headline org-default-notes-file link.orgcaptmpl
ly YouTube file+headline org-default-notes-file youtube.orgcaptmpl
L Protocol Link Link file+headline org-default-notes-file protocol-link.orgcaptmpl
n Notes
nc Conlanging Note file+headline org-conlanging-file notes.orgcaptmpl
nn General file+headline org-default-notes-file notes.orgcaptmpl
nN Novel Note file+headline org-novel-notes-file notes.orgcaptmpl
nq Quote file+headline org-default-notes-file notes-quote.orgcaptmpl
nw Worldbuilding Note file+headline org-wordbuilding-file notes.orgcaptmpl
N Novel
Ni Ideas file+headline org-novel-notes-file notes.orgcaptmpl
p Protocol Link file+headline org-default-notes-file protocol.orgcaptmpl
r Resources
rc Conlanging Resources file+headline org-conlanging-file resource.orgcaptmpl
re Emacs file+headline org-default-notes-file resource.orgcaptmpl
ri Informatique file+headline org-default-notes-file resource.orgcaptmpl
rl Linguistics file+headline org-default-notes-file resource.orgcaptmpl
rL Linux file+headline org-default-notes-file resource.orgcaptmpl
rw Worldbuilding Resources file+headline org-wordbuilding-file resource.orgcaptmpl
t Tasks
tb Birthday file+headline org-private-agenda-file birthday.orgcaptmpl
te Event file+headline org-private-agenda-file event.orgcaptmpl
th Health file+headline org-private-agenda-file health.orgcaptmpl
ti Informatique file+headline org-private-agenda-file informatique.orgcaptmpl

All templates can be found in my dotfiles repository.

(mapconcat (lambda (entry)
             (let ((key      (nth 0 entry))
                   (name     (nth 1 entry))
                   (title    (nth 2 entry))
                   (ins-mode (nth 3 entry))
                   (file     (nth 4 entry))
                   (template (nth 5 entry)))
               (if (string= "" ins-mode)
                   (format "%S" `(,key ,name))
                 (format "(\"%s\" \"%s\" entry\n  %S\n  %S)"
                         key name
                         `(,(intern ins-mode) ,(intern file) ,(if (string= "file+datetree" ins-mode)
                                                                  (intern "")
                                                                (if (string= title "")
                                                                    name
                                                                  title)))
                         `(file ,(concat "~/org/capture/" template))))))
           entries
           "\n")
("e" "Email")
("ew" "Write Email" entry
  (file+headline org-default-notes-file "Emails")
  (file "~/org/capture/email.orgcaptmpl"))
("j" "Journal" entry
  (file+datetree org-journal-file ##)
  (file "~/org/capture/journal.orgcaptmpl"))
("l" "Link")
("ll" "General" entry
  (file+headline org-default-notes-file "General")
  (file "~/org/capture/link.orgcaptmpl"))
("ly" "YouTube" entry
  (file+headline org-default-notes-file "YouTube")
  (file "~/org/capture/youtube.orgcaptmpl"))
("L" "Protocol Link" entry
  (file+headline org-default-notes-file "Link")
  (file "~/org/capture/protocol-link.orgcaptmpl"))
("n" "Notes")
("nc" "Conlanging" entry
  (file+headline org-conlanging-file "Note")
  (file "~/org/capture/notes.orgcaptmpl"))
("nn" "General" entry
  (file+headline org-default-notes-file "General")
  (file "~/org/capture/notes.orgcaptmpl"))
("nN" "Novel" entry
  (file+headline org-novel-notes-file "Note")
  (file "~/org/capture/notes.orgcaptmpl"))
("nq" "Quote" entry
  (file+headline org-default-notes-file "Quote")
  (file "~/org/capture/notes-quote.orgcaptmpl"))
("nw" "Worldbuilding" entry
  (file+headline org-wordbuilding-file "Note")
  (file "~/org/capture/notes.orgcaptmpl"))
("N" "Novel")
("Ni" "Ideas" entry
  (file+headline org-novel-notes-file "Ideas")
  (file "~/org/capture/notes.orgcaptmpl"))
("p" "Protocol" entry
  (file+headline org-default-notes-file "Link")
  (file "~/org/capture/protocol.orgcaptmpl"))
("r" "Resources")
("rc" "Conlanging" entry
  (file+headline org-conlanging-file "Resources")
  (file "~/org/capture/resource.orgcaptmpl"))
("re" "Emacs" entry
  (file+headline org-default-notes-file "Emacs")
  (file "~/org/capture/resource.orgcaptmpl"))
("ri" "Informatique" entry
  (file+headline org-default-notes-file "Informatique")
  (file "~/org/capture/resource.orgcaptmpl"))
("rl" "Linguistics" entry
  (file+headline org-default-notes-file "Linguistics")
  (file "~/org/capture/resource.orgcaptmpl"))
("rL" "Linux" entry
  (file+headline org-default-notes-file "Linux")
  (file "~/org/capture/resource.orgcaptmpl"))
("rw" "Worldbuilding" entry
  (file+headline org-wordbuilding-file "Resources")
  (file "~/org/capture/resource.orgcaptmpl"))
("t" "Tasks")
("tb" "Birthday" entry
  (file+headline org-private-agenda-file "Birthday")
  (file "~/org/capture/birthday.orgcaptmpl"))
("te" "Event" entry
  (file+headline org-private-agenda-file "Event")
  (file "~/org/capture/event.orgcaptmpl"))
("th" "Health" entry
  (file+headline org-private-agenda-file "Health")
  (file "~/org/capture/health.orgcaptmpl"))
("ti" "Informatique" entry
  (file+headline org-private-agenda-file "Informatique")
  (file "~/org/capture/informatique.orgcaptmpl"))

The capture templates are set like so:

(setq org-capture-templates
      '(
        <<org-capture-shortcuts-gen()>>))

Exporters

I want to disable by default behavior of ^ and _ for only one character, making it compulsory to use instead ^{} and _{} respectively. This is due to my frequent usage of the underscore in my org files as a regular character and not a markup one, especially when describing phonetics evolution. So, lets disable it:

(setq org-use-sub-superscripts (quote {}))
Epub

A backend for exporting files through org I like is ox-epub which, as you can guess, exports org files to the Epub format.

(use-package ox-epub
  :after (org ox)
  :straight (:build t))
HTML

On HTML exports, Org-mode tries to include a validation link for the exported HTML. Lets disable that since I never use it.

(setq org-html-validation-link nil)
;; (use-package htmlize
;;   :defer t
;;   :straight (:build t))

This package allows for live-previewing the HTML export of an org buffer in an XWidget Webkit browser window. But when testing it, its not great for large org files, I should keep its usage for smaller org files.

(use-package preview-org-html-mode
  :defer t
  :after (org)
  :straight (preview-org-html-mode :build t
                                   :type git
                                   :host github
                                   :repo "jakebox/preview-org-html-mode")
  :general
  (phundrak/major-leader-key
   :keymaps 'org-mode-map
   :packages 'preview-org-html-mode
   :infix "P"
   ""  '(:ignore t :which-key "preview")
   "h" #'preview-org-html-mode
   "r" #'preview-org-html-refresh
   "p" #'preview-org-html-pop-window-to-frame)
  :config
  (setq preview-org-html-refresh-configuration 'save))
Hugo

I manage my blog with Hugo. Although it natively supports the org format, its not great compared to its markdown support. So, instead, lets directly export our org files as markdown files and let Hugo do the rest of the job for us!

(use-package ox-hugo
  :defer t
  :after ox
  :straight t)

I also have a function for publishing my blog once I exported my articles with ox-hugo. It will compile blog into a public/ directory and copy its content over to my remote server.

(defun phundrak/blog-publish ()
  "Publish my blog through Hugo to my remote server."
  (interactive)
  (let* ((default-directory (expand-file-name "~/org/blog"))
         (public-path       (concat default-directory "/public"))
         (target-path       "/rsync:Tilo:/home/phundrak/www/phundrak.com/blog"))
    (compile "hugo")
    (let ((files (mapcar (lambda (file)
                           (f-relative file public-path))
                         (f-files public-path nil t))))
      (dolist (file files)
        (copy-file (concat public-path "/" file)
                   (concat target-path "/" file)
                   t nil t)))))
LaTeX

When it comes to exports, I want the LaTeX and PDF exports to be done with XeLaTeX only. This implies the modification of the following variable:

(setq org-latex-compiler "xelatex")

I also want to get by default minted for LaTeX listings so I can have syntax highlights:

(setq org-latex-listings 'minted)

The default packages break my LaTeX exports: for some reasons, images are not loaded and exported in PDFs, so I needed to redifine the default packages excluding the one that broke my exports; namely, I need to remove inputenc, fontenc and grffile. I also added some default packages:

  • minted for syntax highlighting
  • cleveref for better references to various elements.
  • svg for inserting SVG files in PDF outputs
  • booktabs for nicer tables
  • and tabularx for tabulars with adjustable columns
(dolist (package '(("AUTO" "inputenc" t ("pdflatex"))
                   ("T1"   "fontenc"  t ("pdflatex"))
                   (""     "grffile"  t)))
  (delete package org-latex-default-packages-alist))

(dolist (package '((""           "minted")
                   ("capitalize" "cleveref")
                   (""           "svg")
                   (""           "booktabs")
                   (""           "tabularx")))
  (add-to-list 'org-latex-default-packages-alist package t))

(setq org-latex-reference-command "\\cref{%s}")

By the way, reference links in LaTeX should be written in this format, since we are using cleveref:

(setq org-export-latex-hyperref-format "\\ref{%s}")

And Minted should be default break lines if a line is too long:

(setq org-latex-minted-options '(("breaklines")
                                 ("tabsize" "2")
                                 ("frame" "single")
                                 ("autogobble")))

When it comes to the export itself, the latex file needs to be processed several times through XeLaTeX in order to get some references right. Dont forget to also run bibtex!

(setq org-latex-pdf-process
      '("xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"
        "bibtex %b"
        "xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"
        "xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"))

Finally, org-mode is supposed to automatically clean logfiles after it exports an org file to LaTeX. However, it misses a few, so I need to add their extension like so:

(dolist (ext '("bbl" "lot"))
  (add-to-list 'org-latex-logfiles-extensions ext t))
Reveal.js
(use-package org-re-reveal
  :defer t
  :after org
  :straight (:build t)
  :init
  (add-hook 'org-mode-hook (lambda () (require 'org-re-reveal)))
  :config
  (setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js"
        org-re-reveal-revealjs-version "4"))
SSH Config

Yet another exporter I enjoy is ox-ssh with which I manage my $HOME/.ssh/config file. You wont find my org file for managing my servers on my repos though.

(use-package ox-ssh
  :after (ox org)
  :straight (:build t))

Keybindings

Be prepared, I have a lot of keybindings for org-mode! They are all prefixed with a comma , in normal mode.

Key chord Function Description
RET org-ctrl-c-ret
* org-ctrl-c-star
, org-ctrl-c-ctrl-c
' org-edit-special
- org-ctrl-c-minus
a org-agenda
c org-capture
e org-export-dispatch
l org-store-link
p org-priority
r org-reload

I then have a couple of babel-related functions.

Key chord Function Description
b nil babel
b. org-babel-transient/body
bb org-babel-execute-buffer
bc org-babel-check-src-block
bC org-babel-tangle-clean
be org-babel-execute-maybe
bf org-babel-tangle-file
bn org-babel-next-src-block
bo org-babel-open-src-block-result
bp org-babel-previous-src-block
br org-babel-remove-result-one-or-many
bR org-babel-goto-named-result
bt org-babel-tangle
bi org-babel-view-src-block-info

The org-babel-transient hydra allows me to quickly navigate between code blocks and interact with them. This code block was inspired by one you can find in Spacemacs.

(defhydra org-babel-transient ()
  "
^Navigate^                    ^Interact
^^^^^^^^^^^------------------------------------------
[_t_/_s_] navigate src blocs  [_x_] execute src block
[_g_]^^   goto named block    [_'_] edit src block
[_z_]^^   recenter screen     [_q_] quit
"
  ("q" nil :exit t)
  ("t" org-babel-next-src-block)
  ("s" org-babel-previous-src-block)
  ("g" org-babel-goto-named-src-block)
  ("z" recenter-top-bottom)
  ("x" org-babel-execute-maybe)
  ("'" org-edit-special :exit t))

We next have keybindings related to org-modes agenda capabilities. We can schedule a todo header for some dates, or set a deadline.

Key chord Function Description
d nil dates
dd org-deadline
ds org-schedule
dt org-time-stamp
dT org-time-stamp-inactive

Lets now define some keybinds for inserting stuff in our org buffer:

Key chord Function Description
i nil insert
ib org-insert-structure-template
id org-insert-drawer
ie org-set-effort
if org-footnote-new
ih org-insert-heading
iH counsel-org-link
ii org-insert-item
il org-insert-link
in org-add-note
ip org-set-property
is org-insert-subheading
it org-set-tags-command

There isnt a lot of stuff I can jump to yet, but theres still some:

Key chord Function Description
j nil jump
ja counsel-org-goto-all
jh counsel-org-goto

Tables get a bit more love:

Key chord Function Description
t nil tables
tc org-table-move-column-left
tt org-table-move-row-down
ts org-table-move-row-up
tr org-table-move-column-right
ta org-table-align
te org-table-eval-formula
tf org-table-field-info
th org-table-convert
tl org-table-recalculate
tp org-plot/gnuplot
tS org-table-sort-lines
tw org-table-wrap-region
tx org-table-shrink
tN org-table-create-with-table.el
td nil delete
tdc org-table-delete-column
tdr org-table-kill-row
ti nil insert
tic org-table-insert-column
tih org-table-insert-hline
tir org-table-insert-row
tiH org-table-hline-and-move
tt nil toggle
ttf org-table-toggle-formula-debugger
tto org-table-toggle-coordinate-overlays

Finally, lets make enabling and disabling stuff accessible:

Key chord Function Description
T nil toggle
Tc org-toggle-checkbox
Ti org-toggle-inline-images
Tl org-latex-preview
Tn org-num-mode
Ts phundrak/toggle-org-src-window-split
Tt org-show-todo-tree
TT org-todo

LaTeX formats

I currently have two custom formats for my Org-mode exports: one for general use (initialy for my conlanging files, hence its conlang name), and one for beamer exports.

Below is the declaration of the conlang LaTeX class:

'("conlang"
  "\\documentclass{book}"
  ("\\chapter{%s}" . "\\chapter*{%s}")
  ("\\section{%s}" . "\\section*{%s}")
  ("\\subsection{%s}" . "\\subsection*{%s}")
  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))

And here is the declaration of the beamer class:

`("beamer"
  ,(concat "\\documentclass[presentation]{beamer}\n"
           "[DEFAULT-PACKAGES]"
           "[PACKAGES]"
           "[EXTRA]\n")
  ("\\section{%s}" . "\\section*{%s}")
  ("\\subsection{%s}" . "\\subsection*{%s}")
  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))

Both these classes have to be added to org-latex-classes like so:

(eval-after-load "ox-latex"
  '(progn
     (add-to-list 'org-latex-classes
                  <<org-latex-class-conlang>>)
     (add-to-list 'org-latex-classes
                  <<org-latex-class-beamer>>)))

Projects

Another great features of Org-mode is the Org projects that allow the user to easily publish a bunch of org files to a remote location. Here is the current declaration of my projects, which will be detailed later:

<<org-proj-config-setup>>
<<org-proj-lang-setup>>
(setq org-publish-project-alist
      `(
        <<org-proj-config-html>>
        <<org-proj-config-static>>
        <<org-proj-config>>
        <<org-proj-lang-html>>
        <<org-proj-lang-pdf>>
        <<org-proj-lang-static>>
        <<org-proj-lang>>))
Configuration website

This is my configuration for exporting my dotfiles to my website in a web format only. No PDFs or anything, just HTML. Please note that I do not use that often anymore, I much prefer the automatic script that I have which deploys through my Drone instance my website on git pushes.

And before we get into the actual configuration, I would like to introduce a couple of variables. This is a bit more verbose than if I declared everything manually, but now I can change all three values at the same time without a hasle.

(defvar phundrak//projects-config-target
  "/ssh:Tilo:~/www/phundrak.com/config"
  "Points to where exported files for config.phundrak.com should be put.")
(defvar phundrak//projects-config-source
  "~/org/config/"
  "Points to where the sources for config.phundrak.com are.")
(defvar phundrak//projects-config-language
  "en"
  "Language of the website config.phundrak.com.")
(defvar phundrak//projects-config-recursive
  t
  "Defines whether subdirectories should be parsed for config.phundrak.com.")

Now, here is my configuration. In this snippet, my org files located in my source directory get exported in the HTML format and published to my target directory on my remote server through RSYNC via TRAMP. A sitemap is automatically generated, which comes in handy with the online sitemap that is available through the navigation bar.

("config-website-org"
 :base-directory ,phundrak//projects-config-source
 :base-extension "org"
 :publishing-directory ,phundrak//projects-config-target
 :recursive ,phundrak//projects-config-recursive
 :language ,phundrak//projects-config-language
 :publishing-function org-html-publish-to-html
 :headline-levels 5
 :auto-sitemap t
 :auto-preamble t)

We also have the component for all the static files needed to run the website (mostly images tbh).

("config-website-static"
 :base-directory ,phundrak//projects-config-source
 :base-extension "png\\|jpg\\|gif\\|webp\\|svg\\|jpeg\\|ttf\\|woff\\|txt\\|epub\\|md"
 :publishing-directory ,phundrak//projects-config-target
 :recursive ,phundrak//projects-config-recursive
 :language ,phundrak//projects-config-language
 :publishing-function org-publish-attachment)

The project is then defined like so:

("config-website"
 :components ("config-website-org"
              "config-website-static"))
Linguistics website

My linguistics website is made out of three projects. As for the previous project, lets declare the common values for these.

(defvar phundrak//projects-conlanging-target
  "/ssh:Tilo:~/www/phundrak.com/langue/"
  "Points to where exported files for langue.phundrak.com should be put")
(defvar phundrak//projects-conlanging-source
  "~/Documents/conlanging/content/"
  "Points to where the sources for langue.phundrak.com are")
(defvar phundrak//projects-conlanging-language
  "fr"
  "Language of langue.phundrak.com")
(defvar phundrak//projects-conlanging-recursive
  t
  "Defines whether subdirectories should be parsed for langue.phundrak.com")

The first component is the one generating the HTML files from the org files.

("langue-phundrak-com-org"
 :base-directory ,phundrak//projects-conlanging-source
 :base-extension "org"
 :exclude "\\./\\(CONTRIB\\|README\\|head\\|temp\\|svg-ink\\).*"
 :publishing-directory ,phundrak//projects-conlanging-target
 :recursive ,phundrak//projects-conlanging-recursive
 :language ,phundrak//projects-conlanging-language
 :publishing-function org-html-publish-to-html
 :headline-levels 5
 :auto-sitemap t
 :auto-preamble t)

We also have the component for the LaTeX and PDF part of the website:

("langue-phundrak-com-pdf"
 :base-directory ,phundrak//projects-conlanging-source
 :base-extension "org"
 :exclude "\\./\\(CONTRIB\\|README\\|index\\|head\\|temp\\|svg-ink\\).*"
 :publishing-directory ,phundrak//projects-conlanging-target
 :recursive ,phundrak//projects-conlanging-recursive
 :language ,phundrak//projects-conlanging-language
 :publishing-function org-latex-publish-to-pdf
 :headline-levels 5
 :auto-preamble t)

And lastly, we have the component for all the static files needed to run the website:

("langue-phundrak-com-static"
 :base-directory ,phundrak//projects-conlanging-source
 :base-extension "png\\|jpg\\|gif\\|webp\\|svg\\|jpeg\\|ttf\\|woff\\|txt\\|epub"
 :publishing-directory ,phundrak//projects-conlanging-target
 :recursive ,phundrak//projects-conlanging-recursive
 :language ,phundrak//projects-conlanging-language
 :publishing-function org-publish-attachment)

The project is then defined like so:

("langue-phundrak-com"
 :components ("langue-phundrak-com-org"
              "langue-phundrak-com-static"
              "langue-phundrak-com-pdf"))

Org-ref and Bibtex configuration

(use-package reftex
  :commands turn-on-reftex
  :init (setq reftex-default-bibliography "~/org/bibliography/references.bib"
              reftex-plug-into-AUCTeX     t))
(use-package org-ref
  ;; :after (org ox-bibtex pdf-tools)
  :after org
  :defer t
  :straight (:build t)
  :custom-face
  (org-ref-cite-face ((t (:weight bold))))
  :init
  (setq org-ref-completion-library    'org-ref-ivy-cite
        org-latex-logfiles-extensions '("lof" "lot" "aux" "idx" "out" "log" "fbd_latexmk"
                                        "toc" "nav" "snm" "vrb" "dvi" "blg" "brf" "bflsb"
                                        "entoc" "ps" "spl" "bbl" "pygtex" "pygstyle"))
  (add-hook 'org-mode-hook (lambda () (require 'org-ref)))
  :config
  (setq bibtex-completion-pdf-field    "file"
        bibtex-completion-notes-path   "~/org/bibliography/notes/"
        bibtex-completion-bibliography "~/org/bibliography/references.bib"
        bibtex-completion-library-path "~/org/bibliography/bibtex-pdfs/"
        bibtex-completion-pdf-symbol   "⌘"
        bibtex-completion-notes-symbol "✎")
  :general
  (phundrak/evil
   :keymaps 'bibtex-mode-map
   :packages 'org-ref
   "C-t" #'org-ref-bibtex-next-entry
   "C-s" #'org-ref-bibtex-previous-entry
   "gt"  #'org-ref-bibtex-next-entry
   "gs"  #'org-ref-bibtex-previous-entry)
  (phundrak/major-leader-key
   :keymaps '(bibtex-mode-map)
   :packages 'org-ref
   ;; Navigation
   "t" #'org-ref-bibtex-next-entry
   "s" #'org-ref-bibtex-previous-entry

   ;; Open
   "b" #'org-ref-open-in-browser
   "n" #'org-ref-open-bibtex-notes
   "p" #'org-ref-open-bibtex-pdf

   ;; Misc
   "h" #'org-ref-bibtex-hydra/body
   "i" #'org-ref-bibtex-hydra/org-ref-bibtex-new-entry/body-and-exit
   "s" #'org-ref-sort-bibtex-entry

   "l" '(:ignore t :which-key "lookup")
   "la" #'arxiv-add-bibtex-entry
   "lA" #'arxiv-get-pdf-add-bibtex-entry
   "ld" #'doi-utils-add-bibtex-entry-from-doi
   "li" #'isbn-to-bibtex
   "lp" #'pubmed-insert-bibtex-from-pmid)
  (phundrak/major-leader-key
   :keymaps 'org-mode-map
   :pakages 'org-ref
   "ic" #'org-ref-insert-link))
(use-package ivy-bibtex
  :defer t
  :straight (:build t)
  :config
  (setq bibtex-completion-pdf-open-function #'find-file)
  :general
  (phundrak/leader-key
    :keymaps '(bibtex-mode-map)
    :packages 'ivy-bibtex
    "m" #'ivy-bibtex))
(after! org
  (require 'org-ref))

Org-present

org-present allows its user to create presentations through org-mode, which is really nice! However, most of my configuration will be stolen from Daviwils with minor changes.

(defun my/org-present-prepare-slide ()
  (org-overview)
  (org-show-entry)
  (org-show-children)
  (org-present-hide-cursor))

(defun my/org-present-init ()
  (setq header-line-format " ")
  (org-display-inline-images)
  (my/org-present-prepare-slide))

(defun my/org-present-quit ()
  (setq header-line-format nil)
  (org-present-small)
  (org-present-show-cursor))

(defun my/org-present-prev ()
  (interactive)
  (org-present-prev)
  (my/org-present-prepare-slide))

(defun my/org-present-next ()
  (interactive)
  (org-present-next)
  (my/org-present-prepare-slide))

(use-package org-present
  :after org
  :defer t
  :straight (:build t)
  :general
  (phundrak/major-leader-key
    :packages 'org-present
    :keymaps 'org-mode-map
    "p" #'org-present)
  (phundrak/evil
    :states 'normal
    :packages 'org-present
    :keymaps 'org-present-mode-keymap
    "+" #'org-present-big
    "-" #'org-present-small
    "<" #'org-present-beginning
    ">" #'org-present-end
    "«" #'org-present-beginning
    "»" #'org-present-end
    "c" #'org-present-hide-cursor
    "C" #'org-present-show-cursor
    "n" #'org-present-next
    "p" #'org-present-prev
    "r" #'org-present-read-only
    "w" #'org-present-read-write
    "q" #'org-present-quit)
  :hook ((org-present-mode      . my/org-present-init)
         (org-present-mode-quit . my/org-present-quit)))

Visual Configuration

While most modes of Emacs are dedicated to development, and therefore are much more comfortable with a fixed-pitch font, more literary modes such as org-mode are much more enjoyable if you have a variable pitch font enabled. BUT, these modes can also require some fixed-pitch fonts for some elements of the buffer, such as code blocks with org-mode. mixed-pitch comes to the rescue!

(use-package mixed-pitch
  :after org
  :straight (:build t)
  :hook
  (org-mode           . mixed-pitch-mode)
  (emms-browser-mode  . mixed-pitch-mode)
  (emms-playlist-mode . mixed-pitch-mode)
  :config
  (add-hook 'org-agenda-mode-hook (lambda () (mixed-pitch-mode -1))))

With this, I also use org-pretty-tables in order to use some capabilities of Unicode in order to make tables nicer to look at.

(use-package org-pretty-table
  :defer t
  :after org
  :straight (org-pretty-table :type git
                              :host github
                              :repo "Fuco1/org-pretty-table"
                              :build t)
  :hook (org-mode . org-pretty-table-mode)
  :commands (org-pretty-table-mode global-org-pretty-table-mode))

I have an issue with org-modes emphasis markers: I find them ugly. I can of course hide them if I simply set org-hide-emphasis-markers to t, but it makes editing hard since I never know whether I am before or after the emphasis marker when editing near the beginning/end of an emphasized region. org-appear fixes this issue so that it shows the emphasis markers only when the cursor is in the emphasized region, otherwise they will remain hidden! Very cool!

(use-package org-appear
  :after org
  :straight (:build t)
  :hook (org-mode . org-appear-mode)
  :config
  (setq org-appear-autoemphasis   t
        org-hide-emphasis-markers t
        org-appear-autolinks      t
        org-appear-autoentities   t
        org-appear-autosubmarkers t)
  (run-at-time nil nil #'org-appear--set-elements))

Similarly, LaTeX fragments previews are nice and all, but if I have my cursor on it, I want to see the LaTeX source code and modify it, not just the generated image!

(use-package org-fragtog
  :defer t
  :after org
  :straight (:build t)
  :hook (org-mode . org-fragtog-mode))

Tired of seeing lots of actual stars * in your headers, and you want a fancier remplacement? Or you are still using org-bullets but just found out how out-of-date and abandoned it is? (Last commit was on September 18th, 2014… damn…) Search no more, org-superstar will take care of that for you!

(use-package org-superstar
  :after org
  :straight (:build t)
  :hook (org-mode . org-superstar-mode)
  :config
  (setq org-superstar-leading-bullet   ?\s
        org-superstar-leading-fallback ?\s
        org-hide-leading-stars         nil
        org-superstar-todo-bullet-alist
        '(("TODO" . 9744)
          ("[ ]"  . 9744)
          ("DONE" . 9745)
          ("[X]"  . 9745))))

org-fancy-priorities change the priority of an org element such such as #A to anything user-defined. Lets make this anything all-the-icons icons!

(use-package org-fancy-priorities
  :after (org all-the-icons)
  :straight (:build t)
  :hook (org-mode        . org-fancy-priorities-mode)
  :hook (org-agenda-mode . org-fancy-priorities-mode)
  :config
  (setq org-fancy-priorities-list `(,(all-the-icons-faicon "flag"     :height 1.1 :v-adjust 0.0)
                                    ,(all-the-icons-faicon "arrow-up" :height 1.1 :v-adjust 0.0)
                                    ,(all-the-icons-faicon "square"   :height 1.1 :v-adjust 0.0))))

Org Outline Tree is a better way of managing my org files outline.

(use-package org-ol-tree
  :after (org avy)
  :defer t
  :straight (org-ol-tree :build t
                         :host github
                         :type git
                         :repo "Townk/org-ol-tree")
  :general
  (phundrak/major-leader-key
    :packages 'org-ol-tree
    :keymaps 'org-mode-map
    "O" #'org-ol-tree))
(add-hook 'org-mode-hook
          (lambda ()
            (dolist (pair '(("[ ]"         . ?☐)
                            ("[X]"         . ?☑)
                            ("[-]"         . ?❍)
                            ("#+title:"    . ?📕)
                            ("#+TITLE:"    . ?📕)
                            ("#+author:"   . ?✎)
                            ("#+AUTHOR:"   . ?✎)
                            ("#+email:"    . ?📧)
                            ("#+EMAIL:"    . ?📧)
                            ("#+include"   . ?⭳)
                            ("#+INCLUDE"   . ?⭳)
                            ("#+begin_src" . )
                            ("#+BEGIN_SRC" . )
                            ("#+end_src"   . )
                            ("#+END_SRC"   . )))
              (add-to-list 'prettify-symbols-alist pair))
            (prettify-symbols-mode)))

Misc

org-tree-slide is a presentation tool for org-mode.

(use-package org-tree-slide
  :defer t
  :after org
  :straight (:build t)
  :config
  (setq org-tree-slide-skip-done nil)
  :general
  (phundrak/evil
    :keymaps 'org-mode-map
    :packages 'org-tree-slide
    "<f8>" #'org-tree-slide-mode)
  (phundrak/major-leader-key
    :keymaps 'org-tree-slide-mode-map
    :packages 'org-tree-slide
    "d" (lambda () (interactive (setq org-tree-slide-skip-done (not org-tree-slide-skip-done))))
    "p" #'org-tree-slide-move-next-tree
    "n" #'org-tree-slide-move-previous-tree
    "t" #'org-tree-slide-move-next-tree
    "s" #'org-tree-slide-move-previous-tree
    "u" #'org-tree-slide-content))

Programming languages

Tools

Flycheck
(use-package flycheck
  :straight (:build t)
  :defer t
  :init
  (global-flycheck-mode)
  :config
  (setq flycheck-emacs-lisp-load-path 'inherit)

  ;; Rerunning checks on every newline is a mote excessive.
  (delq 'new-line flycheck-check-syntax-automatically)
  ;; And dont recheck on idle as often
  (setq flycheck-idle-change-delay 2.0)

  ;; For the above functionality, check syntax in a buffer that you
  ;; switched to on briefly. This allows “refreshing” the syntax check
  ;; state for several buffers quickly after e.g. changing a config
  ;; file.
  (setq flycheck-buffer-switch-check-intermediate-buffers t)

  ;; Display errors a little quicker (default is 0.9s)
  (setq flycheck-display-errors-delay 0.2))

(use-package flycheck-popup-tip
  :straight (:build t)
  :after flycheck
  :hook (flycheck-mode . flycheck-popup-tip-mode)
  :config
  (setq flycheck-popup-tip-error-prefix "X ")
  (after! evil
    (add-hook 'evil-insert-state-entry-hook
              #'flycheck-popup-tip-delete-popup)
    (add-hook 'evil-replace-state-entry-hook
              #'flycheck-popup-tip-delete-popup)))

(use-package flycheck-posframe
  :straight (:build t)
  :hook (flycheck-mode . flycheck-posframe-mode)
  :config
  (setq flycheck-posframe-warning-prefix "! "
        flycheck-posframe-info-prefix    "··· "
        flycheck-posframe-error-prefix   "X "))
Langtool
(use-package langtool
  :defer t
  :straight (:build t)
  :commands (langtool-check
             langtool-check-done
             langtool-show-message-at-point
             langtool-correct-buffer)
  :init
  (setq langtool-default-language "en-US")
  :config
  (setq langtool-java-classpath "/usr/share/languagetool:/usr/share/java/languagetool/*"))
(use-package writegood-mode
  :defer t
  :straight (:build t)
  :hook org-mode latex-mode
  :general
  (phundrak/major-leader-key
    :keymaps 'writegood-mode-map
    "g" #'writegood-grade-level
    "r" #'writegood-reading-ease))
Spellcheck
(use-package ispell
  :defer t
  :straight (:type built-in)
  :config
  (add-to-list 'ispell-skip-region-alist '(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:"))
  (add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_SRC" . "#\\+END_SRC"))
  (add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
  (setq ispell-program-name "aspell"
        ispell-extra-args   '("--sug-mode=ultra" "--run-together")
        ispell-aspell-dict-dir (ispell-get-aspell-config-value "dict-dir")
        ispell-aspell-data-dir (ispell-get-aspell-config-value "data-dir")
        ispell-personal-dictionary (expand-file-name (concat "ispell/" ispell-dictionary ".pws")
                                                     user-emacs-directory)))
(use-package flyspell
  :defer t
  :straight (:type built-in)
  :ghook 'org-mode 'markdown-mode 'TeX-mode
  :init
  (defhydra flyspell-hydra ()
    "
Spell Commands^^           Add To Dictionary^^              Other
--------------^^---------- -----------------^^------------- -----^^---------------------------
[_b_] check whole buffer   [_B_] add word to dict (buffer)  [_t_] toggle spell check
[_r_] check region         [_G_] add word to dict (global)  [_q_] exit
[_d_] change dictionary    [_S_] add word to dict (session) [_Q_] exit and disable spell check
[_n_] next error
[_c_] correct before point
[_s_] correct at point"
    ("B" nil)
    ("b" flyspell-buffer)
    ("r" flyspell-region)
    ("d" nil)
    ("G" nil)
    ("n" flyspell-goto-next-error)
    ("c" flyspell-correct-wrapper)
    ("Q" flyspell-mode :exit t)
    ("q" nil :exit t)
    ("S" nil)
    ("s" flyspell-correct-at-point)
    ("t" nil))
  :config
  (provide 'ispell) ;; force loading ispell
  (setq flyspell-issue-welcome-flag nil
        flyspell-issue-message-flag nil))
(use-package flyspell-correct
  :defer t
  :straight (:build t)
  :general ([remap ispell-word] #'flyspell-correct-at-point)
  :config
  (require 'flyspell-correct-ivy nil t))

(use-package flyspell-correct-ivy
  :defer t
  :straight (:build t)
  :after flyspell-correct)
(use-package flyspell-lazy
  :defer t
  :straight (:build t)
  :after flyspell
  :config
  (setq flyspell-lazy-idle-seconds 1
        flyspell-lazy-window-idle-seconds 3)
  (flyspell-lazy-mode +1))
LSP-Mode

lsp-mode is a mode for Emacs which implements the Language Server Protocol and offers Emacs an IDE-like experience. In short, its awesome!

(use-package lsp-mode
  :defer t
  :straight (:build t)
  :init
  (setq lsp-keymap-prefix "C-c l")
  :hook ((c-mode          . lsp-deferred)
         (c++-mode        . lsp-deferred)
         (html-mode       . lsp-deferred)
         (lsp-mode        . lsp-enable-which-key-integration)
         (lsp-mode        . lsp-ui-mode))
  :commands (lsp lsp-deferred)
  :custom
  (lsp-rust-analyzer-cargo-watch-command "clippy")
  (lsp-eldoc-render-all t)
  (lsp-idle-delay 0.6)
  (lsp-rust-analyzer-server-display-inlay-hints t))

I also want all the visual enhancements LSP can provide.

(use-package lsp-ui
  :after lsp
  :defer t
  :straight (:build t)
  :commands lsp-ui-mode
  :custom
  (lsp-ui-peek-always-show t)
  (lsp-ui-sideline-show-hover t)
  (lsp-ui-doc-enable t)
  :general
  (phundrak/major-leader-key
    :keymaps 'lsp-ui-peek-mode-map
    :packages 'lsp-ui
    "c" #'lsp-ui-pook--select-prev-file
    "t" #'lsp-ui-pook--select-next
    "s" #'lsp-ui-pook--select-prev
    "r" #'lsp-ui-pook--select-next-file))

And lets enable some intergration with ivy.

(use-package lsp-ivy
  :straight (:build t)
  :defer t
  :after lsp
  :commands lsp-ivy-workspace-symbol)
(use-package lsp-treemacs
  :defer t
  :straight (:build t)
  :requires treemacs)
Key Function Description
treemacs
c create
cd treemacs-create-dir
cf treemacs-create-file
ci treemacs-create-icon
ct treemacs-create-theme
cw treemacs-create-workspace
d treemacs-delete-file
f files
ff treemacs-find-file
ft treemacs-find-tag
l lsp
ls treemacs-expand-lsp-symbol
ld treemacs-expand-lsp-treemacs-deps
lD treemacs-collapse-lsp-treemacs-deps
lS treemacs-collapse-lsp-symbol
p projects
pa treemacs-add-project
pf treemacs-project-follow-mode
pn treemacs-project-of-node
pp treemacs-project-at-point
pr treemacs-remove-project-from-workspace
pt treemacs-move-project-down
ps treemacs-move-project-up
r rename
rf treemacs-rename-file
rp treemacs-rename-project
rr treemacs-rename
rw treemacs-rename-workspace
t treemacs
T toggles
Td treemacs-toggle-show-dotfiles
Tn treemacs-toggle-node
v visit node
va treemacs-visit-node-ace
vc treemacs-visit-node-close-treemacs
vn treemacs-visit-node-default
y yank
ya treemacs-copy-absolute-path-at-point
yp treemacs-copy-project-path-at-point
yr treemacs-copy-relative-path-at-point
yf treemacs-copy-file
(use-package exec-path-from-shell
  :defer t
  :straight (:build t)
  :init (exec-path-from-shell-initialize))
(use-package consult-lsp
  :defer t
  :after lsp
  :straight (:build t)
  :general
  (phundrak/evil
    :keymaps 'lsp-mode-map
    [remap xref-find-apropos] #'consult-lsp-symbols))

dap-mode is an advanced debugging mode that works through lsp.

(use-package dap-mode
  :after lsp
  :defer t
  :straight (:build t)
  :config
  (dap-ui-mode)
  (dap-ui-controls-mode 1)

  (require 'dap-lldb)
  (require 'dap-gdb-lldb)

  (dap-gdb-lldb-setup)
  (dap-register-debug-template
   "Rust::LLDB Run Configuration"
   (list :type "lldb"
         :request "launch"
         :name "LLDB::Run"
         :gdbpath "rust-lldb"
         :target nil
         :cwd nil)))

DSLs

DSLs, or Domain Specific Languages, are languages dedicated to some very tasks, such as configuration languages or non-general programming such as SQL.

Caddy

Caddy (or Caddyserver) is a web server akin to Nginx or Apache which I find much easier to configure that the latter two, plus it has built-in support for automatically generating SSL certificates with Letsencrypt! Automatic HTTPS, what more do you want?

All that is nice and all, but Emacs doesnt support the syntax of Caddy files natively, so lets install caddyfile-mode:

(use-package caddyfile-mode
  :defer t
  :straight (:build t)
  :mode (("Caddyfile\\'" . caddyfile-mode)
         ("caddy\\.conf\\'" . caddyfile-mode)))
CMake

CMake is one of the standard tools for indicating how a project should be built. It is not as standard as some other tools such as automake, autoconfig, and the likes, but still pretty standard. CMake however doesnt have a major mode available by default, so lets provide one.

(use-package cmake-mode
  :defer t
  :straight (:build t))

Lets enable first some autocompletion for it.

(use-package company-cmake
  :straight (company-cmake :build t
                           :type git
                           :host github
                           :repo "purcell/company-cmake")
  :after cmake-mode
  :defer t)

And lets also enable a more advanced CMake fontlock.

(use-package cmake-font-lock
  :defer t
  :after cmake-mode
  :straight (:build t))

And finally, lets enable some Eldoc integration for CMake.

(use-package eldoc-cmake
  :straight (:build t)
  :defer t
  :after cmake-mode)
CSV
(use-package csv-mode
  :straight (:build t)
  :defer t
  :general
  (phundrak/major-leader-key
    :keymaps 'csv-mode-map
    "a"  #'csv-align-fields
    "d"  #'csv-kill-fields
    "h"  #'csv-header-line
    "i"  #'csv-toggle-invisibility
    "n"  #'csv-forward-field
    "p"  #'csv-backward-field
    "r"  #'csv-reverse-region
    "s"  '(:ignore t :wk "sort")
    "sf" #'csv-sort-fields
    "sn" #'csv-sort-numeric-fields
    "so" #'csv-toggle-descending
    "t"  #'csv-transpose
    "u"  #'csv-unalign-fields
    "y"  '(:ignore t :wk yank)
    "yf" #'csv-yank-fields
    "yt" #'csv-yank-as-new-table))
Gnuplot

This package is a front-end and major mode for the programming language Gnuplot. Lets make some beautiful graphs, shall we?

(use-package gnuplot
  :straight (:build t)
  :defer t)
Graphviz

Graphviz, often known with dot, allows to programatically create visual graphs and networks.

(use-package graphviz-dot-mode
  :defer t
  :straight (:build t)
  :after ob
  :mode (("\\.diag\\'"      . graphviz-dot-mode)
         ("\\.blockdiag\\'" . graphviz-dot-mode)
         ("\\.nwdiag\\'"    . graphviz-dot-mode)
         ("\\.rackdiag\\'"  . graphviz-dot-mode)
         ("\\.dot\\'"       . graphviz-dot-mode)
         ("\\.gv\\'"        . graphviz-dot-mode))
  :init
  (setq graphviz-dot-indent-width tab-width)
  (after! ob
      (defalias 'org-babel-execute:graphviz-dot #'org-babel-execute:dot)
      (add-to-list 'org-babel-load-languages '(dot . t))
      (require 'ob-dot)
      (setq org-src-lang-modes
            (append '(("dot" . graphviz-dot))
                    (delete '("dot" . fundamental) org-src-lang-modes))))

  :general
  (phundrak/major-leader-key
    :keymaps 'graphviz-dot-mode-map
    "=" #'graphviz-dot-indent-graph
    "c" #'compile)
  :config
  (setq graphviz-dot-indent-width 4))
Markdown

Yes, I love org-mode and I largely prefer to use it instead of Markdown due to its far superior power and abilities. But still, sometimes I need to use Markdown because not everyone use org-mode, unfortunately.

(use-package markdown-mode
  :defer t
  :straight t
  :mode
  (("\\.mkd\\'" . markdown-mode)
   ("\\.mdk\\'" . markdown-mode)
   ("\\.mdx\\'" . markdown-mode))
  :hook (markdown-mode . orgtbl-mode)
  :general
  (phundrak/evil
    :keymaps 'markdown-mode-map
    :packages 'markdown-mode
    "M-RET" #'markdown-insert-list-item
    "M-c"   #'markdown-promote
    "M-t"   #'markdown-move-down
    "M-s"   #'markdown-move-up
    "M-r"   #'markdown-demote)
  (phundrak/major-leader-key
    :keymaps 'markdown-mode-map
    :packages 'markdown-mode
    "{"   #'markdown-backward-paragraph
    "}"   #'markdown-forward-paragraph
    "]"   #'markdown-complete
    ">"   #'markdown-indent-region
    "»"   #'markdown-indent-region
    "<"   #'markdown-outdent-region
    "«"   #'markdown-outdent-region
    "n"   #'markdown-next-link
    "p"   #'markdown-previous-link
    "f"   #'markdown-follow-thing-at-point
    "k"   #'markdown-kill-thing-at-point
    "c"   '(:ignore t :which-key "command")
    "c]"  #'markdown-complete-buffer
    "cc"  #'markdown-check-refs
    "ce"  #'markdown-export
    "cm"  #'markdown-other-window
    "cn"  #'markdown-cleanup-list-numbers
    "co"  #'markdown-open
    "cp"  #'markdown-preview
    "cv"  #'markdown-export-and-preview
    "cw"  #'markdown-kill-ring-save
    "h"   '(:ignore t :which-key "headings")
    "hi"  #'markdown-insert-header-dwim
    "hI"  #'markdown-insert-header-setext-dwim
    "h1"  #'markdown-insert-header-atx-1
    "h2"  #'markdown-insert-header-atx-2
    "h3"  #'markdown-insert-header-atx-3
    "h4"  #'markdown-insert-header-atx-4
    "h5"  #'markdown-insert-header-atx-5
    "h6"  #'markdown-insert-header-atx-6
    "h!"  #'markdown-insert-header-setext-1
    "h@"  #'markdown-insert-header-setext-2
    "i"   '(:ignore t :which-key "insert")
    "i-"  #'markdown-insert-hr
    "if"  #'markdown-insert-footnote
    "ii"  #'markdown-insert-image
    "il"  #'markdown-insert-link
    "it"  #'markdown-insert-table
    "iw"  #'markdown-insert-wiki-link
    "l"   '(:ignore t :which-key "lists")
    "li"  #'markdown-insert-list-item
    "T"   '(:ignore t :which-key "toggle")
    "Ti"  #'markdown-toggle-inline-images
    "Tu"  #'markdown-toggle-url-hiding
    "Tm"  #'markdown-toggle-markup-hiding
    "Tt"  #'markdown-toggle-gfm-checkbox
    "Tw"  #'markdown-toggle-wiki-links
    "t"   '(:ignore t :which-key "table")
    "tc"  #'markdown-table-move-column-left
    "tt"  #'markdown-table-move-row-down
    "ts"  #'markdown-table-move-row-up
    "tr"  #'markdown-table-move-column-right
    "ts"  #'markdown-table-sort-lines
    "tC"  #'markdown-table-convert-region
    "tt"  #'markdown-table-transpose
    "td"  '(:ignore t :which-key "delete")
    "tdc" #'markdown-table-delete-column
    "tdr" #'markdown-table-delete-row
    "ti"  '(:ignore t :which-key "insert")
    "tic" #'markdown-table-insert-column
    "tir" #'markdown-table-insert-row
    "x"   '(:ignore t :which-key "text")
    "xb"  #'markdown-insert-bold
    "xB"  #'markdown-insert-gfm-checkbox
    "xc"  #'markdown-insert-code
    "xC"  #'markdown-insert-gfm-code-block
    "xi"  #'markdown-insert-italic
    "xk"  #'markdown-insert-kbd
    "xp"  #'markdown-insert-pre
    "xP"  #'markdown-pre-region
    "xs"  #'markdown-insert-strike-through
    "xq"  #'markdown-blockquote-region)
  :config
  (setq markdown-fontify-code-blocks-natively t))

Since most of my Mardown files are related to Github, Id like to be able to render Markdown through its API.

(use-package gh-md
  :defer t
  :after markdown-mode
  :straight (:build t)
  :general
  (phundrak/major-leader-key
    :packages 'gh-md
    :keymaps 'markdown-mode-map
    "cr" #'gh-md-render-buffer))

Tables of content are always nice to have for large files, just like with the toc-org package for org-mode.

(use-package markdown-toc
  :defer t
  :after markdown-mode
  :straight (:build t)
  :general
  (phundrak/major-leader-key
    :packages 'markdown-toc
    :keymaps 'markdown-mode-map
    "iT" #'markdown-toc-generate-toc))

vmd-mode allows the user to live-preview their Github-flavored Markdown file quickly. Thats neat!

(use-package vmd-mode
  :defer t
  :after markdown-mode
  :straight (:build t)
  :custom ((vmd-binary-path (executable-find "vmd")))
  :general
  (phundrak/major-leader-key
    :packages 'vmd-mode
    :keymaps 'markdown-mode-map
    "cP" #'vmd-mode))
Nginx

Nginx is another webserver, older and more mature than Caddy. A couple of packages are required in order to be able to properly work with Nginx configuration files. First, we need the correct mode for editing Nginx configuration files.

(use-package nginx-mode
  :straight (:build t)
  :defer t)

We then also have an autocompletion package that adds to company the Nginx syntax.

(use-package company-nginx
  :straight (:build t)
  :defer t
  :config
  (add-hook 'nginx-mode-hook (lambda ()
                               (add-to-list 'company-backends #'company-nginx))))
PKGBUILD

As I am an ArchLinux user, I sometimes have to interact with PKGBUILD files, both from the AUR when I want to install something from there or some I write myself.

(use-package pkgbuild-mode
  :straight (:build t)
  :defer t
  :general
  (phundrak/major-leader-key
    :keymaps 'pkgbuild-mode-map
    "c"  #'pkgbuild-syntax-check
    "i"  #'pkgbuild-initialize
    "I"  #'pkgbuild-increase-release-tag
    "m"  #'pkgbuild-makepkg
    "u"  '(:ignore :wk "update")
    "us" #'pkgbuild-update-sums-line
    "uS" #'pkgbuild-update-srcinfo))
PlantUML
(use-package plantuml-mode
  :defer t
  :straight (:build t)
  :mode ("\\.\\(pum\\|puml\\)\\'" . plantuml-mode)
  :after ob
  :init
  (add-to-list 'org-babel-load-languages '(plantuml . t))
  :general
  (phundrak/major-leader-key
   :keymaps 'plantuml-mode-map
   :packages 'plantuml-mode
   "c"  '(:ignore t :which-key "compile")
   "cc" #'plantuml-preview
   "co" #'plantuml-set-output-type)
  :config
  (setq plantuml-default-exec-mode 'jar
        plantuml-jar-path "~/.local/bin/plantuml.jar"
        org-plantuml-jar-path "~/.local/bin/plantuml.jar"))
Shells

Aside from Eshell, my main shell on my machine is fish (see my fish config), therefore I need a mode for it.

(use-package fish-mode
  :straight (:build t)
  :defer t)
SSH Config files
(use-package ssh-config-mode
  :defer t
  :straight (:build t))
Toml
(use-package toml-mode
  :straight (:build t)
  :defer t
  :mode "/\\(Cargo.lock\\|\\.cargo/config\\)\\'")
Yaml
(use-package yaml-mode
  :defer t
  :straight (:build t)
  :mode "\\.yml\\'"
  :mode "\\.yaml\\'")

General Programming Languages

C/C++

I know, I know, C and C++ no longer are closely related languages, each one of them went their own way and learning C wont make you a good C++ programmer, neither will the other way around. But, They are still somewhat related, and Emacs thinks so too.

(use-package cc-mode
  :straight (:type built-in)
  :defer t
  :init
  (put 'c-c++-backend 'safe-local-variable 'symbolp)
  :config
  (require 'compile)
  :general
  (phundrak/undefine
    :keymaps '(c-mode-map c++-mode-map)
    ";" nil)
  (phundrak/major-leader-key
   :keymaps '(c-mode-map c++-mode-map)
   "l"  '(:keymap lsp-command-map :which-key "lsp" :package lsp-mode))
  (phundrak/evil
   :keymaps '(c-mode-map c++-mode-map)
   "ga" #'projectile-find-other-file
   "gA" #'projectile-find-other-file-other-window))

Something that is also important when working with these languages is respecting the .clang-format file that may be provided by a project.

(use-package clang-format+
  :straight (:build t)
  :defer t
  :init
  (add-hook 'c-mode-common-hook #'clang-format+-mode))

However, Emacs notion of C++ is somewhat outdated, so we need to update its fontlock.

(use-package modern-cpp-font-lock
  :straight (:build t)
  :defer t
  :hook (c++-mode . modern-c++-font-lock-mode))
CommonLisp

In lisp buffers, lets enable parinfer-rust-mode.

(add-hook 'lisp-mode-hook #'parinfer-rust-mode)

My current window manager is StumpWM, inspired by Emacs and written in CommonLisp. stumpwm-mode offers some integration between Emacs and StumpWM so we can evaluate CLisp code and see its effects in StumpWM immediately. Since my only use for CommonLisp is for my StumpWM configuration, it should be automatically enabled when entering lisp-mode.

(use-package stumpwm-mode
  :straight (:build t)
  :defer t
  :hook lisp-mode
  :config
  (phundrak/major-leader-key
   :keymaps 'stumpwm-mode-map
   :packages 'stumpwm-mode
   "e"  '(:ignore t :which-key "eval")
   "ee" #'stumpwm-eval-last-sexp
   "ed" #'stumpwm-eval-defun
   "er" #'stumpwm-eval-region))

Sly enables some deep interactivity between Emacs and a CommonLisp application running the Slynk backend. For an example, see my Sly configuration for StumpWM.

(use-package sly
  :defer t
  :straight (:build t))
Dart
(use-package dart-mode
  :straight (:build t)
  :defer t
  :hook (dart-mode . lsp-deferred)
  :mode "\\.dart\\'")
(use-package lsp-dart
  :straight (:build t)
  :defer t)
EmacsLisp

This package displays the functions arglist or variables docstring in the echo area at the bottom of the frame. Quite useful indeed.

(use-package eldoc
  :defer t
  :after company
  :init
  (eldoc-add-command 'company-complete-selection
                     'company-complete-common
                     'company-capf
                     'company-abort))
(add-hook 'emacs-lisp-mode-hook (lambda () (smartparens-mode -1)))

Lets also declare some Elisp-dedicated keybindings, prefixed by a comma.

(phundrak/major-leader-key
 :keymaps 'emacs-lisp-mode-map
 "'"   #'ielm
 "c"   '(emacs-lisp-byte-compile :which-key "Byte compile")
 "C"   '(:ignore t :which-key "checkdoc")
 "Cc"  #'checkdoc
 "Cs"  #'checkdoc-start
 "e"   '(:ignore t :which-key "eval")
 "eb"  #'eval-buffer
 "ed"  #'eval-defun
 "ee"  #'eval-last-sexp
 "er"  #'eval-region

 "h"   '(:ignore t :which-key "help")
 "hh"  #'helpful-at-point

 "t"   '(:ignore t :wk "toggle")
 "tP"  '(:ignore t :wk "parinfer")
 "tPs" #'parinfer-rust-switch-mode
 "tPd" #'parinfer-rust-mode-disable
 "tPp" #'parinfer-rust-toggle-paren-mode)
(use-package package-lint
  :defer t
  :straight (:build t)
  :general
  (phundrak/major-leader-key
    :keymaps 'emacs-lisp-mode-map
    :packages 'package-lint
    "l" #'package-lint-current-buffer))
Lua
(use-package lua-mode
  :defer t
  :straight (:build t)
  :mode "\\.lua'$"
  :interpreter "lua"
  :hook (lua-mode . lsp-deferred)
  :init
  (setq lua-indent-level           2
        lua-indent-string-contents t)
  :general
  (phundrak/major-leader-key
   :keymaps 'lua-mode-map
   "'"  #'lua-show-process-buffer
   "h"  '(:ignore t :wk "help")
   "hd" #'lua-search-documentation
   "s"  '(:ignore t :wk "REPL")
   "sb" #'lua-send-buffer
   "sf" #'lua-send-defun
   "sl" #'lua-send-current-line
   "sr" #'lua-send-region))
(use-package lsp-lua-emmy
  :defer t
  :after (lua-mode lsp-mode)
  :straight (lsp-lua-emmy :build t
                          :type git
                          :host github
                          :repo "EmmyLua/EmmyLua-LanguageServer")
  :hook (lua-mode . lsp-deferred)
  :config
  (setq lsp-lua-emmy-jar-path (expand-file-name "EmmyLua-LS-all.jar" user-emacs-directory)))
Python

First, we need to set up the main Python mode. With this, well also add Python to the list of LSP languages and to the list of languages org-babel supports.

(use-package python
  :defer t
  :straight (:build t)
  :after ob
  :mode (("SConstruct\\'" . python-mode)
         ("SConscript\\'" . python-mode)
         ("[./]flake8\\'" . conf-mode)
         ("/Pipfile\\'"   . conf-mode))
  :init
  (setq python-indent-guess-indent-offset-verbose nil)
  (add-hook 'python-mode-local-vars-hook #'lsp)
  (add-to-list 'org-babel-load-languages '(python . t))
  :config
  (setq python-indent-guess-indent-offset-verbose nil)
  (when (and (executable-find "python3")
           (string= python-shell-interpreter "python"))
    (setq python-shell-interpreter "python3")))

Now lets add a package for pytest.

(use-package pytest
  :defer t
  :straight (:build t)
  :commands (pytest-one
             pytest-pdb-one
             pytest-all
             pytest-pdb-all
             pytest-last-failed
             pytest-pdb-last-failed
             pytest-module
             pytest-pdb-module)
  :config
  (add-to-list 'pytest-project-root-files "setup.cfg")
  :general
  (phundrak/major-leader-key
   :keymaps 'python-mode-map
   :infix "t"
   :packages 'pytest
   ""  '(:ignore t :which-key "test")
   "a" #'python-pytest
   "f" #'python-pytest-file-dwim
   "F" #'python-pytest-file
   "t" #'python-pytest-function-dwim
   "T" #'python-pytest-function
   "r" #'python-pytest-repeat
   "p" #'python-pytest-dispatch))

Poetry is a nice tool with which we can manage our Python runtime version as well as our dependencies.

(use-package poetry
  :defer t
  :straight (:build t)
  :commands (poetry-venv-toggle
             poetry-tracking-mode)
  :config
  (setq poetry-tracking-strategy 'switch-buffer)
  (add-hook 'python-mode-hook #'poetry-tracking-mode))

This package will bring a new major mode for editing pip requirements.

(use-package pip-requirements
  :defer t
  :straight (:build t))

Why use the command line to interact with pip when we can do it with an Emacs frontend?

(use-package pippel
  :defer t
  :straight (:build t)
  :general
  (phundrak/major-leader-key
   :keymaps 'python-mode-map
   :packages 'pippel
   "P" #'pippel-list-packages))

This is a pipenv porcelain

(use-package pipenv
  :defer t
  :straight (:build t)
  :commands (pipenv-activate
             pipenv-deactivate
             pipenv-shell
             pipenv-open
             pipenv-install
             pipenv-uninstall)
  :hook (python-mode . pipenv-mode)
  :init (setq pipenv-with-projectile nil)
  :general
  (phundrak/major-leader-key
   :keymaps 'python-mode-map
   :packages 'pipenv
   :infix "e"
   ""  '(:ignore t :which-key "pipenv")
   "a" #'pipenv-activate
   "d" #'pipenv-deactivate
   "i" #'pipenv-install
   "l" #'pipenv-lock
   "o" #'pipenv-open
   "r" #'pipenv-run
   "s" #'pipenv-shell
   "u" #'pipenv-uninstall))

This integrates pyenv into python-mode.

(use-package pyenv
  :defer t
  :straight (:build t)
  :config
  (add-hook 'python-mode-hook #'pyenv-track-virtualenv)
  (add-to-list 'global-mode-string
               '(pyenv-virtual-env-name (" venv:" pyenv-virtual-env-name " "))
               'append))

Lets also add a mode for pyenv:

(use-package pyenv-mode
  :defer t
  :after python
  :straight (:build t)
  :if (executable-find "pyenv")
  :commands (pyenv-mode-versions)
  :general
  (phundrak/major-leader-key
    :packages 'pyenv-mode
    :keymaps 'python-mode-map
    :infix "v"
    "u" #'pyenv-mode-unset
    "s" #'pyenv-mode-set))

This package automatically imports packages we forgot to import.

(use-package pyimport
  :defer t
  :straight (:build t)
  :general
  (phundrak/major-leader-key
    :packages 'pyimport
    :keymaps 'python-mode-map
    :infix "i"
    ""  '(:ignore t :which-key "imports")
    "i" #'pyimport-insert-missing
    "r" #'pyimport-remove-unused))

On the other hand, this one sorts our imports to make them more readable.

(use-package py-isort
  :defer t
  :straight (:build t)
  :general
  (phundrak/major-leader-key
   :keymaps 'python-mode-map
   :packages 'py-isort
   :infix "i"
   ""  '(:ignore t :which-key "imports")
   "s" #'py-isort-buffer
   "R" #'py-isort-region))

Access pydoc through counsel.

(use-package counsel-pydoc
  :defer t
  :straight (:build t))

This generates Python documentation that is meant to be compatible with Sphinx, a documentation generaton for Python.

(use-package sphinx-doc
  :defer t
  :straight (:build t)
  :init
  (add-hook 'python-mode-hook #'sphinx-doc-mode)
  :general
  (phundrak/major-leader-key
   :keymaps 'python-mode-map
   :packages 'sphinx-doc
   :infix "S"
   ""  '(:ignore t :which-key "sphinx-doc")
   "e" #'sphinx-doc-mode
   "d" #'sphinx-doc))

Cython is a Python to C compiler. It also introduces the extended Cython programming language which makes writing C for Python easier. This package is a major mode for the Cython programming language.

(use-package cython-mode
  :defer t
  :straight (:build t)
  :mode "\\.p\\(yx\\|x[di]\\)\\'"
  :config
  (setq cython-default-compile-format "cython -a %s")
  :general
  (phundrak/major-leader-key
   :keymaps 'cython-mode-map
   :packages 'cython-mode
   :infix "c"
   ""  '(:ignore t :which-key "cython")
   "c" #'cython-compile))

Flycheck can also be enabled for Cython:

(use-package flycheck-cython
  :defer t
  :straight (:build t)
  :after cython-mode)

Blacken uses the black formatter backend to format Python buffers.

(use-package blacken
  :defer t
  :straight (:build t)
  :init
  (add-hook 'python-mode-hook #'blacken-mode))

Finally, Im using Pyright as my LSP backend for Python.

(use-package lsp-pyright
  :after lsp-mode
  :defer t
  :straight (:buidl t))
Rust

Rust is a general programming language, akin to C++ in some ways, but much more oriented towards safe code, and much better suited for web development. First, lets install the most important package, rustic.

(use-package rustic
  :defer t
  :straight (:build t)
  :mode ("\\.rs\\'" . rustic-mode)
  :hook (rustic-mode-local-vars . rustic-setup-lsp)
  :hook (rustic-mode . lsp-deferred)
  :init
  (after! org-src
    (defalias 'org-babel-execute:rust #'org-babel-execute:rustic)
    (add-to-list 'org-src-lang-modes '("rust" . rustic)))
  (setq rustic-lsp-client 'lsp-mode)
  :general
  (general-define-key
   :keymaps 'rustic-mode-map
   :packages 'lsp
   "M-t" #'lsp-ui-imenu
   "M-?" #'lsp-find-references)
  (phundrak/major-leader-key
   :keymaps 'rustic-mode-map
   :packages 'rustic
   "b"  '(:ignore t :which-key "build")
   "bb" #'rustic-cargo-build
   "bB" #'rustic-cargo-bench
   "bc" #'rustic-cargo-check
   "bC" #'rustic-cargo-clippy
   "bd" #'rustic-cargo-doc
   "bf" #'rustic-cargo-fmt
   "bn" #'rustic-cargo-new
   "bo" #'rustic-cargo-outdated
   "br" #'rustic-cargo-run
   "l"  '(:ignore t :which-key "lsp")
   "la" #'lsp-execute-code-action
   "lr" #'lsp-rename
   "lq" #'lsp-workspace-restart
   "lQ" #'lsp-workspace-shutdown
   "ls" #'lsp-rust-analyzer-status
   "t"  '(:ignore t :which-key "cargo test")
   "ta" #'rustic-cargo-test
   "tt" #'rustic-cargo-current-test)
  :config
  (setq rustic-indent-method-chain    t
        rustic-babel-format-src-block nil
        rustic-format-trigger         nil)
  (remove-hook 'rustic-mode-hook #'flycheck-mode)
  (remove-hook 'rustic-mode-hook #'flymake-mode-off)
  (remove-hook 'rustic-mode-hook #'rustic-setup-lsp))
Web programming

Emmet is a powerful templating engine that can generate through simple CSS-like expression some HTML so you dont have to write everything by hand.

(use-package emmet-mode
  :straight (:build t)
  :defer t
  :hook ((css-mode  . emmet-mode)
         (html-mode . emmet-mode)
         (web-mode  . emmet-mode)
         (sass-mode . emmet-mode)
         (scss-mode . emmet-mode)
         (web-mode  . emmet-mode))
  :config
  (general-define-key
   :keymaps 'emmet-mode-keymap
   "C-RET" #'emmet-expand-yas))

Impatient mode serves web buffers live over HTTP so you can see your editions as you type them.

(use-package impatient-mode
  :straight (:build t)
  :defer t)

Web mode is a sort of hybrid major mode that allows editing several languages in the same buffer, mainly HTML, CSS, and Javascript.

(use-package web-mode
  :defer t
  :straight (:build t)
  :hook ((html-mode . web-mode))
  :mode (("\\.phtml\\'"      . web-mode)
         ("\\.tpl\\.php\\'"  . web-mode)
         ("\\.twig\\'"       . web-mode)
         ("\\.xml\\'"        . web-mode)
         ("\\.html\\'"       . web-mode)
         ("\\.htm\\'"        . web-mode)
         ("\\.[gj]sp\\'"     . web-mode)
         ("\\.as[cp]x?\\'"   . web-mode)
         ("\\.eex\\'"        . web-mode)
         ("\\.erb\\'"        . web-mode)
         ("\\.mustache\\'"   . web-mode)
         ("\\.handlebars\\'" . web-mode)
         ("\\.hbs\\'"        . web-mode)
         ("\\.eco\\'"        . web-mode)
         ("\\.ejs\\'"        . web-mode)
         ("\\.svelte\\'"     . web-mode)
         ("\\.ctp\\'"        . web-mode)
         ("\\.djhtml\\'"     . web-mode))
  :general
  (phundrak/major-leader-key
   :keymaps 'web-mode-map
   :packages 'web-mode
   "="  '(:ignore t :which-key "format")
   "E"  '(:ignore t :which-key "errors")
   "El" #'web-mode-dom-errors-show
   "gb" #'web-mode-element-beginning
   "g"  '(:ignore t :which-key "goto")
   "gc" #'web-mode-element-child
   "gp" #'web-mode-element-parent
   "gs" #'web-mode-element-sibling-next
   "h"  '(:ignore t :which-key "dom")
   "hp" #'web-mode-dom-xpath
   "r"  '(:ignore t :which-key "refactor")
   "rc" #'web-mode-element-clone
   "rd" #'web-mode-element-vanish
   "rk" #'web-mode-element-kill
   "rr" #'web-mode-element-rename
   "rw" #'web-mode-element-wrap
   "z"  #'web-mode-fold-or-unfold))

Auto-completion for emmet-mode, html-mode, and web-mode.

(use-package company-web
  :defer t
  :straight (:build t)
  :after (emmet-mode web-mode))
CSS

Lets customize a bit the built-in CSS mode.

(use-package css-mode
  :defer t
  :straight (:type built-in)
  :hook (css-mode . smartparens-mode)
  :hook (css-mode . lsp-deferred)
  :init
  (put 'css-indent-offset 'safe-local-variable #'integerp)
  :general
  (phundrak/major-leader-key
    :keymaps 'css-mode-map
    :packages 'css-mode
    "=" '(:ignore :wk "format")
    "g" '(:ignore :wk "goto")))

SCSS is much nicer to use than pure CSS in my opinion, so lets add a mode for that.

(use-package scss-mode
  :straight (:build t)
  :hook (scss-mode . smartparens-mode)
  :hook (scss-mode . lsp-deferred)
  :defer t
  :mode "\\.scss\\'")

And lets add some autocompletion for CSS.

(use-package counsel-css
  :straight (:build t)
  :defer t
  :init
  (cl-loop for (mode-map . mode-hook) in '((css-mode-map  . css-mode-hook)
                                           (scss-mode-map . scss-mode-hook))
           do (add-hook mode-hook #'counsel-css-imenu-setup)
           (phundrak/major-leader-key
            :keymaps mode-map
            "gh" #'counsel-css)))
Javascript

javascript-mode is meh at best, while rjsx-mode (Real JSX) is much better: it supports both Javascript and .jsx files for React and Next.JS.

(use-package rjsx-mode
  :defer t
  :straight (:build t)
  :after compile
  :mode "\\.[mc]?js\\'"
  :mode "\\.es6\\'"
  :mode "\\.pac\\'"
  :interpreter "node"
  :hook (rjsx-mode . rainbow-delimiters-mode)
  :hook (rjsx-mode . lsp-deferred)
  :init
  (add-to-list 'compilation-error-regexp-alist 'node)
  (add-to-list 'compilation-error-regexp-alist-alist
               '(node "^[[:blank:]]*at \\(.*(\\|\\)\\(.+?\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\)"
                      2 3 4))
  :general
  (phundrak/major-leader-key
    :keymaps 'rjsx-mode-map
    :infix "a"
    ""  '(:keymap lsp-command-map :which-key "lsp")
    "=" '(:ignore t :wk "format")
    "a" '(:ignore t :which-key "actions"))
  :config
  (setq js-chain-indent                  t
        js2-basic-offset                 2
        ;; ignore shebangs
        js2-skip-preprocessor-directives t
        ;; Flycheck handles this already
        js2-mode-show-parse-errors       nil
        js2-mode-show-strict-warnings    nil
        ;; conflicting with eslint, Flycheck already handles this
        js2-strict-missing-semi-warning  nil
        js2-highlight-level              3
        js2-idle-timer-delay             0.15))

js2-refactor is an amazing tool for refactoring Javascript code. I mean, look at this! And the video is only from 2013 and it still receives some commits!

(use-package js2-refactor
  :defer t
  :straight (:build t)
  :after (js2-mode rjsx-mode)
  :hook (js2-mode . js2-refactor-mode)
  :hook (rjsx-mode . js2-refactor-mode))

Which Emacser prefers the command line over Emacs itself? I dont. Lets interact with NPM through Emacs then.

(use-package npm-mode
  :defer t
  :straight (:build t)
  :hook (js-mode . npm-mode)
  :general
  (phundrak/major-leader-key
    :packages '(npm-mode rjsx-mode)
    :keymaps 'rjsx-mode-map
    "n" '(:keymap npm-mode-command-keymap :which-key "npm")))

And finally, here is a formatter for Javascript.

(use-package prettier-js
  :defer t
  :straight (:build t)
  :hook (rjsx-mode . prettier-js-mode))
Typescript

Typescript is a safer alternative to Javascript. Lets install its major mode then.

(use-package typescript-mode
  :defer t
  :straight (:build t)
  :hook (typescript-mode     . rainbow-delimiters-mode)
  :hook (typescript-mode     . lsp-deferred)
  :hook (typescript-tsx-mode . rainbow-delimiters-mode)
  :hook (typescript-tsx-mode . lsp-deferred)
  :commands typescript-tsx-mode
  :after flycheck
  :init
  (add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-tsx-mode))
  :general
  (phundrak/major-leader-key
    :packages 'lsp
    :keymaps '(typescript-mode-map typescript-tsx-mode-map)
    :infix "a"
    ""  '(:keymap lsp-command-map :which-key "lsp")
    "=" '(:ignore t :wk "format")
    "a" '(:ignore t :which-key "actions"))
  (phundrak/major-leader-key
    :packages 'typescript-mode
    :keymaps '(typescript-mode-map typescript-tsx-mode-map)
    "n" '(:keymap npm-mode-command-keymap :which-key "npm"))
  :config
  (after! flycheck
    (flycheck-add-mode 'javascript-eslint 'web-mode)
    (flycheck-add-mode 'javascript-eslint 'typescript-mode)
    (flycheck-add-mode 'javascript-eslint 'typescript-tsx-mode)
    (flycheck-add-mode 'typescript-tslint 'typescript-tsx-mode))
  (when (fboundp 'web-mode)
    (define-derived-mode typescript-tsx-mode web-mode "TypeScript-TSX"))
  (autoload 'js2-line-break "js2-mode" nil t))

Tide enabled interactivity with Typescript.

(use-package tide
  :defer t
  :straight (:build t)
  :hook (tide-mode . tide-hl-identifier-mode)
  :config
  (setq tide-completion-detailed              t
        tide-always-show-documentation        t
        tide-server-may-response-length       524288
        tide-completion-setup-company-backend nil)

  (advice-add #'tide-setup :after #'eldoc-mode)

  :general
  (phundrak/major-leader-key
    :keymaps 'tide-mode-map
    "R"   #'tide-restart-server
    "f"   #'tide-format
    "rrs" #'tide-rename-symbol
    "roi" #'tide-organize-imports))
Zig

Zig is to C kind of what Rust is to C++: a modern replacement without sacrificing performance. It is much safer than C while providing interop with it. Plus, its standard library is pretty complete.

First, here is its major mode.

(use-package zig-mode
  :defer t
  :straight (:build t)
  :after flycheck
  :hook (zig-mode . lsp-deferred)
  :config
  ;; This is from DoomEmacs
  (flycheck-define-checker zig
    "A zig syntax checker using the zig-fmt interpreter."
    :command ("zig" "fmt" (eval (buffer-file-name)))
    :error-patterns
    ((error line-start (file-name) ":" line ":" column ": error: " (mesage) line-end))
    :modes zig-mode)
  (add-to-list 'flycheck-checkers 'zig)
  :general
  (phundrak/major-leader-key
    :packages 'zig-mode
    :keymaps 'zig-mode-map
    "c" #'zig-compile
    "f" #'zig-format-buffer
    "r" #'zig-run
    "t" #'zig-test-buffer))

For LSP to work, we need zls to be installed.

paru --skipreview --noconfirm -S zls-bin 2>&1

Visual Configuration

Dashboard

(use-package dashboard
  :straight (:build t)
  :ensure t
  :after all-the-icons
  :config
  (setq dashboard-banner-logo-title "Phundraks Vanilla Emacs"
        dashboard-startup-banner    'logo
        dashboard-center-content    t
        dashboard-show-shortcuts    t
        dashboard-set-navigator     t
        dashboard-set-heading-icons t
        dashboard-set-file-icons    t
        initial-buffer-choice       (lambda () (get-buffer "*dashboard*"))
        dashboard-projects-switch-function 'counsel-projectile-switch-project-by-name)
  (setq dashboard-navigator-buttons
        `(((,(all-the-icons-faicon "language" :height 1.1 :v-adjust 0.0)
            "Linguistics Website"
            ""
            (lambda (&rest _) (browse-url "https://langue.phundrak.com")))

           (,(all-the-icons-faicon "firefox" :height 1.1 :v-adjust 0.0)
            "Config Website"
            ""
            (lambda (&rest _) (browse-url "https://config.phundrak.com"))))

          ((,(all-the-icons-octicon "git-branch" :height 1.1 :v-adjust 0.0)
            "Dotfiles Sources"
            ""
            (lambda (&rest _) (browse-url "https://labs.phundrak.com/phundrak/dotfiles")))
           ("!" "Issues" "Show issues" (lambda (&rest _)
                                         (browse-url "https://labs.phundrak.com/phundrak/dotfiles/issues"))
            warning))
          ((,(all-the-icons-faicon "level-up" :height 1.1 :v-adjust 0.0)
            "Update Packages"
            ""
            (lambda (&rest _) (progn
                                (require 'straight)
                                (straight-pull-all)
                                (straight-rebuild-all)))))))

  (setq dashboard-items '((recents  . 15)
                          (projects . 10)))
  (dashboard-setup-startup-hook)
  :init
  (add-hook 'after-init-hook 'dashboard-refresh-buffer))

Fringe

Its nice to know which lines were modified since the last commit in a file.

(use-package git-gutter-fringe
  :straight (:build t)
  :hook ((prog-mode     . git-gutter-mode)
         (org-mode      . git-gutter-mode)
         (markdown-mode . git-gutter-mode)
         (latex-mode    . git-gutter-mode)))

Icons? Did someone say icons?

YES! ALL OF THEM!

Ahem…

The package all-the-icons allows us to use a wide variety of icons in Emacs for various purposes, wherever we want, and THAT is GREAT! Ill (ab)use this feature in my config, be warned! NOTE: The first time a configuration with all-the-icons loads on a machine, the needed fonts might not be available, so youll need to install them with the command M-x all-the-icons-install-fonts.

(use-package all-the-icons
  :defer t
  :straight t)

prettify-symbols-mode is also a nifty feature of Emacs, and it is built-in! With that, I can replace strings of my choice by another character of my choice! First, lets declare the general symbols that will be used everywhere.

(defun prog-mode-set-symbols-alist ()
  (setq prettify-symbols-alist '(("lambda"  . )
                                 ("return"  . ?⮐)
                                 ("null"    . ?∅)
                                 ("NULL"    . ?∅)
                                 ("for"     . ?∀)
                                 ("in"      . ?∈)
                                 ("and"     . ?∧)
                                 ("&&"      . ?∧)
                                 ("or"      . ?)
                                 ("||"      . ?)
                                 ("xor"     . ?⊻)))
  (prettify-symbols-mode 1))

(add-hook 'prog-mode-hook #'prog-mode-set-symbols-alist)

We can now take care of the language-specific symbols. First, lets declare some symbols for the Lisp languages.

(setq-default lisp-prettify-symbols-alist '(("lambda"    . )
                                            ("mapc"      . ?↦)
                                            ("defun"     . ?𝑓)
                                            ("defvar"    . ?𝑣)
                                            ("defcustom" . ?𝑐)
                                            ("defconst"  . ?𝐶)
                                            ("not"       . ?!)
                                            ("pi"        . )))

(defun lisp-mode-prettify ()
  (setq prettify-symbols-alist lisp-prettify-symbols-alist)
  (prettify-symbols-mode -1)
  (prettify-symbols-mode 1))

(dolist (lang '(emacs-lisp lisp common-lisp scheme))
  (add-hook (intern (format "%S-mode-hook" lang))
            #'lisp-mode-prettify))

Lets declare also some for Rust:

(defun rust-mode-pretty-symbols ()
  (push '("fn"     . ?𝑓) prettify-symbols-alist)
  (push '("struct" . ) prettify-symbols-alist)
  (prettify-symbols-mode -1)
  (prettify-symbols-mode 1))

(add-hook 'rustic-mode-hook #'rust-mode-pretty-symbols)

And some for C and C++.

(defun c-cpp-mode-pretty-symbols ()
  (push '("int"      . ?) prettify-symbols-alist)
  (push '("float"    . ?) prettify-symbols-alist)
  (push '("#include" . ?⭳) prettify-symbols-alist)
  (push '("struct"   . ) prettify-symbols-alist)
  (prettify-symbols-mode -1)
  (prettify-symbols-mode 1))

(add-hook 'c-mode-hook #'c-cpp-mode-pretty-symbols)
(add-hook 'c++-mode-hook #'c-cpp-mode-pretty-symbols)

Finally, similar to how org-appear behaves, lets show the real string of our symbols when the cursor is on it.

(setq prettify-symbols-unprettify-at-point t)

Ligatures

The font Im using (see §#Basic-configuration-Visual-Configuration-Fontsxfkjel6184j0) supports ligatures, but Emacs in GUI mode does not. And of course, theres a package for that.

(use-package ligature
  :straight (ligature :type git
                      :host github
                      :repo "mickeynp/ligature.el"
                      :build t)
  :config
  (ligature-set-ligatures 't
                          '("www"))
  ;; Enable traditional ligature support in eww-mode, if the
  ;; `variable-pitch' face supports it
  (ligature-set-ligatures '(eww-mode org-mode elfeed-show-mode)
                          '("ff" "fi" "ffi"))
  ;; Enable all Cascadia Code ligatures in programming modes
  (ligature-set-ligatures 'prog-mode
                          '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                            ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                            "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                            "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                            "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                            "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                            "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                            "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                            ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                            "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                            "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                            "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                            "\\\\" "://"))
  (global-ligature-mode t))

Modeline

The DoomEmacs modeline looks nice in my opinion, lets use it.

(use-package doom-modeline
  :straight (:build t)
  :defer t
  :init (doom-modeline-mode 1)
  :custom ((doom-modeline-height 15)))

Secret mode

Sometimes, I want to hide the text displayed by Emacs but not lock altogether my computer. In this case, secret-mode comes in handy.

(use-package secret-mode
  :defer t
  :straight (secret-mode :build t
                         :type git
                         :host github
                         :repo "bkaestner/secret-mode.el"))

Solaire: Incandescent Emacs

A common issue when you have a lot of windows opened in Emacs is sometimes theres just too much. Is the first window source code? Is the other one just an open email? Oh, lets not forget the *Messages* buffer open next to another source buffer.

Solaire-mode applies a subtle but useful tweak to your current color scheme: the background of programming buffers is slightly lighter than the background of other buffers. (Or is it other buffers that have a slightly darker background? Im not sure.)

(use-package solaire-mode
  :defer t
  :straight (:build t)
  :init (solaire-global-mode +1))

Theme

You may have noticed I use the Nord theme pretty much everywhere on my computer, why not Emacs?

(use-package doom-themes
  :straight (:build t)
  :defer t
  :init (load-theme 'doom-nord t))

Rainbow Delimiters

This makes Lisp especially more readable, but its also nice to have for any language that has delimiters like brackets too.

(use-package rainbow-delimiters
  :straight (:build t)
  :defer t
  :hook (prog-mode . rainbow-delimiters-mode))

Yall want some more COLORS?

It is possible to make info buffers much more colorful (and imo easier to read) with this simple package:

(use-package info-colors
  :straight (:build t)
  :commands info-colors-fnontify-node
  :hook (Info-selection . info-colors-fontify-node)
  :hook (Info-mode      . mixed-pitch-mode))

Misc

ArchWiki pages

A small package Ive written allows the user to view ArchLinux pages either in Emacs or in an external web browser. I prefer the defaults.

(use-package archwiki
  :defer t
  :straight (archwiki :build t
                      :type git
                      :repo "https://labs.phundrak.com/phundrak/archwiki.el"))

avy

avy is a really convenient way of jumping around, but Ill need some configuration to make it bépo-compatible.

(use-package avy
  :defer t
  :straight t
  :config
  (setq avy-keys '(?a ?u ?i ?e ?c ?t ?s ?r ?n))
  (defun my/avy-goto-url ()
    "Jump to url with avy."
    (interactive)
    (avy-jump "https?://"))
  (defun my/avy-open-url ()
    "Open url selected with avy."
    (interactive)
    (my/avy-goto-url)
    (browse-url-at-point))
  :general
  (phundrak/evil
    :packages 'avy
    "gl" #'avy-goto-line)
  (phundrak/leader-key
    :packages 'avy
    :infix "j"
    "j" #'evil-avy-goto-char-timer
    "l" #'evil-avy-goto-line
    "u" #'my/avy-goto-url
    "U" #'my/avy-open-url))

Calc

Lets give calc-mode some better defaults.

(setq calc-angle-mode    'rad
      calc-symbolic-mode t)

Elcord

Whats the point of using Emacs if you cant tell everyone?

(use-package elcord
  :straight (:built t)
  :defer t
  :config
  (setq elcord-use-major-mode-as-main-icon t
        elcord-refresh-rate                5
        elcord-display-elapsed             nil)

  (add-to-list 'elcord-boring-buffers-regexp-list ".*window-managers.*"))

Enime

(use-package enime
  :defer t
  :straight (enime :type git
                   :host github
                   :repo "xl666/enime"
                   :fork t
                   :build nil)
  :init
  (require 'enime)
  :config
  (setq enime-storage-file "~/.cache/enime.db"))

ivy-quick-find-files.el

This package is a small utility package Ive written in order to quickly find files across my filesystem.

(use-package ivy-quick-find-files
  :defer t
  :straight (ivy-quick-find-files :type git
                                  :host github
                                  :repo "phundrak/ivy-quick-find-files.el"
                                  :build t)
  :config
  (setq ivy-quick-find-files-program 'fd
        ivy-quick-find-files-dirs-and-exts '(("~/org"                  . "org")
                                             ("~/Documents/conlanging" . "org")
                                             ("~/Documents/university" . "org"))))

Keycast

In case I am sharing my screen with people and I want to show which functions are called on my keystrokes since I dont exactly use standard keybindings.

(use-package keycast
  :defer t
  :straight (:build t)
  :config
  (define-minor-mode keycast-mode
    "Show current command and its key binding in the mode line."
    :global t
    (if keycast-mode
        (add-hook 'pre-command-hook 'keycast--update t)
      (remove-hook 'pre-command-hook 'keycast--update)))
  (add-to-list 'global-mode-string '("" mode-line-keycast " ")))

SICP

Who would get interested in Emacs and not want to read the SICP? Moreover, inside Emacs?

(use-package sicp
  :straight (:build t)
  :defer t)

Winum

Winum allows Emacs to associate windows with a specific number and navigate through these windows by directly refering to their associated number! This allows for faster window configuration than just going to the frame above, then left, left, and up.

(use-package winum
  :straight (:build t)
  :init (winum-mode))

Ytplay

ytplay is a small package Ive written with which you can choose at which resolution to play a YouTube video in an external video player.

(use-package ytplay
  :defer t
  :straight (ytplay :build t
                    :type git
                    :repo "https://labs.phundrak.com/phundrak/ytplay.el"))

Keybindings

Undefining some stuff to make keybind prefixes work correctly.

(general-define-key
 :keymaps 'global-map
 "<mouse-2>" nil
 "<mouse-3>" nil)

(phundrak/evil
 "U"   #'evil-redo
 "C-a" #'beginning-of-line
 "C-e" #'end-of-line
 "C-y" #'yank)
(phundrak/leader-key
  "SPC" '(counsel-M-x :wk "M-x")
  "'"   #'shell-pop

  <<general-keybindings-gen(table=keybinds-apps, prefix="a")>>
  <<general-keybindings-gen(table=keybinds-apps-shell, prefix="as")>>
  <<general-keybindings-gen(table=treemacs-keybinds, prefix="at")>>

  <<general-keybindings-gen(table=keybinds-buffers, prefix="b")>>

  "c"   '(:ignore t :wk "code")
  "cl"  #'evilnc-comment-or-uncomment-lines

  <<keybindings-flycheck>>

  <<general-keybindings-gen(table=keybinds-files, prefix="f")>>
  <<keybinds-specific-files>>

  <<general-keybindings-gen(table=keybinds-help, prefix="h")>>

  "i"   '(:ignore t :wk "insert")
  "iu"  #'counsel-unicode-char

  <<general-keybindings-gen(table=keybinds-jump, prefix="j")>>

  <<general-keybindings-gen(table=keybinds-project, prefix="p")>>

  <<general-keybindings-gen(table=keybinds-toggle, prefix="t")>>

  <<general-keybindings-gen(table=keybinds-text, prefix="T")>>

  <<general-keybindings-gen(table=keybinds-project, prefix="p")>>

  <<general-keybindings-gen(table=keybinds-windows, prefix="w")>>

  <<general-keybindings-gen(table=keybinds-quit, prefix="q")>>

  "u"   #'universal-argument
  "U"   #'undo-tree-visualize)

Apps

Here are my apps keybinds. Each one of them is prefixed by a.

Key Function Description
apps
c calc
d docker
E elfeed
e email
ec mu4e-compose-new
em mu4e
k keycast-mode
K keycast-log-mode
T tetris
w wttrin
C calendar

I also have two main shell-related functions, prefixed with as.

Key Function Description
shells
e eshell-new
v vterm

Buffers

My buffer-related keybinds are all prefixed by b.

Key Function Description
buffers
b bufler-switch-buffer
B bury-buffer
l bufler
d kill-this-buffer
D kill-buffer
h dashboard-refresh-buffer
m switch-to-messages-buffer
r counsel-buffer-or-recentf
s switch-to-scratch-buffer

Errors

(defhydra hydra-flycheck
  (:pre (flycheck-list-errors)
   :post (quit-windows-on "*Flycheck errors*")
   :hint nil)
  ("f" flycheck-error-list-set-filter "Filter")
  ("t" flycheck-next-error "Next")
  ("s" flycheck-previous-error "Previous")
  ("gg" flycheck-first-error "First")
  ("G" (progn (goto-char (point-max)) (flycheck-previous-error)) "Last")
  ("q" nil))
"e"  '(:ignore t :which-key "errors")
"e." '(hydra-flycheck/body :wk "hydra")
"el" #'counsel-flycheck
"ee" '(:keymap flycheck-command-map :wk "flycheck")
"ef" '(:keymap flyspell-mode-map :wk "flyspell")
"eF" #'flyspell-hydra/body

Files

My keybinds for file manipulation are prefixed by f.

Key Function Description
files
f counsel-find-file
F ivy-quick-find-files
h hexl-find-file
r counsel-recentf
s save-buffer

I also have some keybinds dedicated to opening specific files.

"fc"  '((lambda ()
          (interactive)
          (find-file "~/org/config/emacs.org"))
        :wk "emacs.org")
"fi"  '((lambda ()
          (interactive)
          (find-file (concat user-emacs-directory "init.el")))
        :which-key "init.el")
"fS"  '((lambda ()
          (interactive)
          (find-file "~/org/config/stumpwm.org"))
        :which-key "stumpwm.org")

Help

My keybinds for help are prefixed by h.

Key Function Description
help
k which-key-show-top-level
i info
I info-display-manual
d describe
dc describe-char
dC helpful-command
df helpful-callable
di describe-input-method
dk helpful-key
dm helpful-macro
dM helpful-mode
dp describe-package
ds helpful-symbol
dv helpful-variable

Jump

My keybinds for jumping around are prefixed by j.

Key Function Description
jump
c counsel-dired-jump
f counsel-file-jump
d dired-jump
D dired-jump-other-window

Project

My keybinds for my projects are prefixed by p.

Key Function Description
project
! projectile-run-shell-command-in-root
& projectile-run-async-shell-command-in-root
b counsel-projectile-switch-to-buffer
c counsel-projectile
d counsel-projectile-find-dir
e projectile-edit-dir-locals
f counsel-projectile-find-file
g projectile-find-tag
k project-kill-buffers
p counsel-projectile-switch-project
t ivy-magit-todos
v projectile-vc

Text

The prefix here is T.

Key Function Description
text
e string-edit-at-point
u downcase-region
U upcase-region
z hydra-zoom/body

Toggles

My toggle keybinds are prefixed by t.

Key Function Description
toggles
t counsel-load-theme
d debug
de toggle-debug-on-error
dq toggle-debug-on-quit
i input method
it toggle-input-method
is set-input-mode

Windows

A couple of keybinds are hidden from which-key, otherwise theres not much to say. The prefix here is w.

Key Function Description
windows
c evil-window-left
t evil-window-down
s evil-window-up
r evil-window-right
. windows-adjust-size/body
- split-window-below-and-focus
/ split-window-right-and-focus
$ winum-select-window-by-number
0 winum-select-window-0-or-10 none
1 winum-select-window-1 none
2 winum-select-window-2 none
3 winum-select-window-3 none
4 winum-select-window-4 none
5 winum-select-window-5 none
6 winum-select-window-6 none
7 winum-select-window-7 none
8 winum-select-window-8 none
9 winum-select-window-9 none
b kill-buffer-and-delete-window
d delete-window
o other-window
D delete-other-windows
w writeroom
w. writeroom-buffer-width/body
ww writeroom-mode

Quit

Why would I ever use any of these keybinds? They are prefixed with q.

Key Function Description
quit
f delete-frame
q save-buffers-kill-terminal
Q kill-emacs