Spacemacs

Before proceeding, be aware that I deprecated this i3 config on October 20th 2021, meaning I won’t update it anymore unless I use it again some day in the future. I will keep it on my website though.

This file is the main source file for my Spacemacs configuration which contains most of the user code. It is exported thanks to Emacs’ code tangling from the original Org file which you can find on my dotfiles’ repository1 if you are reading the web version of it. You can also find there my .spacemacs2 and its code which isn’t part of the present file. As you can see in my .spacemacs, at init-time, if Emacs detects the tangled configuration files are older than the Org file, then Emacs tangles them again, and then loads them.

Spacemacs layers and packages

Here will be our layer configuration set. Everything here is set with a setq-default in the dotspacemacs/layers function like so:

(defun dotspacemacs/layers ()
  (setq-default
   ;; configuration goes here
   ))

General configuration

First, we need to tell Spacemacs which base distribution we are using. This is a layer contained in the directory +distribution. For now, available distributions are spacemacs-base and spacemacs (the default one).

(setq-default dotspacemacs-distribution 'spacemacs)

We can seet a lazy installation of layers —i.e. layers are installed only when a file with a supported type is opened. Possible values are:

  • all: will lazy install any layer that support lazy installation even the layers listed in dotspacemacs-configuration-layers
  • unused: will lazy install only unused layers (i.e. layers not listed in the variable dotspacemacs-configuration-layers)
  • nil: disables the lazy installation feature and you have to explicitly list a layer in the variable dotspacemacs-configuration-layers to install it.

The default value is unused.

(setq-default dotspacemacs-enable-lazy-installation nil)

If the following variable is non-nil, Spacemacs will ask for confirmation before installing a layer lazily. The default value is t.

(setq-default dotspacemacs-ask-for-lazy-installation t)

Package management

It is possible to indicate to Spacemacs a list of additional paths where to look for configuration layers. Paths must have a trailing slash, i.e. ~/.mycontribs/. As you can see, I added only one:

(setq-default dotspacemacs-configuration-layer-path
              '("~/fromGIT/emacs-packages"))

However, I do have additional packages I installed either from the Elpa or the Melpa. These are set in dotspacemacs-additional-packages, a list of additional packages that will be installed without being wrapped in a layer. If I need some configuration for these packages, then I should consider creating a layer. I can also puth the configuration in dotspacemacs/user-config. To use a local version of a package, use the :location property, for instance:

'(your-package :location "~/path/to/your-package/")

With the variable dotspacemacs-additional-packages, it is possible to install extra packages which are not already included in any layers. Dependencies should be explicitly included as they won’t be resolved automatically. Here is a table of all the extra packages I use:

name of the packagewhy is it installed
caddyfile-modeMajor mode for editing Caddyfiles
dired-git-infoGit information in Dired buffers
diredflExtra font lock rules for a more colourful dired
edit-indirectedit region in separate buffer
elcordrich integration of Emacs in Discord
eshell-syntax-highlightingSyntax highlighting for Eshell
info-colorsExtra colors for Emacs’s Info-mode
multiple-cursorsI don’t like the layer, I prefer this package alone
ob-latex-as-pngInline arbitrary LaTeX snippets as PNGs in Emacs
org-sidebardisplay on the side the outline of an Org buffer
org-tree-slidepresentation tool for org-mode
outorgedit comments as Org-mode buffers
ox-sshSSH config export for org-mode
pinentryenter a GPG password from Emacs
nord-themeAn arctic, north-bluish clean and elegant Emacs theme.
sThe long lost Emacs string manipulation library.
sicpTexinfo version of the SICPopen in new window
visual-fill-columnallow the use of fill-column in visual-line-mode
wrap-regioneasily wrap region with delimiters
wttrinweather in Emacs
yasnippet-snippetssnippets for YaSnippet

It is possible to also list packages that cannot be updated:

(setq-default dotspacemacs-frozen-packages '(helm-icons))

And to list packages which won’t be installed nor loaded:

(setq-default dotspacemacs-excluded-packages '(company-tern))

Finally, it is possible to define the behaviour of Spacemacs when installing packages. Possible values are:

  • used-only: installs only explicitly used packages and deletes any unused packages as well as their unused dependencies
  • used-but-keep-unused: installs only the used packages but won’t delete unused ones
  • all : installs all packages supported by Spacemacs and never uninstalls them.

The default value is used-only.

(setq-default dotspacemacs-install-packages 'used-only)

Layers

All layers are set one variable: dotspacemacs-configuration-layers. This variable is a list of layers, some of them will have some custom variables. Typically, the variable will be set like so:

(setq-default dotspacemacs-configuration-layers
              '(emacs-lisp
                helm
                multiple-cursors
                org
                (shell :variables shell-default-height 30
                                  shell-default-position 'bottom)
                treemacs))

Checkers

The two first checkers I use are for spell and syntax checking. spell-checking is disabled by default, however it should auto-detect the dictionary to use.

(spell-checking :variables
                spell-checking-enable-by-default nil
                spell-checking-enable-auto-dictionary t)
syntax-checking

Completion

auto-completion is a layer enabled in order to provide auto-completion to all supported language layers. It is set so that the RET key has no behavior with this layer, however the TAB key cycles between candidates displayed by the auto-completion toolbox. I also want the autocompletion to include snippets in the popup, and the content of the popup is sorted by usage. It is also disabled for two modes: magit and Org.

(auto-completion :variables
                 auto-completion-complete-with-key-sequence-delay 0.2
                 auto-completion-enable-help-tooltip 'manual
                 auto-completion-enable-sort-by-usage t
                 :disabled-for
                 org
                 git)

helm is also enabled, with its header disabled.

(helm :variables helm-no-header t)

Email

I use as my daily Email client mu4e, so let’s enable it and tell Emacs where mu4e is installed. I also tell mu4e to use maildirs extensions, use async operations, where to keep attachments, and enable the mu4e modeline.

(mu4e :variables
      mu4e-installation-path "/usr/share/emacs/site-lisp"
      mu4e-use-maildirs-extension t
      mu4e-enable-mode-line t
      mu4e-attachment-dir "~/Documents/mu4e")

Emacs

The first layer enabled in this category is better-defaults. I also made it so that when a command equivalent to C-a or C-e is pressed, the cursor will respectively first move to the beginning of code first before going past the indentation and to the end of the code before going to the end of the line before going over the end of the comments on the same line.

(better-defaults :variables
                 better-defaults-move-to-beginning-of-code-first t
                 better-defaults-move-to-end-of-code-first t)

I also enabled ibuffer and made it so that buffers are sorted by projects.

(ibuffer :variables
         ibuffer-group-buffers-by 'projects)

Most important of all, the org layer is also enabled. I enabled support for Epub exports, Github, Reveal.JS exports, and sticky headers. Project support is also enabled through files named TODOs.org. I also set the org-download folder for images in ~/Pictures/org/, and I set the RET key to follow org links if the cursor is on one.

(org :variables
     org-enable-epub-support t
     org-enable-github-support t
     org-enable-hugo-support t
     org-enable-reveal-js-support t
     org-enable-sticky-header t
     org-enable-appear-support t
     spaceline-org-clock-p t
     org-projectile-file "TODOs.org"
     org-download-image-dir "~/Pictures/org/"
     org-return-follows-link t)

The semantic layer is also enabled.

semantic

File trees

In this category, I only enabled one layer: treemacs. In this layer, I set is so that treemacs syncs with my current buffer, and it automatically refreshes its buffer when there is a change in the part of the file system shown by treemacs.

(treemacs :variables
          treemacs-use-follow-mode t
          treemacs-use-filewatch-mode t)

Fonts

In this category, again, one layer is enabled: unicode-fonts. This layer addssupport for the unicode-fonts package.

(unicode-fonts :variables
               unicode-fonts-enable-ligatures t
               unicode-fonts-ligature-modes '(prog-mode)
               unicode-fonts-ligature-set '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                                            ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                                            "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                                            "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                                            "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                                            "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                                            "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                                            "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                                            ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                                            "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                                            "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                                            "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                                            "\\\\" "://"))

Fun

In this category, I only enabled two layers: selectric and xkcd.

selectric xkcd

Internationalization

In this category, I enabled the keyboard-layout layer to enable compatibility with the bépo layout. This layer, however, is disabled for magit, Dired and eww.

(keyboard-layout :variables
                 kl-layout 'bepo
                 kl-disabled-configurations '(magit dired eww))

Programming languages

  1. Domain-specific (DSLs)

    In this category, I enabled support for the major-modes layer for the Arch Linux PKGBUILDs support, emacs-lisp and scheme layers, support for the CSV format with the csv layer, the yaml language, shell scripting languages and support for the dot tool with the graphviz layer.

    major-modes emacs-lisp scheme graphviz yaml shell-scripts
    

    I also added support for HTML and CSS with the html layer, with the web formatting tool set to web-beautify, and the LSP layer compatibility enabled for CSS, less, SCSS and HTML.

    (html :variables
          web-fmt-tool 'web-beautify
          css-enable-lsp t
          less-enable-lsp t
          scss-enable-lsp t
          html-enable-lsp t)
    

    The json layer is also enabled, with the format tool set to web-beautify.

    (json :variables
          json-fmt-tool 'web-beautify)
    

    The LaTeX layer has also been enabled, with its default compiler set to XeLaTeX. I also enabled the auto-fill feature, the folding capacity and the “magic” symbols.

    (latex :variables
           latex-build-command "xelatex"
           latex-enable-auto-fill t
           latex-enable-folding t
           latex-enable-magic t)
    

    The Markdown layer has been enabled, with support for live preview with vmd, and and automatic MMM-mode generation for C, C++, Python, Rust and Emacs Lisp.

    (markdown :variables
              markdown-live-preview-engine 'vmd
              markdown-mmm-auto-modes '("c"
                                        "c++"
                                        "python"
                                        "rust"
                                        ("elisp" "emacs-lisp")))
    

    PlantUML is a very useful DSL for creating UML diagrams from some text description. As you can see below, this layer will be enabled, both as a standalone mode for opening .pum files, but also for org-mode code blocks.

    (plantuml :variables
              plantuml-jar-path "~/.local/bin/plantuml.jar"
              org-plantuml-jar-path "~/.local/bin/plantuml.jar")
    
    (bibtex :variables
            org-ref-default-bibliography '("~/Documents/Papers/references.bib")
            org-ref-pdf-directory "~/Documents/Papers"
            org-ref-bibliography-notes "~/Documents/Papers/notes.org")
    
    csv
    
  2. Frameworks

    Only one framework support has been enabled so far, and is is for the Django framework.

    django
    
  3. General-purpose

    Among the layers I activated, the only one without any specific configuration is the asm layer for the Assembly language.

    asm
    

    Next, you can find the C/C++ layer for which I set the default language for .h files to be C. I also enabled support for subprojects and organization of the include directives on a file save. I also set a couple of LSP-related variables, such as the LSP executable for C/C++ for its CCLS backend and some highlight variables.

    (c-c++ :variables
           c-c++-default-mode-for-headers 'c-mode
           c-c++-adopt-subprojects t
           c-c++-enable-c++11 t
           c-c++-backend 'lsp-clangd
           c-c++-lsp-enable-semantic-highlight t
           c-c++-lsp-semantic-highlight-method 'overlay
           c-c++-lsp-semantic-highlight-rainbow t
           c++-enable-organize-includes-on-save t)
    

    Dart has also been enabled, with a custom path to the SDK of the Dart server, and to the LSP server of Dart.

    (dart :variables
          lsp-dart-project-sdk-dir "/opt/dart-sdk/"
          lsp-dart-sdk-dir "/opt/dart-sdk/")
    

    When it comes to the Python layer, I set its backend and formatter to be bound to the LSP layer. Its fill columnn was also set to 80 characters, imports are sorted on save, and the tests can be run using either nose.el or pytest.

    (python :variables
            python-fill-column 80
            python-test-runner '(pytest nose))
    

    With the Rust layer, the only custom configuration set is the backend being bound to the LSP layer.

    (rust :variables rust-backend 'lsp)
    

    As regards the JavaScript layer, I set its backend to the LSP layer, and bound its format tool to web-beautify and its REPL is browser-based. I also want to include node_modules/.bin to be automatically added to the buffer local exec_path.

    (javascript :variables
                javascript-backend 'lsp
                javascript-fmt-tool 'web-beautify
                javascript-repl 'skewer
                node-add-modules-path t)
    

    Alternatively, I also use Typescript which is a sort of better Javascript as it should have been, with the LSP backend.

    (typescript :variables
                typescript-backend 'lsp)
    

    I am also currently using the Awesome window manager which requires the Lua programming language, so here it is.

    (lua :variables
         lua-backend 'lsp-emmy
         lua-lsp-emmy-jar-path "~/.config/awesome/EmmyLua-LS-all.jar"
         lua-lsp-emmy-java-path "java"
         lua-lsp-emmy-enable-file-watchers t)
    
    (php :variables php-backend 'lsp)
    

Readers

  1. Epub and Pdf readers

    In this category, only the epub and pdf layers are enabled without any special configuration, so I can read these files from Emacs directly.

    epub pdf
    
  2. Elfeed

    Elfeed is an Emacs feeed and RSS reader which can be managed through org files. Actually, through only one file in my case, located in my ~/org directory.

    (elfeed :variables
            rmh-elfeed-org-files '("~/org/elfeed.org"))
    

Version control

Only the git layer is enabled in this category.

git

Themes

Here, the colors layer is the only one enabled. It activates support for identifiers colorization, and strings representing colors.

colors

Tools

In this category, the first layer to be enabled is the CMake layer for which I enabled support for the cmake-ide package.

(cmake :variables
       cmake-enable-cmake-ide-support t)

Next, we have the Docker, Nginx, Pass (the standard Unix password manager), Prettier, Systemd, Meson, Imenu-list, Web-beautify, Dap, and Helpful.

dap docker helpful imenu-list meson nginx pass prettier systemd web-beautify

Of course, let’s not forget about the awesome LSP layer:

(lsp :variables lsp-lens-enable t
                lsp-use-lsp-ui t
                lsp-rust-server 'rust-analyzer)

We also have the RestClient layer enabled for which I enabled the Org compatibility support.

(restclient :variables
            restclient-use-org t)

LanguageTool works with Flyspell and will check for grammatical issues in my english texts.

(languagetool :variables
              langtool-default-language "en-US"
              languagetool-show-error-on-jump t
              langtool-java-classpath "/usr/share/languagetool:/usr/share/java/languagetool/*")

And finally, we also have the Shell layer for which I specified its default height when spawning at the bottom of the screen should be 40 lines high, and the default shell to invoke is Eshell.

(shell :variables
       shell-default-height 40
       shell-default-position 'bottom
       shell-default-shell 'eshell)

Web Services

In this category, I have only enabled a layer for Twitter support.

twitter

Custom layers

Lastly, one custom layers have been enabled: my custom layer for conlanging tools.

conlanging

Init

The dotspacemacs/init function is the one called at the very begining of the Spacemacs startup, before any kind of configuration, including the layer configuration. Only the values of the Spacemacs settings should be modified here. By default, every values are set in a setq-default sexp, and they represent all the supported Spacemacs settings. Hence, the function looks like this:

(defun dotspacemacs/init ()
  (setq-default
   ;; default Spacemacs configuration here
   ))

Handling my Spacemacs litterate config

Just before we get onto the usual content of the dotspacemacs/init function you would find in a typical Spacemacs installation, I would like to talk a bit about how I manage writing a litterate config for Spacemacs and ensure Emacs starts with an up-to-date configuration from said litterate config. For that, I actually declared a couple of variables:

(defvar phundrak--dotspacemacs-src-dir "~/.emacs.spacemacs/private/"
  "Directory for my exported Elisp configuration files")
(defvar phundrak--dotspacemacs-src "~/org/config/emacs.org"
  "My litterate config file for Emacs")
(defvar phundrak--dotspacemacs-si (concat phundrak--dotspacemacs-src-dir "spacemacs-init"))
(defvar phundrak--dotspacemacs-sl (concat phundrak--dotspacemacs-src-dir "spacemacs-layers"))
(defvar phundrak--dotspacemacs-uc (concat phundrak--dotspacemacs-src-dir "user-config"))
(defvar phundrak--dotspacemacs-ui (concat phundrak--dotspacemacs-src-dir "user-init"))
(defvar phundrak--dotspacemacs-files (list phundrak--dotspacemacs-si phundrak--dotspacemacs-sl
                                           phundrak--dotspacemacs-uc phundrak--dotspacemacs-ui))

I also declared the following function that tells me if my Elisp files are more recent than my emacs.org file. The compiled? argument lets me compare either the .el files if it is nil, or the .elc files if it is t.

(defun phundrak-update-config-files-p (&optional compiled?)
  "Verify if any of my exported Elisp configuration files are
newer than my litterate configuration.

If `COMPILED?' is `t', check the `.elc' files instead of the
`.el' files."
  (catch 'ret
    (dolist (file phundrak--dotspacemacs-files)
      (when (file-newer-than-file-p phundrak--dotspacemacs-src
                                    (format "%s.%s"
                                            file
                                            (if compiled? "elc" "el")))
        (throw 'ret t)))))

Now I know a couple of my files that get exported by this document. If I compare how recent these files are compared to my litterate config, I know if Emacs missed tangling its configuration before launching, so if any of my si, sl, uc, or ui files are older than my emacs.org, then I’ll tangle the latter; and since my user config is growing longer and longer, I want Emacs to be able to parse it fast next time it boots, so let’s compile my exported .el files!

(when (or (file-newer-than-file-p phundrak--dotspacemacs-src (concat phundrak--dotspacemacs-si ".el"))
            (file-newer-than-file-p phundrak--dotspacemacs-src (concat phundrak--dotspacemacs-sl ".el"))
            (file-newer-than-file-p phundrak--dotspacemacs-src (concat phundrak--dotspacemacs-ui ".el"))
            (file-newer-than-file-p phundrak--dotspacemacs-src (concat phundrak--dotspacemacs-uc ".el")))
    (message "Exporting new Emacs configuration from spacemacs.org through org-babel...")
    (with-temp-buffer
      (shell-command (format "emacs -Q --batch %s %s %s"
                             "--eval \"(require 'ob-tangle)\""
                             "--eval \"(setq org-confirm-babel-evaluate nil)\""
                             (format "--eval '(org-babel-tangle-file \"%s\")'"
                                     phundrak--dotspacemacs-src))
                     (current-buffer)))
    (message "Exporting new Emacs configuration from spacemacs.org through org-babel...done")
    (with-temp-buffer
      (byte-recompile-directory phundrak--dotspacemacs-src-dir
                                0 t)))

All that’s left to do in the Spacemacs functions is to call load on si, sl, uc, and ui. Be aware this sub-chapter won’t be tangled, so it might not be up to date with the actual dotspacemacs fileopen in new window. Please check it just in case something changed and I forgot to update this part of emacs.org.

Emacs with pdumper

It is possible to compile Emacs 27 from source with support for the portable dumper, as shown in Spacemacs’ EXPERIMENTAL.org file. I do not use this feature yet, as I am still on Emacs 26 provided from Arch Linux’s repositories, so I’ll disable the Spacemacs support for this feature. The default value of this variable is nil.

(setq-default dotspacemacs-enable-emacs-pdumper t)

In case the support for pdumper was enabled, Spacemacs needs to know the name of the Emacs executable which supports such a feature. The executable must be in the user’s PATH. By default, the value of the variable is "emacs".

(setq-default dotspacemacs-emacs-pdumper-executable-file "emacs")

And finally, we can name the Spacemacs dump file. This is the file that will be created by the portable dumper in the cache directory under the dumps sub-directory. To load it when starting Emacs, the parameter --dump-file should be added when invoking Emacs 27.1 executable from the command line, for instance:

./emacs --dump-file=~/.emacs.spacemacs/.cache/dumps/spacemacs.pdmp

The default value of this variable is "spacemacs.pdmp".

(setq-default dotspacemacs-emacs-dumper-dump-file
              (format "spacemacs-%s.pdmp" emacs-version))

Package managment and updates

Spacemacs’ core configuration can be updated via git commands using Github services. If Spacemacs is not set to the develop branch, it can check by itself if any update is available. However, I am using said branch, therefore I should set this variable to nil. The default value is nil.

(setq-default dotspacemacs-check-for-update nil)

When it comes to package management, Spacemacs is able to store them in different directories depending on the version of Emacs used or based on other variables. I personally prefer to use the value emacs-version since it makes it easier to upgrade or downgrade Emacs without any conflict with the already installed packages. The default value is emacs-version.

(setq-default dotspacemacs-elpa-subdirectory 'emacs-version)

Spacemacs has a capacity of performing rollbacks after updates. We can set the maximum number of rollback slots to keep in the cache. The default value is 5.

(setq-default dotspacemacs-max-rollback-slots 5)

Elpa repository

It is possible to ask Emacs to use an HTTPS connection when contacting the Elpa whenever possible. This value should be set to nil when the user has no way to contact the Elpa though HTTPS, otherwise it is strongly recommended to let it set to t. This variable however has no effect if Emacs is launched with the parameter --insecure which forces the value of this variable to nil. The default value is t.

(setq-default dotspacemacs-elpa-https t)

We can set a maximum amount of seconds which will represent the maximum allowed time to contact the Elpa repository. By default, this setting is set on 5.

(setq-default dotspacemacs-elpa-timeout 5)

Spacelpa repository

The Spacelpa repository is a Spacemacs-specific package repository. It is possible to use it as the primary source to install a locked version of a package. If the below value is set to nil, then Spacemacs will install the latest version of packages from MELPA. I personally don’t use it, so I let it set to nil. The default value is nil.

(setq-default dotspacemacs-use-spacelpa nil)

If the below value is not nil, then the signature for the downloaded Spacelpa packages must be verified.

(setq-default dotspacemacs-verify-spacelpa-archives t)

Editing style

By default, Spacemacs encourages the use of evil-mode, which brings vim keybinding in Emacs. Still, it has three different styles available:

  • vim, which goes full evil-mode usage and most adapted to Emacs newcomers, especially if they were used to vim before, with the use of a normal mode and an insert mode.
  • emacs which keeps an Emacs-like feel to the keybindings, without any difference between an insert or normal mode.
  • hybrid is a modification of the vim mode which brings the emacs style in insert mode, but otherwise behaves like the vim style in normal mode. This is the style I personally use.

The value can also be a list with the :variables keyword (similar to layers). Check the editing styles section of the documentation for details on available variables. The default value is vim.

(setq-default dotspacemacs-editing-style
              '(hybrid :variables
                       hybrid-mode-enable-evilified-state t
                       hybrid-mode-default-state 'normal))

If non-nil, the paste transient-state is enabled. While enabled, after you paste something, pressing C-j and C-k several times cycles through the elements in the kill-ring. Default nil.

(setq-default dotspacemacs-enable-paste-transient-state t)

Spacemacs home configuration

The value below specifies the startup banner of Spacemacs. The default value is official, it displays the official Spacemacs logo. An integer value is the index of text banner, random chooses a random text banner in the core/banners directory. A string value must be a path to an image format supported by your Emacs build. If the value is nil, then no banner is displayed. The default value is official.

(setq-default dotspacemacs-startup-banner 'official)

On the Spacemacs homepage, a list of elements can also be shown, be it recent files, projects, agenda items,… Each of the elements making up the list value of the below variable are pairs in the form (list-type . list-size). If the value is nil, then it is disabled. The possible values for list-type are:

  • recents: displays recently opened files
  • bookmarks: displays saved bookmarks
  • projects: displays projectile projects recently opened
  • agenda: displays upcoming events from Org-mode agendas
  • todos: displays recent TODOs detected in projectile projects

The order in which they are set in the below list affects their order on the Spacemacs startup page. List sikes may be nil, in which case spacemacs-buffer-startup-lists-length takes effect.

(setq-default dotspacemacs-startup-lists '((recents . 15)
                                           (projects . 15)))

The below variable allows the startup page to respond to resize events. Its default value is t.

(setq-default dotspacemacs-startup-buffer-responsive t)

If non-nil show the version string in the Spacemacs buffer. It will appear as (spacemacs version)@(emacs version). Default t.

(setq-default dotspacemacs-startup-buffer-show-version t)

Default major modes

The below variable sets a default major mode for a new empty buffer. Possible values are mode names such as text-mode, or nil to use Fundamental mode. The default value is text-mode, but I prefer to use org-mode by default.

(setq-default dotspacemacs-new-empty-buffer-major-mode 'org-mode)

Similarly, the below variable sets the default mode for the scratch buffer. Its default value is text-mode, but I set it to use emacs-lisp-mode by default.

(setq-default dotspacemacs-scratch-mode 'emacs-lisp-mode)

By the way, it is possible to set a default message for the scratch buffer, such as “Welcome to Spacemacs!”. I prefer to keep it clean. The default value is nil.

(setq-default dotspacemacs-initial-scratch-message nil)

Visual configuration

Themes

Spacemacs makes it quite easy to use themes and organize them. The below value is a list of themes, the first of the list is loaded when Spacemacs starts. The user can press SPC T n to cycle to the next theme in the list.

(setq-default dotspacemacs-themes '(nord doom-nord doom-vibrant spacemacs-dark
                                         doom-one doom-opera doom-dracula doom-molokai
                                         doom-peacock doom-sourcerer doom-spacegrey
                                         kaolin-dark kaolin-aurora kaolin-bubblegum
                                         kaolin-galaxy kaolin-mono-dark kaolin-temple
                                         kaolin-valley-dark))

Emacs also makes use of themes for the Spaceline at the bottom of buffers. Supported themes are:

  • spacemacs
  • all-the-icons
  • custom
  • doom (the one I use)
  • vim-powerline
  • vanilla

The first three are Spaceline themes. doom is the Doom-Emacs mode-line, and vanilla is the default Emacs mode-line. custom is a user defined theme, refer to Spacemacs’ DOCUMENTATION.org file for more info on how to create your own Spaceline theme. Value can be a symbol or list with additional properties. The default value is '(spacemacs :separator wave :separator-scale 1.5)).

(setq-default dotspacemacs-mode-line-theme '(doom
                                             :separator wave
                                             :separator-scale 1.0))

It is also possible to color the cursor depending on which mode Spacemacs is in, in order to mach the state color in GUI Emacs. The default value is t.

(setq-default dotspacemacs-colorize-cursor-according-to-state t)

The below variable sets either the default font or a prioritized list of fonts to be used by Emacs. The :size can be specified as a non-negative integer (pixel size), or a floating-point (point size). Point size is recommended, because it’s device independent (add a .0 to make an integer a floating point). The default size is 10.0.

(setq-default dotspacemacs-default-font '("Cascadia Code"
                                          :size 9.0))

I also added the following code in order to define a fallback font for emojis, defined only on their unicode range:

(set-fontset-font "fontset-default" '(#x1f600 . #x1f64f) "NotoEmoji Nerd Font")

Other on-screen elements

which-key is a helper which displays available keyboard shortcuts. This variable sets in seconds the time Spacemacs should wait between a key press and the moment which-key should be shown.

(setq-default dotspacemacs-which-key-delay 1)

This variable sets which-key’s frame position. Possible values are:

  • right
  • bottom
  • right-then-bottom

right-then-bottom tries to display the frame to the right, but if there is insufficient space it displays it at the bottom. The default value is bottom.

(setq-default dotspacemacs-which-key-position 'right-then-bottom)

This controls where switch-to-buffer displays the buffer. If the value is nil, switch-to-buffer displays the buffer in the current window even if another same-purpose window is available. If non-nil, switch-to-buffer displays the buffer in a same-purpose window even if the buffer can be displayed in the current window. The default value is nil.

(setq-default dotspacemacs-switch-to-buffer-prefers-purpose nil)

If this variable is non-nil, a progress bar is displayed when Spacemacs is loading. This may increase the boot time on some systems and emacs builds, set it to nil to boost the loading time. The default value is t.

(setq-default dotspacemacs-loading-progress-bar t)

If the value is non-nil, Emacs will show the title of the transient states. The default value is t.

(setq-default dotspacemacs-show-transient-state-title t)

If non-nil, this will show the color guide hint for transient state keys. The default value is t.

(setq-default dotspacemacs-show-transient-state-color-guide t)

If non-nil, unicode symbols are displayed in the mode line. If you use Emacs as a daemon and want unicode characters only in GUI set the value to quoted display-graphic-p. The default value is t.

(setq-default dotspacemacs-mode-line-unicode-symbols t)

If non-nil, smooth scrolling (native-scrolling) is enabled. Smooth scrolling overrides the default behavior of Emacs which recenters point when it reaches the top or bottom of the screen. The default value is t.

(setq-default dotspacemacs-smooth-scrolling t)

The following value controls the line number activation. If set to t, relative or visual then line numbers are enabled in all prog-mode and text-mode derivatives. If set to relative, line numbers are relative. If set to visual, line numbers are also relative, but only visual lines are counted. For example, folded lines will not be counted and wrapped lines are counted as multiple lines. This variable can also be set to a property list for finer control:

'(:relative nil
  :visual nil
  :disabled-for-modes dired-mode
                      doc-view-mode
                      markdown-mode
                      org-mode
                      pdf-view-mode
                      text-mode
  :size-limit-kb 1000)

When used in a plist, visual takes precendence over relative.

(setq-default dotspacemacs-line-numbers '(:relative nil
                                          :visual nil
                                          :disabled-for-modes org-mode pdf-view-mode
                                                              dired-mode doc-view-mode
                                                              text-mode))

Select a scope to highlight delimiter. Possible values are:

  • any
  • current
  • all
  • nil

The default value is all (highlights any scope and emphasis the current one).

(setq-default dotspacemacs-highlight-delimiters 'all)

After a certain amount of time in seconds, Spacemacs can zone-out. The default value is nil. I set it so Spacemacs zones out after 15 minutes.

(setq-default dotspacemacs-zone-out-when-idle 900)

Run spacemacs/prettify-org-buffer when visiting the README.org files of Spacemacs. The default value is nil.

(setq-default dotspacemacs-pretty-docs nil)

If nil, the home buffer shows the full path of agenda items and todos. If non nil, only the file name is shown.

(setq-default dotspacemacs-home-shorten-agenda-source t)

Appearance of Emacs frames

Starting from Emacs 24.4, it is possible to make the Emacs frame fullscreen when Emacs starts up if the variable is set to a non-nil value. The default value is nil.

(setq-default dotspacemacs-fullscreen-at-startup nil)

This variable is to be used if the user does not want to use native fullscreen with spacemacs/toggle-fullscreen. This disables for instance the fullscreen animation under OSX. The default value is nil.

(setq-default dotspacemacs-fullscreen-use-non-native nil)

If you do not start Emacs in fullscreen at startup, you might want it to be maximized by default. If the value for the variable below is set to be non-nil, the frame will be maximized. This can only work if dotspacemacs-fullscreen-at-startup is set to nil, and it is only available from Emacs 24.4 onwards. The default value is nil.

(setq-default dotspacemacs-maximized-at-startup nil)

If non-nil, the frame is undecorated when Emacs starts up. Combine this with the variable dotspacemacs-maximized-at-startup in OSX to obtain borderless fullscreen. The default value is nil.

(setq-default dotspacemacs-undecorated-at-startup nil)

You can also set a transparency level for Emacs when you toggle the transparency of the frame with toggle-transparency. The value of the transparency, going from 0 to 100 in increasing opacity, describes the transparency level of a frame when it’s active or selected. The default value is 90.

(setq-default dotspacemacs-active-transparency 85)

Similarly, you can set a value from 0 to 100 in increasing opacity which describes the transparency level of a frame when it’s inactive or deselected. The default value is 90.

(setq-default dotspacemacs-inactive-transparency 80)

The variable below sets the format of frame title. You can use:

  • %a: the abbreviated-file-name or buffer-name
  • %t: projectile-project-name
  • %I: invocation-name
  • %S: system-name
  • %U: contents of $USER
  • %b: buffer name
  • %f: visited file name
  • %F: frame name
  • %s: process status
  • %p: percent of buffer above top of window, or Top, Bot, or All
  • %P: percent of buffer above bottom of window, perhaps plus Top, or Bot, or All
  • %m: mode name
  • %n: Narrow if appropriate
  • %z: mnemonics of buffer, terminal, and keyboard coding systems
  • %Z: like %z, but including the end-of-line format

The default value is "%I@%S".

(setq-default dotspacemacs-frame-title-format "Emacs: %b (%t) %U@%S")

Format specification for setting the icon title format. The default value is nil, same as frame-title-format.

(setq-default dotspacemacs-icon-title-format nil)

Spacemacs leader keys and shortcuts

The below setting sets the Spacemacs leader key. By default, this is the SPC key.

(setq-default dotspacemacs-leader-key "SPC")

Once the leader key has been pressed, it is possible to set another key in order to call Emacs’ command M-x. By default, it is again the SPC key.

(setq-default dotspacemacs-emacs-command-key "SPC")

It is also possible to invoke Vim Ex commands with the press of a key, and by default it is the : key.

(setq-default dotspacemacs-ex-command-key ":")

The below variable sets the leader key accessible in emacs-state and insert-state:

(setq-default dotspacemacs-emacs-leader-key "M-m")

The major mode leader key is a shortcut key which is the equivalent of pressing <leader> m. Set it to nil to disable it. Its default value is ,.

(setq-default dotspacemacs-major-mode-leader-key ",")

In emacs-state and insert-state, the same major mode leader key can be accessible from another shortcut, which by default is C-M-m in terminal mode, or M-return in GUI mode.

(setq-default dotspacemacs-major-mode-emacs-leader-key
              (if window-system "<M-return>" "C-M-m"))

These variables control whether separate commands are bound in the GUI to the key pairs C-i and TAB, and C-m and RET. Setting it to a non-nil value allows for separate commands under C-i and TAB, and C-m and RET. In the terminal, these pairs are generally indistinguishable, so this only works in the GUI. The default value is nil.

(setq-default dotspacemacs-distinguish-gui-tab nil)

Layouts

The variable belows sets the name of the default layout. Its default value is "Default".

(setq-default dotspacemacs-default-layout-name "Default")

If non-nil, the default layout name is displayed in the mode-line. The default value is nil.

(setq-default dotspacemacs-display-default-layout nil)

If non-nil, then the last auto saved layouts are resumed automatically upon start. The default value is nil.

(setq-default dotspacemacs-auto-resume-layouts nil)

If non-nil, the layout name will be auto-generated when creating new layouts. It only has an effect when using the “jump to layout by number” command. The default value is nil.

(setq-default dotspacemacs-auto-generate-layout-names nil)

The below value sets the size in MB above which Spacemacs will prompt to open the file literally in order to avoid preformance issues. Opening a file literally means that no major or minor mode is active. The default value is 1.

(setq-default dotspacemacs-large-file-size 5)

This variable sets the location where to auto-save files. Possible values are:

  • original: auto-saves files in-place
  • cache: auto-saves files in another file stored in the cache directory
  • nil: disables auto-saving.

The default value is cache.

(setq-default dotspacemacs-auto-save-file-location 'original)

Emacs server

Emacs can be launched as a server if the following value is set to non-nil and if one isn’t already running. The default value is nil.

(setq-default dotspacemacs-enable-server nil)

You can also set a custom emacs server socket location. If the value is nil, Emacs will use whatever the Emacs default is, otherwise a directory path like "$HOME/.config/emacs/server". It has no effect if dotspacemacs-enable-server is nil.

(setq-default dotspacemacs-server-socket-dir nil)

It is also possible to tell Emacs that the quit function should keep the server open when quitting. The default value is nil.

(setq-default dotspacemacs-persistent-server t)

Miscellaneous

This value changes the folding method of code blocks. The possible values are either evil, the default value, or origami.

(setq-default dotspacemacs-folding-method 'evil)

If non-nil, smartparens-strict-mode will be enabled in programming modes. The default value is nil.

(setq-default dotspacemacs-smartparens-strict-mode nil)

If non-nil, pressing the closing parenthesis ) key in insert mode passes over any automatically added closing parenthesis, bracket, quote, etc… This can temporarily disabled by pressing C-q before ). The default value is nil.

(setq-default dotspacemacs-smart-closing-parenthesis nil)

List of search tool executable names. Spacemacs uses the first installed tool of the list. Supported tools are:

  • rg
  • ag
  • pt
  • ack
  • grep

The default value is '("rg" "ag" "pt" "ack" "grep").

(setq-default dotspacemacs-search-tools '("rg" "grep"))

Delete whitespace while saving buffer. Possible values are:

  • all: aggresively delete empty lines and long sequences of whitespace
  • trailing: only detele the whitespace at end of lines
  • changed: to delete only whitespace for changed lines
  • nil: disable cleanup

The default value is nil.

(setq-default dotspacemacs-whitespace-cleanup nil)

Set gc-cons-threshold and gc-cons-percentage when startup finishes. This is an advanced option and should not be changed unless you suspect performance issues due to garbage collection operations. The default is '(100000000 0.1)

(setq-default dotspacemacs-gc-cons '(100000000 0.1))

If non nil activate clean-aindent-mode which tries to correct virtual indentation of simple modes. This can interfer with mode specific indent handling like has been reported for go-mode. If it does deactivate it here. Default t.

(setq-default dotspacemacs-use-clean-aindent-mode t)

If non nil, shift your number row to match the entered keyboard layout (only in insert state). Currently supported keyboard layouts are querty-us, quertz-de and querty-ca-fr. New layouts can be added in spacemacs-editing layer. Default nil.

(setq-default dotspacemacs-swap-number-row nil)

Set read-process-output-max when startup finishes. This defines how much data is read from a foreign process. Setting this >= 1 MB should increase performance for lsp servers in emacs 27.

(setq-default dotspacemacs-read-process-output-max (* 1024 1024 8))

User Initialization

User Init

While Emacs and especially Spacemacs loads, I want it to initialize some elements and load some packages. First of all, I want it to load my private Emacs config file:

(load "~/.emacs.spacemacs/private/private_emacs")

I would also like to enable the setup of flycheck for Rust when Flycheck is loaded:

(add-hook 'flycheck-mode-hook #'flycheck-rust-setup)

By default, Flyspell should be disabled and only enabled manually.

(flyspell-mode 0)

Finally, here is a quick workaround for Tramp, sometimes it cannot connect to my hosts if I don’t have this code snippet.

(setq tramp-ssh-controlmaster-options
      "-o ControlMaster=auto -o ControlPath='tramp.%%C' -o ControlPersist=no")
(require 'org)

User Load

Then, I want a couple of requires:

(require 'org-id)
(require 'org-protocol)
(require 'package)
(require 'ox-latex)
(require 'ox-publish)
(require 'tramp)

User Configuration

Custom functions, macros, and variables

In this section, I will put my various custom functions that do not fit in other sections and which are more oriented towards general usage throughout Emacs and in Elisp code.

Almost all of my code snippets will be prefixed by either my name or the name of the package or layer they are part of, unless they are an explicit overwrite of a function that already exists.

Elisp Utilities and Predicates

  1. phundrak-filter

    (defun phundrak-filter (fn list)
      "Filter `LIST' according to the predicate `FN'.
    
    All elements from `LIST' that do not satisfy the predicate `FN'
    will be left out of the result, while all elements that do
    satisfy it will be included in the resulting list. This function
    also preserves the relative position between elements that
    satisfy the predicate."
      (declare (pure t) (side-effect-free t))
      (when list
        (let ((rest (phundrak-filter fn
                                     (cdr list))))
          (if (funcall fn
                       (car list))
              (cons (car list) rest)
            rest))))
    
  2. phundrak-all?

    This function is inspired by dash’s -all? function: it will test all the elements of the list seq against the predicate fn which should return either t or nil. If all of them return something else than nil, then it is a success, otherwise it is a failure. Note that empty lists will always return t.

    (defun phundrak-all? (fn seq)
      "Check if all members of `SEQ' satisfy predicate `FN'. Note that
    it will return t if `SEQ' is nil."
      (declare (pure t) (side-effect-free t))
      (if seq
          (and (funcall fn (car seq))
               (phundrak-all? fn (cdr seq)))
        t))
    
  3. phundrak-none?

    In the same vein as phundrak-all?, phundrak-none? checks if all elements of seq do not satify the predicate fn. Again, if the list is empty, it will return t.

    (defun phundrak-none? (fn seq)
      "Check if all members of `SEQ' do not satisfy predicate `FN'.
    Note that it will return t if `SEQ' is nil."
      (declare (pure t) (side-effect-free t))
      (if seq
          (and (not (funcall fn (car seq)))
               (phundrak-none? fn (cdr seq)))
        t))
    
  4. phundrak-zip

    (defun phundrak-zip (&rest lists)
      "Zip `LISTS' together.
    Be aware only the amount of elements of the smallest list will be zipped."
      (declare (pure t) (side-effect-free t))
      (when lists
        (let ((lists (if (= 1 (length lists)) ; only one element => a list of lists was passed
                         (car lists)
                       lists)))
          (when (phundrak-none? 'null lists)
            (cons (mapcar 'car lists)
                  (phundrak-zip (mapcar 'cdr lists)))))))
    
  1. phundrak-eshell-git-status

    This function is used in my Eshell prompt which you can consult here. This function basically executes two git calls to get some information about a git repo, which path we provide as an argument. Based on the result of these git calls, the function will know what it needs to know about the repo to build a git prompt that will be inserted in my Eshell prompt. And just for shit and giggles, I’ve made it so it is a powerline prompt.

    (defun phundrak-eshell-git-status ($path &optional $background-color)
      "Returns a string indicating the status of the repository located
    in `$PATH' if it exists. It should also append the name of the
    current branch if it is not `master' or `main'.
    `$BACKGROUND-COLOR' allows to choose the color that will be
    visible behind the powerline characters. The theme is inspired by
    the bobthefish theme for the fish shell which you can find here:
    https://github.com/oh-my-fish/theme-bobthefish
    
    Color code:
    - green:
    - orange: tracked stuff is staged but not commited
    - red: tracked stuff is modified and not commited
    
    Symbols:
    - `*': dirty working dir, RED
    - `~': staged changes, ORANGE
    - `…': untracked files, GREEN
    - `$': stashed changes
    - `-': unpulled commits
    - `-': unpushed commits
    - `±': unpulled and unpushed commits"
      (let* ((git-status-command       (concat "cd " $path "; git status"))
             (git-stash-status-command (concat "cd " $path "; git stash list"))
             (status                   (eshell-command-result git-status-command))
             (stashstat                (eshell-command-result git-stash-status-command))
             (detached                 (s-contains? "HEAD detached" status))
             (dirty                    (s-contains? "Changes not staged for commit" status))
             (staged                   (s-contains? "Changes to be committed" status))
             (untracked                (s-contains? "Untracked files" status))
             (pullable                 (s-contains? "git pull" status))
             (pushable                 (s-contains? "git push" status))
             (branch                   (replace-regexp-in-string "On Branch \\(.*\\)\n\\(.\\|\n\\)*" "\\1" status))
             (branch                   (unless (or (string= "master" branch)
                                                   (string= "main" branch)
                                                   detached)
                                         branch)))
        (let ((prompt (concat " "
                              (if detached ">" "")
                              (when branch (concat " " branch " "))
                              (when dirty "*")
                              (when staged "~")
                              (when untracked "…")
                              (cond ((and pullable pushable) "±")
                                    (pullable "-")
                                    (pushable "+"))
                              (when stashstat "$")
                              " "))
              (accent (cond
                       (dirty phundrak-nord11)
                       (staged phundrak-nord13)
                       (t phundrak-nord14)))
              (background (if $background-color
                              $background-color
                            phundrak-nord0)))
          (concat (with-face ""
                             :background accent
                             :foreground background)
                  (with-face prompt
                             :background accent
                             :foreground (if dirty phundrak-nord6 background))
                  (with-face ""
                             :background background
                             :foreground accent)))))
    
  2. phundrak-git-repo-root

    This function detects if the path passed as an argument points to a git directory or to one of its subdirectories. If it is, it will return the path to the root of the git repository, else it will return nil.

    (defun phundrak-git-repo-root ($path)
      "Return `$PATH' if it points to a git repository or one of its
    subdirectories."
      (when $path
        (if (f-dir? (concat $path "/.git"))
            $path
          (phundrak-git-repo-root (f-parent $path)))))
    
  3. phundrak-prompt-toggle-abbreviation

    (defvar phundrak-prompt--abbreviate t
      "Whether or not to abbreviate the displayed path in the Eshell
    prompt.")
    
    (defun phundrak-prompt-toggle-abbreviation ()
      "Toggles whether the Eshell prompt should shorten the name of
    the parent directories or not. See `phundrak-eshell-prompt'."
      (interactive)
      (setq phundrak-prompt--abbreviate (not phundrak-prompt--abbreviate)))
    
  4. phundrak-abbr-path

    The following is a nice little function I use in my Eshell prompt. It shortens the name of all the parent directories of the current one in its path, but leaves the current one written in full. It also abbreviates the equivalent of the $HOME (/home/<username>/) directory to a simple ~.

    (defun phundrak-abbr-path ($path &optional $abbreviate)
      "Abbreviate `$PATH'. If `$ABBREVIATE' is t, then all parent
    directories of the current directory will be abbreviated to one
    letter only. If a parent directory is a hidden directory (i.e.
    preceeded by a dot), the directory will be abbreviated to the dot
    plus the first letter of the name of the directory (e.g.
    \".config\" -> \".c\").
    
    For public use of the function, `$PATH' should be a string
    representing a UNIX path. For internal use, `$PATH' can also be a
    list. If `$PATH' is neither of those, an error will be thrown by
    the function."
      (cond
       ((stringp $path) (f-short
                         (if $abbreviate
                             (phundrak-abbr-path (f-split (phundrak-abbr-path $path)))
                           $path)))
       ((null $path) "")
       ((listp $path)
        (f-join (cond ((= 1 (length $path)) (car $path))
                      (t (let* ((dir (car $path))
                                (first-char (s-left 1 dir)))
                           (if (string= "." first-char)
                               (s-left 2 dir)
                             first-char))))
                (phundrak-abbr-path (cdr $path))))
       (t (error "Invalid argument %s, neither stringp nor listp" $path))))
    
  1. phundrak-file-to-string

    (defun phundrak-file-to-string (FILE)
      "Returns the content of `FILE' as a string."
      (with-temp-buffer
        (insert-file-contents FILE)
        (buffer-string)))
    
  2. phundrak-find-org-files

    There are lots of files which I want to be able to quickly open. I used to have one shortcut for each one of these files, but as their number grew, I decided to switch to helm for my file selector which will be called by only one common shortcut. Most of my files will be located in ~/org, but I have some conlanging files which are located in ~/Documents/conlanging, and all my university notes are in ~/Documents/university. Let’s declare these directories in a variable:

    (defvar phundrak-org-directories '("~/org"
                                       "~/Documents/university/S8"
                                       "~/Documents/conlanging")
      "Directories in which to look for org files with the function
    `phundrak-find-org-files'.")
    

    With this established, let’s write some emacs-lisp that will allow me to get a list of all these files and select them through helm. Be aware that I will be using some functions from third party packages, such as s.elopen in new window and dashopen in new window, as well as fdopen in new window.

    (defun phundrak-find-org-files ()
      "Find all org files in the directories listed in
    `phundrak-org-directories', then list them in an ido buffer where
    the user can match one and open it."
      (interactive)
      (find-file
       (ivy-completing-read
        "Org File: "
        (s-split "\n"
                 (mapconcat (lambda (path)
                              (shell-command-to-string
                               (format "fd . %s -e org -c never" path)))
                            phundrak-org-directories
                            "\n")))))
    
  3. phundrak-open-marked-files

    This function is particularly useful in Dired buffers when someone wants to open multiple files. This function will basically look for all marked files in the current dired buffer and open each one of them in their individual buffer.

    (defun phundrak-open-marked-files ()
      "This function allows the user to open all marked files in a
    Dired buffer at once."
      (interactive)
      (let ((file-list (if (string= major-mode "dired-mode")
                           (dired-get-marked-files)
                         (list (buffer-file-name)))))
        (mapc (lambda (file)
                (find-file file))
              file-list)))
    
  4. xah/open-in-external-app

    Here is another of Xah’s functions, this time to open a file externally to Emacs. For instance, I sometimes want to open a PDF in Zathura rather than in Emacs, or an HTML file in Firefox. With this function, it is now possible!

    (defun xah/open-in-external-app (&optional files)
      "Open the current file or dired marked files in external app.
    The app is chosen from your OS’ preference.
    
    When called in emacs lisp, if `FILES' is given, open that.
    
    URL `http://ergoemacs.org/emacs/emacs_dired_open_file_in_ext_apps.html'
    Version 2019-01-18"
      (interactive)
      (let* (($file-list (if files
                             (progn (list files))
                           (if (string-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 ($fpath)
                  (let ((process-connection-type nil))
                    (start-process "" nil "xdg-open" $fpath)))
                $file-list))))
    

Theming

  1. Nord theming variables

    Yes, I do use a preconfigured theme, as mentioned above, but for some elements such as Eshell, I need to define some variables for color, and I’ll do it here.

    (defvar phundrak-nord0  "#2e3440")
    (defvar phundrak-nord1  "#3b4252")
    (defvar phundrak-nord2  "#434c5e")
    (defvar phundrak-nord3  "#4c566a")
    (defvar phundrak-nord4  "#d8dee9")
    (defvar phundrak-nord5  "#e5e9f0")
    (defvar phundrak-nord6  "#eceff4")
    (defvar phundrak-nord7  "#8fbcbb")
    (defvar phundrak-nord8  "#88c0d0")
    (defvar phundrak-nord9  "#81a1c1")
    (defvar phundrak-nord10 "#5e81ac")
    (defvar phundrak-nord11 "#bf616a")
    (defvar phundrak-nord12 "#d08770")
    (defvar phundrak-nord13 "#ebcb8b")
    (defvar phundrak-nord14 "#a3be8c")
    (defvar phundrak-nord15 "#b48ead")
    
  2. with-face

    with-face is a simple yet very useful macro that allows me to easily create strings with faces defined as properties to the string passed as the first argument. Here is how it is implemented:

    (defmacro with-face ($str &rest $properties)
      "Helper macro for creating strings `$STR' with `$PROPERTIES'"
      `(propertize ,$str 'face (list ,@$properties)))
    

phundrak-blog-publish

This function is quite a simple function made to automatically publish my blogopen in new window based on Hugo. After exporting my blog using ox-hugo, I simply have to call this function which will look for all files located in ~/org/blog/public and copy them to my remote server once hugo has been executed in ~/org/blog.

(defun phundrak-blog-publish ()
  "Publish my blog through Hugo and rsync to my remote server."
  (interactive)
  (let* ((blog-path "~/org/blog")
         (public-path (concat blog-path "/public"))
         (target-path "/rsync:Tilo:/home/phundrak/www/phundrak.com/blog"))
    (shell-command (format "cd %s && hugo" blog-path))
    (let ((files (mapcar (lambda (file)
                           (f-relative file public-path))
                         (f-files (format "%s/public" blog-path) nil t))))
      (dolist (file files)
        (copy-file (concat public-path "/" file)
                   (concat target-path "/" file)
                   t nil t)))))

phundrak-yas-rust-new-assignments

The following function is a function that will allow me to easily create new functions for Rust structs. Inspired from elpyopen in new window’s elpy-snippet-init-assignments function, it will automatically write assignments to my new struct as I write new parameters in the new function. It also comes with a helper function that parses the arguments given to the new function.

(defun phundrak--yas-snippet-split-rust-args ($arg-string)
  "Split a Rust argument string `$ARG-STRING' into ((name,
default)...) tuples"
  (mapcar (lambda ($elem)
            (split-string $elem "[[:blank:]]*:[[:blank:]]*" t))
          (split-string $arg-string "[[:blank:]]*,[[:blank:]]*" t)))

(defun phundrak-yas-rust-new-assignments ($arg-string)
  "Return a typical new assignment for arguments.
Inspired from elpy’s functions https://github.com/jorgenschaefer/elpy"
  (let ((indentation (make-string (save-excursion
                                   (goto-char start-point)
                                   (current-indentation))
                                 ?\s)))
    (mapconcat (lambda ($elem)
                 (if (string-match "^\\*" (car $elem))
                     ""
                   (format "%s,\n%s" (car $elem) indentation)))
               (phundrak--yas-snippet-split-rust-args $arg-string)
               "")))

screenshot-svg

This function allows for taking SVG screenshots of Emacs from itself using Cairo. The function definition was taken from hereopen in new window.

(defun screenshot-svg ()
  "Save a screenshot of the current frame as an SVG image.
Saves to a temp file and puts the filename in the kill ring."
  (interactive)
  (let* ((filename (make-temp-file "Emacs" nil ".svg"))
         (data (x-export-frames nil 'svg)))
    (with-temp-file filename
      (insert data))
    (kill-new filename)
    (message filename)))

xah/dired-sort

This function comes directly from Xah Lee’s website and allows the user to sort files in a dired buffer depending on four factors:

  • File name
  • File size
  • Last modification date
  • File extension
(defun xah/dired-sort ()
  "Sort dired dir listing in different ways. Prompt for a choice.
URL `http://ergoemacs.org/emacs/dired_sort.html'
Version 2018-12-23, modified by Phundrak on 2019-08-06"
  (interactive)
  (let ($sort-by $arg)
    (setq $sort-by (ido-completing-read "Sort by:" '( "name" "size" "date" "extension" )))
    (cond
     ((equal $sort-by "name") (setq $arg "-ahl --group-directories-first"))
     ((equal $sort-by "date") (setq $arg "-ahl -t --group-directories-first"))
     ((equal $sort-by "size") (setq $arg "-ahl -S --group-directories-first"))
     ((equal $sort-by "extension") (setq $arg "-ahlD -X --group-directories-first"))
     (t (error "logic error 09535" )))
    (dired-sort-other $arg )))

Editing and modes

There is first a setting I would like to enable to make the navigation through text a bit easier which is to enable the subword mode. This allows me to iterate through words that are in CamelCase more easily, I don’t have to go either to the beginning or the end of the word and then move my cursor a bunch of times to get to the subword I want to modify.

(global-subword-mode 1)

Default modes

Some buffers sometimes won’t have a default mode at all, such as the *scratch* buffer. In any vanilla configuration, they will then default to text-mode. I personally prefer org-mode to be my default mode, so let’s set it so!

(setq edit-server-default-major-mode 'org-mode)

Evil

Evil is not really smart by default when I ask it to undo stuff. As an example, let’s say I enter insert-mode and write a whole paragraph, and then I exit insert-mode back to normal-mode, and I notice a small mistake I made when typing my stuff down, like accidentally using a snippet from yasnippet, and I want to undo that. My initial reaction would be to press u to undo, and so would be yours too right? That’s the wrong answer with evil, since it considers your actions between entering and leaving insert-mode as only one action, and it would thus erase your WHOLE PARAGRAPH. Fortunately, there is a way to make the undo action more granular, but it comes at the cost of increasing the amount of undo actions you can do (which shouldn’t really matter if you don’t have a potato PC).

(setq undo-limit 500000
      evil-want-fine-undo t)

File extensions

Sometimes, Emacs doesn’t recognize or misrecognizes some extensions, resulting in a wrong mode set for said file. Let’s fix that by associating the extension with the desired mode:

(dolist (e '(("xml" . web-mode)
             ("xinp" . web-mode)
             ("aiml" . web-mode)
             ("C" . c++-mode)
             ("dconf" . conf-mode)
             ("yy" . bison-mode)
             ("ll" . flex-mode)
             ("s" . asm-mode)
             ("pl" . prolog-mode)
             ("l" . scheme-mode)
             ("vs" . glsl-mode)
             ("fs" . glsl-mode)))
  (push (cons (concat "\\."
                      (car e)
                      "\\'") (cdr e))
        auto-mode-alist))

We also have a couple of extensions which should all be in conf-unix-mode, let’s indicate that to Emacs:

(dolist (e '("service" "timer" "target" "mount" "automount"
             "slice" "socket" "path" "netdev" "network"
             "link"))
  (push (cons (concat "\\." e "\\'") 'conf-unix-mode)
        auto-mode-alist))

Hooks

I also have some hooks I use for enabling some major and minor modes. The first one here allows the execution of the deletion of trailing space each time I save a file.

(add-hook 'before-save-hook 'delete-trailing-whitespace)

I also want to always be in visual-line-mode so Emacs soft-wraps lines that are too long for the buffer they are displayed in. This will also be enabled for Elfeed.

(add-hook 'prog-mode-hook 'visual-line-mode)
(add-hook 'elfeed-read-mode-hook 'visual-line-mode)

I also want for some non-programming modes to enable a hard-limit in terms of how many characters can fit on one line. The modes that benefit are message-mode, org-mode, text-mode and markdown-mode.

(mapc (lambda (x)
     (add-hook x 'visual-line-mode))
   '(message-mode-hook
     text-mode-hook
     markdown-mode-hook))

Twittering mode

For twittering-mode, a Twitter major mode for Emacs, I want to encrypt my data using a master password, which I do thanks to this option:

(setq twittering-use-master-password t)

Wrapping regions

I really like the M-( keybinding for wrapping a selected region between parenthesis. However, parenthesis are not everything (even in Lisp dialects), and other wrappers could be nice. And they are! Here is how they are declared:

(global-set-key (kbd "M-[") 'insert-pair)
(global-set-key (kbd "M-{") 'insert-pair)
(global-set-key (kbd "M-<") 'insert-pair)
(global-set-key (kbd "M-'") 'insert-pair)
(global-set-key (kbd "M-`") 'insert-pair)
(global-set-key (kbd "M-\"") 'insert-pair)

For the record, this is from Howard Abramopen in new window’s dotfilesopen in new window.

Emacs builtins

Dired

When it comes to dired, I chose do modify some elements on how things are sorted and shown, but there isn’t much configuration. First, I want to always copy folders in a recursive way, no questions asked.

(setq dired-recursive-copies 'always)

Also, when I have two Dired buffers opened side by side, I generally want them to interact with each other, for example if I want to move around or copy stuff. So, let’s tell Emacs that:

(setq dired-dwim-target t)

Finally, let’s tell Dired how to sort the elements to be displayed: directories first, non-hidden first.

(setq dired-listing-switches "-ahl --group-directories-first")

By the way, let’s enable org-download when we are in a Dired buffer:

(add-hook 'dired-mode-hook 'org-download-enable)

Finally, let’s enable globally diredfl so we can get a colourful Dired buffer each time we open one:

(diredfl-global-mode 1)

Emacs Lisp

For some reason, flycheck-mode is not enabled by default when in an elisp buffer. Let’s add that:

(add-hook 'emacs-lisp-mode-hook 'flycheck-mode)

Eshell

Eshell is a built-in shell available from Emacs which I use almost as often as Fish. Some adjustments are necessary for making this shell usable for me.

But first, here is a screenshot of what to expect visually from my configuration of Eshell when it is launched:

img

  1. Aliases

    This function is a function that will come in very handy for Eshell functions that call shell processes. It concatenates the initial string command with all the arguments args, each separated with a space.

    (defun phundrak/concatenate-shell-command ($command &rest $args)
      (string-join (cons $command $args) " "))
    

    Just like most shells, it is possible to declare in Eshell aliases. First, I would like to be able to use open to open files in Emacs:

    (defalias 'open 'find-file)
    

    I also have openo which allows me to perform the same action, but in another window:

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

    The function yes-or-no-p is also aliased to y-or-n-p so I only have to answer by y or n instead of typing yes or no.

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

    For some ease of use, I’ll also declare list-buffers as an alias of ibuffer.

    (defalias 'list-buffers 'ibuffer)
    

    mkcd is a function that allows me to create a directory and cd into it at the same time.

    (defun eshell/mkcd ($directory)
      (eshell/mkdir "-p" $directory)
      (cd $directory))
    
  2. Custom functions

    When I’m 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. Let’s 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))
    
    1. Redirect text editors to Emacs

      I still have some stupid muscle memory telling me to open emacs in the terminal, which is stupid with Eshell since I’m already inside Emacs. So, let’s open each file passed to the emacs command and bury the eshell buffer (we’ll get back to it later).

      (defun eshell/emacs (&rest $files)
        "Open a file in a new buffer. Old habits die hard"
        (if $files
            (mapc #'find-file
                  (mapcar #'expand-file-name
                          (eshell-flatten-list (reverse $files))))
          (bury-buffer)))
      
  3. Environment variables

    Some environment variables need to be correctly set so Eshell can correctly work. The first environment variable to be set is the PATH, as I have a couple of directories where executables are located. Let’s add them to our path.

    (setenv "PATH"
            (concat
             (getenv "HOME") "/.pub-cache/bin"
             ":" (getenv "HOME") "/.local/bin"
             ":" (getenv "HOME") "/go/bin"
             ":" (getenv "HOME") "/.cargo/bin"
             ":" (getenv "HOME") "/.gem/ruby/2.6.0/bin"
             ":" (getenv "PATH")))
    

    I would also 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/"))
    

    Finally, I’d like to add a custom directory to the PKG_CONFIG_PATH:

    (setenv "PKG_CONFIG_PATH" (concat
                               "/usr/local/lib/pkgconfig/" ":"
                               (getenv "PKG_CONFIG_PATH")))
    

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

    (setenv "EDITOR" "emacsclient -c")
    
    (setenv "SHELL" "/bin/sh")
    
  4. Eshell banner

    The code creating the Eshell banner is a bit lengthy and requires some additional explanations that would make the following chapter Eshell theme and prompt too long. So, here it is!

    The banner for Eshell will collect some system information and display them gracefully. These pieces of information are:

    • The GNU/Linux distribution running (I do not use any other OS on my computer)
    • The kernel name and its version
    • The machine’s hostname
    • Its uptime
    • Its RAM and Swap usage
    • How full are its mountpoints

    Some of these information can be grabbed directly from Emacs built-in functions, but some others need to be retrieved manually. Let’s first get into it with the mounted partitions for which we’ll define a structure:

    (cl-defstruct phundrak/mounted-partitions
      "Object representing a mounted partition found in the system"
      path size used percent)
    

    We’ll also define a variable setting the maximum length of a partition path before it gets abbreviated:

    (defvar phundrak//eshell-banner--max-length-part 13
      "Maximum length of a partition path")
    

    Now, we can get our partitions. For this, we’ll make a call to the shell command df -lH and we’ll keep only the partitions mounted on a device stored in /dev, for instance on /dev/sda. And as mentioned above, if the mount path of the partition exceeds the length specified by phundrak//eshell-banner--max-length-part, it will get abbreviated by phundrak-abbr-path.

    (defun phundrak/get-mounted-partitions ()
      (let ((partitions (s-split "\n"
                                 (shell-command-to-string "df -lH")
                                 t)))
        (-keep (lambda (partition)
                 (let* ((partition  (s-split " " partition t))
                        (filesystem (nth 0 partition))
                        (size       (nth 1 partition))
                        (used       (nth 2 partition))
                        (percent    (nth 4 partition))
                        (mount      (nth 5 partition)))
                   (when (s-prefix? "/dev" filesystem)
                     (make-phundrak/mounted-partitions
                      :path (if (> phundrak//eshell-banner--max-length-part (length mount))
                                mount
                              (phundrak-abbr-path mount t))
                      :size size
                      :used used
                      :percent (string-to-number (s-chop-suffix "%" percent))))))
               partitions)))
    

    We’ll need some padding for the name of the information displayed on the left hand side of the banner. The maximum length without any partitions is eight characters due to the text Hostname, so if any partition path is longer than this, the left padding will increase.

    (defun phundrak//eshell-banner--get-left-pad (initial-pad partitions)
      (if partitions
          (let ((part-length (length (phundrak/mounted-partitions-path (car partitions)))))
            (phundrak//eshell-banner--get-left-pad (if (> part-length initial-pad)
                                                       part-length
                                                     initial-pad)
                                                   (cdr partitions)))
        initial-pad))
    

    Now, Let’s set three variables that will be used in the function following this declaration. They will be used to determine in which color a percentage should be displayed. I’ll consider any percentage below 60% to be acceptable and therefore displayed in green. However, starting from this threshold, I want the user to be noticed of the usage of whatever percentage shown that it has gone up and it should be watched and displayed in yellow. Above 75%, the user should consider this a warning, and the percentage will be displayed in orange. Above 90%, it is considered critical and the percentage will be displayed in red.

    (defvar phundrak//eshell-banner--critical-percentage 90)
    (defvar phundrak//eshell-banner--warning-percentage  75)
    (defvar phundrak//eshell-banner--notice-percentage   60)
    
    (defun phundrak//eshell-banner--color-percentage (percentage)
      (cond
       ((> percentage phundrak//eshell-banner--critical-percentage)
        (with-face (format "%2d" percentage) :foreground phundrak-nord11))
       ((> percentage phundrak//eshell-banner--warning-percentage)
        (with-face (format "%2d" percentage) :foreground phundrak-nord12))
       ((> percentage phundrak//eshell-banner--notice-percentage)
        (with-face (format "%2d" percentage) :foreground phundrak-nord13))
       (t
        (with-face (format "%2d" percentage) :foreground phundrak-nord14))))
    

    This function will be used when displaying progress bars. These will be used for displaying the Ram, Swap and partitions usage of the system, displaying the used part in red and the free part in green. For this, we just need to know the size of the progress bar we wish to use as well as how full it should be. Note that the percentage should be between 0 and 100.

    (defun phundrak//eshell-banner--progress-bar (length percentage)
      (let* ((length-green (if (= 0 percentage)
                               0
                             (/ (* length percentage) 100)))
             (length-red   (- length length-green)))
        (concat (with-face "[" :weight 'bold)
                (with-face (s-repeat length-green "=")
                           :weight 'bold :foreground phundrak-nord14)
                (with-face (s-repeat length-red "=")
                           :weight 'bold :foreground phundrak-nord11)
                (with-face "]" :weight 'bold))))
    

    This function will be used in two distinct functions: phundrak-eshell-banner which we will see later, and phundrak//eshell-banner--display-memory which we will see now. This function displays information for the two types of memory we have, RAM and Swap memory. Here is the definition of this function:

    (defun phundrak//eshell-banner--display-memory (type used total text-padding ramp-length)
      (let ((percentage (if (= used 0)
                            0
                          (/ (* 100 used) total))))
        (concat (s-pad-right text-padding "." type)
                ": "
                (phundrak//eshell-banner--progress-bar ramp-length
                                                       percentage)
                (format " %6s / %-5s ("
                        (file-size-human-readable used)
                        (file-size-human-readable total))
                (phundrak//eshell-banner--color-percentage
                 percentage)
                "%)\n")))
    

    We now need a function for displaying partitions. As you can see, it will be quite similar to the above one:

    (defun phundrak//eshell-banner--display-partition (part left-pad ramp-length)
      (concat (s-pad-right left-pad "."
                           (with-face (phundrak/mounted-partitions-path part)
                                      :weight 'bold))
              ": "
              (phundrak//eshell-banner--progress-bar ramp-length
                                                     (phundrak/mounted-partitions-percent part))
              (format " %6s / %-5s (%s%%)"
                      (phundrak/mounted-partitions-used part)
                      (phundrak/mounted-partitions-size part)
                      (phundrak//eshell-banner--color-percentage (phundrak/mounted-partitions-percent part)))))
    

    And we can now build our banner! Here is our function that does exactly that:

    (defun phundrak-eshell-banner ()
      (let* ((partitions        (phundrak/get-mounted-partitions))
             (os                (replace-regexp-in-string
                                 ".*\"\\(.+\\)\""
                                 "\\1"
                                 (car (-filter (lambda (line)
                                                 (s-contains? "PRETTY_NAME" line))
                                               (s-lines (phundrak-file-to-string "/etc/os-release"))))))
             (memory            (-map (lambda (line)
                                        (s-split " " line t))
                                      (s-split "\n"
                                               (shell-command-to-string "free -b | tail -2")
                                               t)))
             (ram               (nth 0 memory))
             (swap              (nth 1 memory))
             (ramp-length       41)
             (left-pad          (phundrak//eshell-banner--get-left-pad phundrak//eshell-banner--max-length-part partitions))
             (right-pad         8)
             (left-column-width 27))
        (concat (format "%s\n" (s-repeat 79 "="))
                ;; OS and Kernel
                (format "%s: %s%s: %s\n"
                        (s-pad-right left-pad "." "OS")
                        (s-pad-right left-column-width
                                     " "
                                     (with-face (s-trim os)
                                                :weight 'bold))
                        (s-pad-right right-pad "." "Kernel")
                        (with-face (concat "Linux " operating-system-release)
                                   :weight 'bold))
                ;; Hostname and Uptime
                (format "%s: %s%s: %s\n"
                        (s-pad-right left-pad "." "Hostname")
                        (s-pad-right left-column-width
                                     " "
                                     (with-face (system-name) :weight 'bold))
                        (s-pad-right right-pad "." "Uptime")
                        (with-face (s-chop-prefix "up "
                                                  (s-trim (shell-command-to-string "uptime -p")))
                                   :weight 'bold))
                ;; RAM ramp
                (phundrak//eshell-banner--display-memory "Ram"
                                                        (string-to-number (nth 2 ram))
                                                        (string-to-number (nth 1 ram))
                                                        left-pad
                                                        ramp-length)
                ;; SWAP ramp
                (phundrak//eshell-banner--display-memory "Swap"
                                                        (string-to-number (nth 2 swap))
                                                        (string-to-number (nth 1 swap))
                                                        left-pad
                                                        ramp-length)
                ;; Partitions
                (mapconcat (lambda (part)
                             (phundrak//eshell-banner--display-partition part left-pad ramp-length))
                           partitions
                           "\n")
                (format "\n%s\n" (s-repeat 79 "=")))))
    

    We now only have to set the result of this function as our Eshell banner. Since a simple setq would only run phundrak-eshell-banner once when Emacs starts, we’ll actually make Emacs set the value of eshell-banner-message each time it is required by Eshell with a hook:

    (add-hook 'eshell-banner-load-hook
              (lambda ()
                (setq eshell-banner-message (phundrak-eshell-banner))))
    
  5. Eshell theme and prompt

    As with most shells, again, it is possible to customize the appearance of the Eshell prompt. As you can see, my prompt has some Nord colors, a shortened path, a git prompt, and an indicator of whether the previous command succeeded or failed. Note however that the abbreviation of the current path depends on the value of phundrak-prompt--abbreviate, if it is t it is abbreviated; otherwise, it is kept in full. It can be toggled with a keyboard shortcut, see Keybinds: Toggle.

    (defun phundrak-eshell-prompt ()
      "Definition of my prompt for Eshell
    It displays a powerline prompt, with first an abbreviated path to
    the current directory. If `phundrak-prompt--abbreviate' is `t',
    then all preceding directories will be abbreviated to one
    character, except hidden directory which first character will be
    preceded by a dot. Otherwise, the full name of the directories is
    displayed.
    
    Then, if the current directory is a git repository or one of its
    subdirectories, it will display the current state of the
    repository. See `phundrak-eshell-git-status'
    
    Finally, a lambda character is displayed, either in blue or in
    red depending on if the last eshell command was a success or a
    failure respectively."
      (let* ((header-bg   phundrak-nord0)
             ($path       (phundrak-abbr-path (eshell/pwd)))
             ($git-path   (phundrak-git-repo-root $path))
             ($abbr-path  (phundrak-abbr-path $path phundrak-prompt--abbreviate))
             ($background phundrak-nord1)
             ($foreground phundrak-nord14)
             ($success    phundrak-nord10)
             ($error      phundrak-nord11))
        (concat (with-face (concat " "
                                   (phundrak-abbr-path (if $git-path
                                                           $git-path
                                                         $path)
                                                       phundrak-prompt--abbreviate)
                                   " ")
                           :foreground $foreground
                           :background $background)
                (when $git-path
                  (concat (phundrak-eshell-git-status $path $background)
                          (with-face (format "%s "
                                             (let (($in-git-path (phundrak-abbr-path (f-relative $path $git-path)
                                                                                     phundrak-prompt--abbreviate)))
                                               (if (string= "." $in-git-path)
                                                   ""
                                                 (concat " " $in-git-path))))
                                     :foreground $foreground
                                     :background $background)))
                (with-face "λ "
                           :foreground (if (zerop eshell-last-command-status)
                                           $success
                                         $error)
                           :background $background)
                (with-face "" :foreground $background)
                " ")))
    

    Now, let’s declare our prompt regexp and our prompt functions:

    (setq eshell-prompt-regexp "^[^\n]*λ  "
          eshell-prompt-function 'phundrak-eshell-prompt)
    

    I also don’t want the banner to be displayed, I know I entered the Elisp shell, no need to remind me. Maybe I’ll do something with it one day.

    (setq eshell-banner-message "")
    

    Finally, let’s enable some fish-like syntax highlighting:

    (eshell-syntax-highlighting-global-mode +1)
    
  6. Visual commands

    With Eshell, some commands don’t work very well, especially commands that create a TUI. So, let’s declare them as visual commands or subcommands:

    (setq eshell-visual-commands
          '("fish" "zsh" "bash" "tmux" "htop" "top" "vim" "bat" "nano")
          eshell-visual-subcommands
          '("git" "log" "l" "diff" "show"))
    

Org-mode

Org-mode is probably one of the best if not the best Emacs feature I have ever discovered. It is awesome for writing documents, regardless of the format you need it to be exported to, for agenda management, and for literary programming, such as with this document.

(with-eval-after-load 'org
  ;; configuration goes here
  )
  1. Agenda

    One awesome feature of Org mode is the agenda. By default, my agendas are stored in ~/org/agenda.

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

    I also have a custom command in Org agenda to mark some tasks as daily tasks with the :DAILY: tag,:

    (setq org-agenda-custom-commands
          '(("h" "Daily habits"
             ((agenda ""))
             ((org-agenda-show-log t)
              (org-agenda-ndays 7)
              (org-agenda-log-mode-items '(state))
              (org-agenda-skip-function
               '(org-agenda-skip-entry-if 'notregexp
                                          ":DAILY:"))))
            ("Y" "Yearly events"
             ((agenda ""))
             ((org-agenda-show-log t)
              (org-agenda-ndays 365)
              (org-agenda-log-mode-items '(state))
              (org-agenda-skip-entry-if 'notregexp
                                        ":YEARLY:")))))
    
  2. 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 | | dot | | emacs-lisp | | gnuplot | | latex | | latex-as-png | | makefile | | plantuml | | python | | restclient | | sass | | scheme | | shell |

    The corresponding code is as follows:

    (org-babel-do-load-languages
     'org-babel-load-languages
     '((C . t)
       (dot . t)
       (emacs-lisp . t)
       (gnuplot . t)
       (latex . t)
       (latex-as-png . t)
       (makefile . t)
       (plantuml . t)
       (python . t)
       (restclient . t)
       (sass . t)
       (scheme . t)
       (shell . t))
     )
    

    Scheme requires a default implementation for geiser:

    (setq geiser-default-implementation 'racket)
    

    By the way, I wish to see source code behave the same way in the source blocks as in their own major mode. Let’s tell Emacs so:

    (setq org-src-tab-acts-natively t)
    

    Lastly, I know this can be a terrible idea, but I want Emacs to just evaluate Org code blocks without asking me. Of course, this could represent some big security issue if not careful enough, but I generaly just open my own org files.

    (setq org-confirm-babel-evaluate nil)
    
  3. Beautify Org-mode

    As I will always say, orgmode is an amazing piece of software that deserves particular care and love. That is why I want to give it a unique look and feel compared to the rest of my Emacs configuration, in order to make it feel much more comfortable. You will find below how my org buffers look like when I open one of them.

    Screenshot of an org-mode buffer

    In order to make org-mode even sexier, let’s enable variable-pitch-mode for org-mode so we can get some proportional font. I’ll also remove auto-fill-mode which seems to stick to Orgmode like hell and I don’t know why.

    (add-hook 'org-mode-hook 'visual-line-mode)
    (remove-hook 'org-mode-hook 'auto-fill-mode)
    (add-hook 'org-mode-hook 'variable-pitch-mode)
    (auto-fill-mode -1)
    

    You can then see the modified faces for org-mode here.

    By default, I would like my org-mode buffers to be indented and tables to be aligned.

    (setq org-startup-indented t
          org-startup-align-all-tables t)
    
    1. Fontifying parts of org-mode

      Some blocks of org-mode should have their own face, such as the whole heading line, the done headline, the quote and the verse blocks,… actually, let’s enable that for all of them.

      (setq org-pretty-entities t
            org-fontify-whole-heading-line t
            org-fontify-done-headline t
            org-fontify-quote-and-verse-blocks t)
      
    2. Fontifying inline src blocks

      When it comes to source blocks in org-mode, Emacs handle them really well with some beautiful syntax highlight thanks to the the languages’ major mode and their font-locks. But inline src blocks are the forgotten child and get next to no love, which is really sad ; I want it to feel loved, to stand out from the crowd and to give me what its brother gives me already!

      Enters Tecosaur’s configopen in new window! With org-src-font-lock-fontify-block, anything’s possible! And {{{results(...)}}} can also have the org-block face applied to match and make org-mode even more beautiful! Let’s do it:

      (defvar org-prettify-inline-results t
        "Whether to use (ab)use prettify-symbols-mode on
      {{{results(...)}}}.")
      
      (defun org-fontify-inline-src-blocks (limit)
        "Try to apply `org-fontify-inline-src-blocks-1'."
        (condition-case nil
            (org-fontify-inline-src-blocks-1 limit)
          (error (message "Org mode fontification error in %S at %d"
                          (current-buffer)
                          (line-number-at-pos)))))
      
      (defun org-fontify-inline-src-blocks-1 (limit)
        "Fontify inline src_LANG blocks, from `point' up to `LIMIT'."
        (let ((case-fold-search t))
          (when
              ; stolen from `org-element-inline-src-block-parser'
              (re-search-forward "\\_<src_\\([^ \t\n[{]+\\)[{[]?" limit t)
            (let ((beg (match-beginning 0))
                  pt
                  (lang-beg (match-beginning 1))
                  (lang-end (match-end 1)))
              (remove-text-properties beg lang-end '(face nil))
              (font-lock-append-text-property lang-beg lang-end 'face 'org-meta-line)
              (font-lock-append-text-property beg lang-beg 'face 'shadow)
              (font-lock-append-text-property beg lang-end 'face 'org-block)
              (setq pt (goto-char lang-end))
              (when (org-element--parse-paired-brackets ?\[)
                (remove-text-properties pt (point) '(face nil))
                (font-lock-append-text-property pt
                                                (point)
                                                'face
                                                'org-block)
                (setq pt (point)))
              (when (org-element--parse-paired-brackets ?\{)
                (remove-text-properties pt (point) '(face nil))
                (font-lock-append-text-property pt
                                                (1+ pt)
                                                'face
                                                '(org-block shadow))
                (unless (= (1+ pt) (1- (point)))
                  (if org-src-fontify-natively
                      (org-src-font-lock-fontify-block
                       (buffer-substring-no-properties lang-beg
                                                       lang-end)
                       (1+ pt)
                       (1- (point)))
                    (font-lock-append-text-property (1+ pt)
                                                    (1- (point))
                                                    'face
                                                    'org-block)))
                (font-lock-append-text-property (1- (point))
                                                (point)
                                                'face
                                                '(org-block shadow))
                (setq pt (point)))
              (when (and org-prettify-inline-results
                         (re-search-forward "\\= {{{results(" limit t))
                (font-lock-append-text-property pt
                                                (1+ pt)
                                                'face
                                                'org-block)
                (goto-char pt))))
          (when (and org-prettify-inline-results
                     (re-search-forward "{{{results(\\(.+?\\))}}}"
                                        limit t))
            (remove-list-of-text-properties (match-beginning 0)
                                            (point)
                                            '(composition prettify-symbols-start prettify-symbols-end))
            (font-lock-append-text-property (match-beginning 0)
                                            (match-end 0)
                                            'face
                                            'org-block)
            (let ((start (match-beginning 0))
                  (end (match-beginning 1)))
              (with-silent-modifications (compose-region start end "⟨")
                                         (add-text-properties start
                                                              end
                                                              `(prettify-symbols-start ,start prettify-symbols-end
                                                                                       ,end))))
            (let ((start (match-end 1))
                  (end (point)))
              (with-silent-modifications (compose-region start end "⟩")
                                         (add-text-properties start
                                                              end
                                                              `(prettify-symbols-start ,start prettify-symbols-end
                                                                                       ,end)))))))
      
      (defun org-fontify-inline-src-blocks-enable ()
        "Add inline src fontification to font-lock in Org.
      Must be run as part of `org-font-lock-set-keywords-hook'."
        (setq org-font-lock-extra-keywords
              (append org-font-lock-extra-keywords '((org-fontify-inline-src-blocks)))))
      
      (add-hook 'org-font-lock-set-keywords-hook #'org-fontify-inline-src-blocks-enable)
      
    3. Images in org-mode

      By default, images should be displayed inline, but not with a too large width. I found that 550px fits well, since that is roughly the average width of the text when org-fill-paragraph is called. Let’s also tell org-mode to display images as inline images and redisplay them when needed.

      (setq org-image-actual-width 550
            org-redisplay-inline-images t
            org-display-inline-images t
            org-startup-with-inline-images "inlineimages")
      
    4. Prettier LaTeX inline rendering

      Tecosaur strikes againopen in new window! Let’s admit it, inline LaTeX code looks cool, properly formatted LaTeX inline fragments look rad! Let’s fix their appearance:

      (setq org-format-latex-header "\\documentclass{article}
      \\usepackage[usenames]{color}
      
      \\usepackage[T1]{fontenc}
      
      \\usepackage{booktabs}
      
      \\pagestyle{empty}             % do not remove
      % The settings below are copied from fullpage.sty
      \\setlength{\\textwidth}{\\paperwidth}
      \\addtolength{\\textwidth}{-3cm}
      \\setlength{\\oddsidemargin}{1.5cm}
      \\addtolength{\\oddsidemargin}{-2.54cm}
      \\setlength{\\evensidemargin}{\\oddsidemargin}
      \\setlength{\\textheight}{\\paperheight}
      \\addtolength{\\textheight}{-\\headheight}
      \\addtolength{\\textheight}{-\\headsep}
      \\addtolength{\\textheight}{-\\footskip}
      \\addtolength{\\textheight}{-3cm}
      \\setlength{\\topmargin}{1.5cm}
      \\addtolength{\\topmargin}{-2.54cm}
      % my custom stuff
      \\usepackage[nofont,plaindd]{bmc-maths}
      \\usepackage{arev}
      ")
      

      And I much prefer when LaTeX fragments are transparent, so let’s make them.

      (setq org-format-latex-options
            (plist-put org-format-latex-options :background "Transparent"))
      
    5. Symbols

      I visually prefer to have a nicer folding icon in Emacs and the markers of macros hidden.

      (setq org-hide-macro-markers t
            org-ellipsis " ")
      

      I also have an issue where small dots precede my org headers. Let’s fix that:

      (setq org-hide-leading-stars nil
            org-superstar-leading-bullet ?\s)
      
  4. Behavior

    Something really neat I learned about is the ability of org headers to inherit properties from parent headers. Let’s enable that!

    (setq org-use-property-inheritance t)
    

    Sometimes, I also want to have alphabetical lists in org-mode:

    (setq org-list-allow-alphabetical t)
    

    LSP can work in source blocks, but some work is needed (shamelessly stolen from hereopen in new window, though modified a tiny bit). Here are the languages I want to activate LSP for in this environment:

    | c | | c++ | | dart | | python | | rust |

    And here is the code to activate that:

    (cl-defmacro lsp-org-babel-enable (lang)
      "Support LANG in org source code block."
      (setq centaur-lsp 'lsp-mode)
      (cl-check-type lang stringp)
      (let* ((edit-pre (intern (format "org-babel-edit-prep:%s" lang)))
             (intern-pre (intern (format "lsp--%s" (symbol-name edit-pre)))))
        `(progn
           (defun ,intern-pre (info)
             (let ((file-name (->> info caddr (alist-get :file))))
               (unless file-name
                 (setq file-name (make-temp-file "babel-lsp-")))
               (setq buffer-file-name file-name)
               (lsp-deferred)))
           (put ',intern-pre 'function-documentation
                (format "Enable lsp-mode in the buffer of org source block (%s)."
                        (upcase ,lang)))
           (if (fboundp ',edit-pre)
               (advice-add ',edit-pre :after ',intern-pre)
             (progn
               (defun ,edit-pre (info)
                 (,intern-pre info))
               (put ',edit-pre 'function-documentation
                    (format "Prepare local buffer environment for org source block (%s)."
                            (upcase ,lang))))))))
    (defvar org-babel-lsp-lang-list
      '("c" "c++" "dart" "python" "rust"))
    (dolist (lang org-babel-lsp-lang-list)
      (eval `(lsp-org-babel-enable ,lang)))
    

    Here is one behavior that I really want to see modified: the ability to use M-RET without slicing the text the marker is on.

    (setq org-M-RET-may-split-line nil)
    

    Since Org 9.3, Org no longer attempts to restore the window configuration in the frame to which the user returns after editing a source block with org-edit-src-code. This means with the original value of org-src-window-setup (reorganize-frame), the current frame will be split in two between the original org window and the source window, and once we quit the source window only the org window will remain. This is not a desired behavior for me, so I chose to set this variable to split-window-right in order to keep my windows organization and have a similar behavior to the old one.

    (setq org-src-window-setup 'split-window-below)
    

    However, it is not rare that I want to change that for an horizontal split, which can be achieved with the value split-window-below. Thus, I have made this function that allows me to switch between the (default) vertical split and the horizontal 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")))
    

    When creating a link to an Org flie, I want to create an ID only if the link is created interactively, and only if there is no custom ID already created.

    (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
    

    The tag :noexport: is fine and all, but it doesn’t allow for hidden org structures, that is headers that are visible in the org buffer but once the file is exported to another format the header disappears but its content stays. ox-extra has such a feature through ignore-headlines.

    (require 'ox-extra)
    (ox-extras-activate '(ignore-headlines))
    

    This gives us access to the :ignore: tag which allows the behavior above mentioned.

  5. Capture

    Org-capture is an amazing feature of Org-mode which allows me to quickly save links, resources, reminders, and notes in neatly organized org files. Here they are described:

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

    With Spacemacs, an Org capture can be invoked with the shortcut SPC a o c. It will then ask which template I wish to use. In the table below are described the shortcuts that are available after SPC a o c is invoked. The name will be the one displayed in Org capture’s interface, the title is the headline where to save the capture (if it does not differ from the capture’s name, the cell will be blank). The insertion mode tells Emacs how to add the capture to the file, using which template. A line with no insertion mode, file, or template is just a category. All of the following insert entries to their org files, that is a new org node with a headline and some content.

    ShortcutNameTitleInsertion modefiletemplate
    eEmail
    ewWrite EmailEmailsfile+headlineorg-default-notes-fileemails.orgcaptmpl
    jJournalfile+datetreeorg-journal-filejournal.orgcaptmpl
    lLink
    llGeneralfile+headlineorg-default-notes-filelink.orgcaptmpl
    lyYouTubefile+headlineorg-default-notes-fileyoutube.orgcaptmpl
    LProtocol LinkLinkfile+headlineorg-default-notes-fileprotocol-link.orgcaptmpl
    nNotes
    ncConlangingNotefile+headlineorg-conlanging-filenotes.orgcaptmpl
    nnGeneralfile+headlineorg-default-notes-filenotes.orgcaptmpl
    nNNovelNotefile+headlineorg-novel-notes-filenotes.orgcaptmpl
    nqQuotefile+headlineorg-default-notes-filenotes-quote.orgcaptmpl
    nwWorldbuildingNotefile+headlineorg-wordbuilding-filenotes.orgcaptmpl
    NNovel
    NiIdeasfile+headlineorg-novel-notes-filenotes.orgcaptmpl
    pProtocolLinkfile+headlineorg-default-notes-fileprotocol.orgcaptmpl
    rResources
    rcConlangingResourcesfile+headlineorg-conlanging-fileresource.orgcaptmpl
    reEmacsfile+headlineorg-default-notes-fileresource.orgcaptmpl
    riInformatiquefile+headlineorg-default-notes-fileresource.orgcaptmpl
    rlLinguisticsfile+headlineorg-default-notes-fileresource.orgcaptmpl
    rLLinuxfile+headlineorg-default-notes-fileresource.orgcaptmpl
    rwWorldbuildingResourcesfile+headlineorg-wordbuilding-fileresource.orgcaptmpl
    tTasks
    tbBirthdayfile+headlineorg-private-agenda-filebirthday.orgcaptmpl
    teEventfile+headlineorg-private-agenda-fileevent.orgcaptmpl
    thHealthfile+headlineorg-private-agenda-filehealth.orgcaptmpl
    tiInformatiquefile+headlineorg-private-agenda-fileinformatique.orgcaptmpl

    Below you can find the equivalent code as described above.

    (setq
     org-capture-templates
     '(("e" "Email")
       ("ew" "Write Email" entry
         (file+headline org-default-notes-file "Emails")
         (file "~/org/capture/emails.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"))))
    

    You may notice a capture entry for my journal, and this is due to the fact I do not use org-journal anymore: it was too overpowered for me, and I prefer to keep it simple with a single file. And as you can see, and unlike a lot of other Emacs configurations, the content of the template is not set in the variable, but in external files which can be modified freely as actual Org buffers instead of trying to get a proper one with loads of \n characters and such. All these templates are declared below.

    My org capture templates are not tangled into my Emacs configuration files, but into separate .orgcaptmpl files stored into ~/org/capture/. You can find these in my repository hereopen in new window or hereopen in new window.

  6. Custom org-mode functions

    We begin with a couple of custom functions that I use in my org-mode files.

    1. Custom and unique headings ID

      The first ones are dedicated to provide org-mode headings a fixed and unique ID that won’t change over time. This code was taken from https://writequit.org/articles/emacs-org-mode-generate-ids.htmlopen in new window. The first function’s job is to create these unique IDs

      (defun eos/org-id-new (&optional prefix)
        "Create a new globally unique ID.
      
      An ID consists of two parts separated by a colon:
      - a prefix
      - a unique part that will be created according to
        `org-id-method'.
      
      PREFIX can specify the prefix, the default is given by the
      variable `org-id-prefix'. However, if PREFIX is the symbol
      `none', don't use any prefix even if `org-id-prefix' specifies
      one.
      
      So a typical ID could look like \"Org-4nd91V40HI\"."
        (let* ((prefix (if (eq prefix 'none)
                           ""
                         (concat (or prefix org-id-prefix)
                                 "-"))) unique)
          (when (equal prefix "-")
            (setq prefix ""))
          (cond
           ((memq org-id-method
                  '(uuidgen uuid))
            (setq unique (org-trim (shell-command-to-string org-id-uuid-program)))
            (unless (org-uuidgen-p unique)
              (setq unique (org-id-uuid))))
           ((eq org-id-method 'org)
            (let* ((etime (org-reverse-string (org-id-time-to-b36)))
                   (postfix (when org-id-include-domain
                              (progn
                                (require 'message)
                                (concat "@"
                                        (message-make-fqdn))))))
              (setq unique (concat etime postfix))))
           (t (error "Invalid `org-id-method'")))
          (concat prefix (car (split-string unique "-")))))
      

      Now, let’s see the function that will be used to get the custom id of a heading at point. If the function does not detect any custom ID, then one should be created and inserted.

      (defun eos/org-custom-id-get (&optional pom create prefix)
        "Get the CUSTOM_ID property of the entry at point-or-marker POM.
      If POM is nil, refer to the entry at point. If the entry does not
      have an CUSTOM_ID, the function returns nil. However, when CREATE
      is non nil, create a CUSTOM_ID if none is present already. PREFIX
      will be passed through to `eos/org-id-new'. In any case, the
      CUSTOM_ID of the entry is returned."
        (interactive)
        (org-with-point-at pom
          (let* ((orgpath (mapconcat #'identity (org-get-outline-path) "-"))
                 (heading (replace-regexp-in-string
                           "[_-]+$" ""
                           (replace-regexp-in-string
                            "[-_]+" "-"
                            (replace-regexp-in-string
                             "[^a-zA-Z0-9-_]" "-"
                             (if (string= orgpath "")
                                 (org-get-heading t t t t)
                               (concat orgpath "_" (org-get-heading t t t t)))))))
                 (id (org-entry-get nil "CUSTOM_ID")))
            (cond
             ((and id
                   (stringp id)
                   (string-match "\\S-" id)) id)
             (create (setq id (eos/org-id-new (concat prefix heading)))
                     (org-entry-put pom "CUSTOM_ID" id)
                     (org-id-add-location id
                                          (buffer-file-name (buffer-base-buffer)))
                     id)))))
      

      Finally, this is the function that gets called on file saves. If the function detects auto-id:t among the org options in the #+OPTIONS: header, then the above function is called.

      (defun eos/org-add-ids-to-headlines-in-file ()
        "Add CUSTOM_ID properties to all headlines in the current file
      which do not already have one.
      
      Only adds ids if the `auto-id' option is set to `t' in the file
      somewhere. ie, #+OPTIONS: auto-id:t"
        (interactive)
        (save-excursion
          (widen)
          (goto-char (point-min))
          (when (re-search-forward "^#\\+OPTIONS:.*auto-id:t" (point-max) t)
            (org-map-entries (lambda () (eos/org-custom-id-get (point) 'create))))))
      

      Let’s add a hook to the above function so it is called automatically on save, and only in read-write functions.

      (add-hook 'org-mode-hook
                (lambda ()
                  (add-hook 'before-save-hook
                            (lambda ()
                              (when (and (eq major-mode 'org-mode)
                                         (eq buffer-read-only nil))
                                (eos/org-add-ids-to-headlines-in-file))))))
      
  7. File export

    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, let’s disable it:

    (setq org-use-sub-superscripts (quote {}))
    
    1. 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. I also added two default packages, minted and xeCJK for syntax highlighting and Japanese (and additionally Chinese and Korean) support.

      (setq org-latex-default-packages-alist '((""         "graphicx"  t)
                                               ("T1"       "fontspec"  t ("pdflatex"))
                                               (""         "longtable" nil)
                                               (""         "wrapfig"   nil)
                                               (""         "rotating"  nil)
                                               ("normalem" "ulem"      t)
                                               (""         "amsmath"   t)
                                               (""         "textcomp"  t)
                                               (""         "amssymb"   t)
                                               (""         "capt-of"   nil)
                                               (""         "minted"    nil)
                                               (""         "hyperref"  nil)))
      

      By the way, reference links in LaTeX should be written in this format:

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

      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. Don’t 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"))
      
    2. HTML

      For Reveal.JS exports, I need to set where to find the framework by default:

      (setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js")
      

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

      (setq org-html-validation-link nil)
      

      However, something I very often use are metadata information in my HTML files. I just want to automate that. Fortunately, Tecosaur comes to the rescueopen in new window! First, here is our function that will generate all the meta tags in our HTML:

      (defun org-html-meta-tags-fancy (info)
        "Use the INFO plist to construct the meta tags, as described in
      `org-html-meta-tags'."
        (message "%s" info)
        (let ((title (org-html-plain-text
                      (org-element-interpret-data (plist-get info :title)) info))
              (author (and (plist-get info :with-author)
                           (let ((auth (plist-get info :author)))
                             ;; Return raw Org syntax.
                             (and auth (org-html-plain-text
                                        (org-element-interpret-data auth) info))))))
          (list
           (when (org-string-nw-p author)
             (list "name" "author" author))
           (when (org-string-nw-p (plist-get info :description))
             (list "name" "description"
                   (plist-get info :description)))
           '("name" "generator" "org mode (Emacs)")
           '("name" "theme-color" "#3b4252")
           '("property" "og:type" "article")
           (list "property" "og:title" title)
           (let ((subtitle (org-export-data (plist-get info :subtitle) info)))
             (when (org-string-nw-p subtitle)
               (list "property" "og:description" subtitle)))
           (let ((meta-image (org-export-data (plist-get info :metaimage) info)))
             (when (org-string-nw-p meta-image)
               (list "property" "og:image" meta-image)))
           (let ((meta-image-type (org-export-data (plist-get info :metaimagetype) info)))
             (when (org-string-nw-p meta-image-type)
               (list "property" "og:image:type" meta-image-type)))
           (let ((meta-image-width (org-export-data (plist-get info :metaimagewidth) info)))
             (when (org-string-nw-p meta-image-width)
               (list "property" "og:image:width" meta-image-width)))
           (let ((meta-image-height  (org-export-data (plist-get info :metaimageheight) info)))
             (when (org-string-nw-p meta-image-height)
               (list "property" "og:image:height" meta-image-height)))
           (let ((meta-image-alt  (org-export-data (plist-get info :metaimagealt) info)))
             (when (org-string-nw-p meta-image-alt)
               (list "property" "og:image:alt" meta-image-alt)))
           (when (org-string-nw-p author)
             (list "property" "og:article:author:first_name" (car (s-split-up-to " " author 2))))
           (when (and (org-string-nw-p author) (s-contains-p " " author))
             (list "property" "og:article:author:last_name" (cadr (s-split-up-to " " author 2))))
           (list "property" "og:article:published_time" (format-time-string "%FT%T%z")))))
      

      This will use some special keywords in our org buffer and insert their content in

      Now let’s bind it to when we export our org buffer to HTML:

      (unless (functionp #'org-html-meta-tags-default)
        (defalias 'org-html-meta-tags-default #'ignore))
      (setq org-html-meta-tags #'org-html-meta-tags-fancy)
      
  8. 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
                      '("conlang"
                        "\\documentclass{book}"
                        ("\\chapter{%s}" . "\\chapter*{%s}")
                        ("\\section{%s}" . "\\section*{%s}")
                        ("\\subsection{%s}" . "\\subsection*{%s}")
                        ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
                      )
         (add-to-list 'org-latex-classes
                      `("beamer"
                        ,(concat "\\documentclass[presentation]{beamer}\n"
                                 "[DEFAULT-PACKAGES]"
                                 "[PACKAGES]"
                                 "[EXTRA]\n")
                        ("\\section{%s}" . "\\section*{%s}")
                        ("\\subsection{%s}" . "\\subsection*{%s}")
                        ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
                      )))
    
  9. 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:

    (defvar phundrak//projects-config-target
      "/rsync: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 config.phundrak.com")
    (defvar phundrak//projects-config-recursive
      t
      "Defines whether subdirectories should be parsed for config.phundrak.com")
    (defvar phundrak//projects-conlanging-target
      "/rsync: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")
    (setq org-publish-project-alist
          `(
            ("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)
            ("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)
            ("config-website"
             :components ("config-website-org"
                          "config-website-static"))
            ("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)
            ("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)
            ("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)
            ("langue-phundrak-com"
             :components ("langue-phundrak-com-org"
                          "langue-phundrak-com-static"
                          "langue-phundrak-com-pdf"))))
    
    1. 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
        "/rsync: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 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"))
      
    2. Linguistics website

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

      (defvar phundrak//projects-conlanging-target
        "/rsync: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"))
      
  10. User information

    Some variables about myself need to be set so Org-mode knows what information to include in exported files.

    (setq user-full-name "Lucien Cartier-Tilet"
          user-real-login-name "Lucien Cartier-Tilet"
          user-login-name "phundrak"
          user-mail-address "[email protected]")
    

Recentf

recentf-mode allows Emacs to list all recent files it read. It is also used by Spacemacs to display a list of recent files so they can be quickly opened by the user. Unfortunately, a lot of these files are just noise I don’t care about, but fortunately we can ignore files with the variable recentf-exclude. So, I will ignore these paths:

| ~/.authinfo.gpg | | ~/.mail/ | | ~/.emacs.d/ | | ~/.emacs.spacemacs/ | | ~/.elfeed/index | | ~/Documents/mu4e | | /tmp/ |

(with-eval-after-load 'recentf
  (add-to-list 'recentf-exclude
               (expand-file-name "~/.authinfo.gpg"))
  (add-to-list 'recentf-exclude
               (expand-file-name "~/.mail/"))
  (add-to-list 'recentf-exclude
               (expand-file-name "~/.emacs.d/"))
  (add-to-list 'recentf-exclude
               (expand-file-name "~/.emacs.spacemacs/"))
  (add-to-list 'recentf-exclude
               (expand-file-name "~/.elfeed/index"))
  (add-to-list 'recentf-exclude
               (expand-file-name "~/Documents/mu4e"))
  (add-to-list 'recentf-exclude
               (expand-file-name "/tmp/")))

Keybindings

As you will see, I defined a LOT of custom keybindings. All of them are Spacemacs keybindings, defined in a way they can be used seamlessly with Evil. They almost all begin with o, which is a prefix reserved for user-defined keybindings so they won’t conflict with any package. Let’s declare it like so.

(spacemacs/declare-prefix "o" "custom")

Now, all keybindings that will be defined can be invoked in Normal-mode with the SPC key followed by the sequence assigned to each keybinding.

Before some more specialized categories, I have two commands which don’t fit into any other category that I sometime use. The first one is a fix for the Bépo keybindings which left out a keybind: winum-select-window-by-number is still bound to SPC ², which is not a key that is available on the bépo layout (instead, we use the dead key ^ followed by 2, or any digits). So instead, let’s use the key that is physically in the same place: $.

(spacemacs/declare-prefix "$" "select window by number")
(spacemacs/set-leader-keys "$"  'winum-select-window-by-number)

The following, I use it rarely, it can launch an external command from Emacs to launch, for instance, my web browser or any other software not related to Emacs. It offers a similar interface to dmenuopen in new window through helm.

(spacemacs/declare-prefix "or" "external command")
(spacemacs/set-leader-keys "or" 'helm-run-external-command)

However this one I use often, generally in org or text buffers.

(spacemacs/set-leader-keys "os" 'sort-lines)

Applications

As this is a new category, let’s declare its prefix:

(spacemacs/declare-prefix "oa" "applications")

Now, let’s also declare the keybindings in this category. oac will invoke Emacs’ calculator, while oac invokes the calendar, oae invokes the Eww web browser, and oaw invokes the weather forecast. Lastly, the apostrophe in o' will invoke Eshell directly, without any popup window as with SPC ' while oan will open a new eshell buffer if another one already exists. ov will also open a vterm terminal.

(spacemacs/set-leader-keys
  "o'" 'eshell-new
  "ov" 'vterm
  "oac" 'calc
  "oaC" 'calendar
  "oae" 'eww
  "oaw" 'wttrin)
  1. Image mode

    Viewing images in Emacs is nice, but I want to be able to do more than just view them, such as opening them in GIMP. I’ll also declare a couple of keybindings that make sense to me.

    (spacemacs/declare-prefix-for-mode 'image-mode "G" "Open in GIMP")
    (spacemacs/declare-prefix-for-mode 'image-mode "o" "Open in external viewer")
    (spacemacs/declare-prefix-for-mode 'image-mode "r" "Rotate clockwise")
    (spacemacs/set-leader-keys-for-major-mode 'image-mode
      "G" (lambda () (interactive) (start-process "" nil "gimp" (buffer-name)))
      "o" (lambda () (interactive) (start-process "" nil "xdg-open" (buffer-name)))
      "r" 'image-rotate)
    
  2. Org tree slide

    Finally, here we have the keybindings for org-tree-slide, a presentation mode with orgmode. Since I want the keys to be directly accessible without any prefix from Spacemacs, I’ll have to declare them the vanilla way. First we have keybindings that will launch the presentation:

    (define-key org-mode-map (kbd "<f8>") 'org-tree-slide-mode)
    (define-key org-mode-map (kbd "s-<f8>") 'org-tree-slide-skip-done-toggle)
    

    Next, we have some additional keybindings that will only be active when in org-tree-slide-mode. The first one will allow us to exit this mode, while the second one will toggle the display of headers marked as DONE. Next, we have F9 and F10 which are bound to movement in the slide, while F11 changes the way the content is displayed. We also set org-tree-slide-skip-outline-level to set the maximum depth we will display as an individual heading during the presentation.

    (when (require 'org-tree-slide nil t)
      (global-set-key (kbd "<f8>") 'org-tree-slide-mode)
      (global-set-key (kbd "S-<f8>") 'org-tree-slide-skip-done-toggle)
      (define-key org-tree-slide-mode-map (kbd "<f9>")
        'org-tree-slide-move-previous-tree)
      (define-key org-tree-slide-mode-map (kbd "<f10>")
        'org-tree-slide-move-next-tree)
      (define-key org-tree-slide-mode-map (kbd "<f11>")
        'org-tree-slide-content)
      (setq org-tree-slide-skip-outline-level 4)
      (org-tree-slide-narrowing-control-profile)
      (setq org-tree-slide-skip-done nil))
    

Comments

Some keybindings are also related to comment editing, in particular using outorg. Let’s first declare the dedicated prefix:

(spacemacs/declare-prefix "oc" "comments")

Now, let’s declare the following keybindings:

(spacemacs/set-leader-keys
  "occ" 'outorg-copy-edits-and-exit
  "oce" 'outorg-edit-as-org
  "oco" 'outline-minor-mode)

oco enables the outline minor mode, which then allows for the edition of comments in org buffers with oce and saving them to the original source file with occ.

Dired

A couple of keybindings will be added to Dired. The first one is the opening parenthesis which will enable or disable dired-hide-details-mode. On the other hand, a closing parenthesis will show git information in the current Dired buffer.

(define-key dired-mode-map (kbd "(") 'dired-hide-details-mode)
(define-key dired-mode-map (kbd ")") 'dired-git-info-mode)

Something I use from time to time is S-F1 for opening dired in my $HOME directory. For that, I simply did the following:

(global-set-key (kbd "<s-f1>") (lambda () (interactive) (dired "~/")))

A couple of other useful utilities, sach as opening all marked files, sorting files, opening them externally and renaming them, are also bound to a simple key press:

(define-key dired-mode-map (kbd "f") 'phundrak-open-marked-files)
(define-key dired-mode-map (kbd "F") 'xah/open-in-external-app)
(define-key dired-mode-map (kbd "s") 'xah/dired-sort)

Files

(spacemacs/declare-prefix "of" "open org file")
(spacemacs/set-leader-keys "of" 'phundrak-find-org-files)

I also have a shortcut for helm-locate in case I need to find a file that is not in these directories. One advantage of this over helm-find is that it doesn’t matter from where I call it, it will find any file on my system that matches the query, whereas helm-find will only search in the current directory and its subdirectories. This time, the declaration is much simpler:

(spacemacs/declare-prefix "oF" "locate file")
(spacemacs/set-leader-keys "oF" 'helm-locate)

And that’s it! This should list all my org files under these directories and give me fuzzy finding for these files. I just need to partially type the name of the file I want to open and it should open without any issue.

Games

Just to make it easier to launch it, I’ll declare a shortcut for launching tetris (which is built into Emacs).

(spacemacs/declare-prefix "oat" "tetris")
(spacemacs/set-leader-keys "oat" 'tetris)

Apparently, no evil keybindings are set for Tetris. Let’s declare them (adapted to the bépo layout):

(require 'tetris)
(define-key tetris-mode-map (kbd "c") 'tetris-move-left)
(define-key tetris-mode-map (kbd "t") 'tetris-move-down)
(define-key tetris-mode-map (kbd "s") 'tetris-rotate-prev)
(define-key tetris-mode-map (kbd "r") 'tetris-move-right)

Multiple cursors

I don’t really like Spacemacs’ layer for MultipleCursors, so I prefer to simply install the package and create shortcuts for it myself. Let’s first declare category:

(spacemacs/declare-prefix "om" "multiple-cursors")

Now, let’s declare the shortcuts related to multiple-cursors:

(spacemacs/set-leader-keys
  "ome" 'mc/edit-lines
  "omn" 'mc/mark-next-like-this
  "omp" 'mc/mark-previous-like-this
  "oma" 'mc/mark-all-like-this)

Org-mode

Now, onto some shortcuts related to org-mode. Let’s first declare the category:

(spacemacs/declare-prefix-for-mode 'org-mode "mo" "custom" "User-defined keybindings")
(spacemacs/declare-prefix-for-mode 'org-mode "mot" "toggle" "Toggle org elements")
(spacemacs/declare-prefix-for-mode 'org-mode "moT" "tables")
(spacemacs/set-leader-keys-for-major-mode 'org-mode "ob" 'phundrak-blog-publish)
(spacemacs/declare-prefix-for-mode 'org-mode "ob" "publish blog")

Now, I have a couple of shortcuts I use regularly:

(spacemacs/set-leader-keys-for-major-mode 'org-mode
  "os" 'org-insert-structure-template
  "ots" 'phundrak/toggle-org-src-window-split
  "ott" 'org-sidebar-tree)
(spacemacs/declare-prefix-for-mode 'org-mode "moS" "insert template")
(spacemacs/declare-prefix-for-mode 'org-mode "mots" "toggle src split")

os allows me to insert an org structure template defined in org-structure-template-alist (see org-mode behavior), while ott displays the outline of the current org file.

oT is the prefix for tree-related operations:

(spacemacs/declare-prefix-for-mode 'org-mode "moT" "tables")

These shortcuts allow to manipulate the width of the column the cursor is currently in, by either shrinking it, expanding it, or toggling its state between shrunk or expanded. A prefix for all of these commands has been also added in order to make the purpose of the shortcuts clearer.

(spacemacs/set-leader-keys-for-major-mode 'org-mode
  "oTt" 'org-table-toggle-column-width
  "oTe" 'org-table-expand
  "oTs" 'org-table-shrink)
(spacemacs/declare-prefix-for-mode 'org-mode "moTt" "toggle width")
(spacemacs/declare-prefix-for-mode 'org-mode "moTe" "expand")
(spacemacs/declare-prefix-for-mode 'org-mode "moTs" "shrink")

Finaly, I set the following shortcut in order to easily remove RESULTS blocks from org source code blocks:

(spacemacs/set-leader-keys-for-major-mode 'org-mode
  "or" 'org-babel-remove-result-one-or-many)
(spacemacs/declare-prefix-for-mode 'org-mode "mor" "remove org result")

Toggle

This category allows to toggle some modes and options.

(spacemacs/declare-prefix "ot" "toggle")

As you can see, I have here four shortcuts for toggling various elements in Emacs:

  • otb: toggles fancy-battery-mode. This comes in very handy when I am on a laptop that is not pluged in or which is charging.
  • otd: toggles elcord-mode. This mode is used to create an Emacs rich integration in Discord.
  • otf: toggles the activation of FlyCheck, Emacs’ spell checker. It is by default disabled, and I can turn it on with this shortcut only when needed.
  • ots: toggles prettify-symbols-mode. This allows Emacs to replace some symbols by some others, like for example by replacing lambda in Emacs Lisp buffers with an actual λ.
  • otS: toggles whether or not Eshell should shorten the current path in its prompt
(spacemacs/set-leader-keys
  "otb" 'fancy-battery-mode
  "otd" 'elcord-mode
  "otf" 'flycheck-mode
  "ots" 'prettify-symbols-mode
  "otS" 'phundrak-prompt-toggle-abbreviation)

We also have some input methods-related shortcuts in a sub-category: oti. The first shortcuts below are used to either toggle between no input method or the last one used (otit), or choose an input method among the various available ones from Emacs (otis).

(spacemacs/declare-prefix "oti" "input methods")
(spacemacs/set-leader-keys
  "otit"  'toggle-input-method
  "otis"  'set-input-method)

The shortcuts below though allow me to directly switch to one of these three known input methods I sometimes or often use, namely Japanese, Tibetan and IPA (by typing in X-SAMPA).

(spacemacs/declare-prefix "otij" "Japanese")
(spacemacs/declare-prefix "otix" "IPA (X-SAMPA)")
(spacemacs/declare-prefix "otiT" "Tibetan")
(spacemacs/set-leader-keys
  "otij" (lambda () (interactive) (set-input-method 'japanese))
  "otix" (lambda () (interactive) (set-input-method 'ipa-x-sampa))
  "otiT" (lambda () (interactive) (set-input-method 'tibetan-wylie)))

Mu4e

Mu4e is a frontend for mu, an email analyzer which sits on top of a Maildir which gets updated with the mbsync command from isync. It has a lot of neat features, but I guess my favorite ones are:

  1. the search query feature
  2. rendering an HTML email in the browser

Setup

Due to mu sitting on top of a maildir, I need to tell mu4e where said maildir is, and point it the trash, archive, and sent folders as well as the refresh command and how frequently I want my emails to be refreshed.

(setq mu4e-maildir "~/.mail"
      mu4e-trash-folder "/Trash"
      mu4e-refile-folder "/Archive"
      mu4e-sent-folder "/Sent"
      mu4e-drafts-folder "/Drafts"
      mu4e-get-mail-command "mbsync -a"
      mu4e-update-interval 60)

This source block is an example of the search queries in mu4e, and part of the reason why I very much like mu4e: these bookmarks are actually defined by search queries, but act as if they were just yet another type of custom inbox you get with modern Email client (and often you don’t even get them). All these bookmarks can be accessed through a shortcut on the main mu4e buffer, prefixed by b. So, for instance, my unread messages are accessed through bU.

(setq mu4e-bookmarks
      `((,(s-join " "
                  '("NOT flag:trashed"
                    "AND (maildir:/Inbox OR maildir:/Junk)"
                    "AND NOT to:CONLANG@LISTSERV.BROWN.EDU"
                    "AND NOT to:AUXLANG@LISTSERV.BROWN.EDU"
                    "AND NOT to:[email protected]"
                    "AND NOT to:[email protected]"
                    "AND NOT list:ateliers-emacs.framalistes.org"
                    "AND NOT list:ateliers-paris.emacs-doctor.com"))
         "Inbox" ?i) ;; Inbox without the linguistics mailing lists
        (,(s-join " "
                  '("NOT flag:trashed"
                    "AND (maildir:/Inbox OR maildir:/Junk)"
                    "AND (f:/.*up8\.edu|.*univ-paris8.*/"
                    "OR c:/.*up8\.edu|.*univ-paris8.*/"
                    "OR t:/.*up8\.edu|.*univ-paris8.*/)"))
         "University" ?u) ;; University-related emails
        (,(s-join " "
                  '("to:CONLANG@LISTSERV.BROWN.EDU"
                    "OR to:AUXLANG@LISTSERV.BROWN.EDU"))
         "Linguistics" ?l) ;; linguistics mailing lists
        (,(s-join " "
                  '("list:ateliers-emacs.framalistes.org"
                    "OR to:[email protected]"
                    "OR list:ateliers-paris.emacs-doctor.com"))
         "Emacs" ?e) ;; Emacs mailing list
        ("maildir:/Sent" "Sent messages" ?s)
        ("flag:unread AND NOT flag:trashed" "Unread messages" ?U)
        ("date:today..now AND NOT flag:trashed" "Today's messages" ?t)
        ("date:7d..now AND NOT flag:trashed" "Last 7 days" ?w)
        ("date:1m..now AND NOT flag:trashed" "Last month" ?m)
        ("date:1y..now AND NOT flag:trashed" "Last year" ?y)
        ("flag:trashed AND NOT flag:trashed" "Trash" ?T)
        ("mime:image/* AND NOT flag:trashed" "Messages with images" ?p)))

On new email arrival, Emacs can send the system a notification which will be handled as any other notification received by the system and will display the number of unread emails to the user; in my case, notifications are handled by AwesomeWM.

(setq mu4e-enable-notifications t
      mu4e-alert-email-notification-types '(count))
(with-eval-after-load 'mu4e-alert
  (mu4e-alert-set-default-style 'notifications))
(add-hook 'mu4e-view-mode-hook 'visual-line-mode)

This is the setup I have for my SMTP mail server: I point Emacs’ SMTP services to my private mail server on its SMTP port, which should be used with a STARTTLS stream. And I tell Emacs this is the default way to send an email.

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

I wish my emails to be signed by default using PGP/MIME. mu4e uses message for composing new emails, so I simply need to add the function that will add the signature to emails to the hook called when creating a new email.

(add-hook 'mu4e-compose-mode-hook 'mml-secure-message-sign-pgpmime)

mu4e used to be able to export emails to PDFs, but unfortunately this possibility was discontinued. But we can (sort of) bring it back!

(defun mu4e-action-open-as-pdf (msg)
  "Export and open as PDF a mu4e `MSG'"
  (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)))

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

Lastly, some emails are better displayed in a browser than in Emacs. My Emacs build has the webkit browser enabled, so I’ll add an option to open with it emails.

(defun phundrak/mu4e-view-in-browser (msg)
  (xwidget-webkit-browse-url (concat "file://"
                                     (mu4e~write-body-to-html msg))))

(add-to-list 'mu4e-view-actions
             '("Xwidget Webkit Browser" . phundrak/mu4e-view-in-browser)
             t)

Visual Configuration

The following also allows me to automatically include my signature in my Emails, to view images in my Emacs buffers and to show me the address of my contacts and not just their names.

(setq mu4e-compose-signature-auto-include t
      mu4e-view-show-images t
      mu4e-view-prefer-html t
      mu4e-view-show-addresses t)

Now this hook is added so I can get a maximal width for the text of my emails, I really don’t like it when lines are kilometers long. I would like instead to hook visual-line-mode and auto-fill-mode, but for some reasons Emacs throws an error when I add them, So I go with visual-fill-column-mode instead.

(add-hook 'mu4e-view-mode-hook 'visual-fill-column-mode)

Icons are nice and all, but my current font does not display some of the default icons set by mu4e. Due to this, I will define back these icons to the original characters defined by mu4e:

(setq mu4e-headers-draft-mark     '("D"  . "D")
      mu4e-headers-flagged-mark   '("F"  . "F")
      mu4e-headers-new-mark       '("N"  . "N")
      mu4e-headers-passed-mark    '("P"  . "P")
      mu4e-headers-replied-mark   '("R"  . "R")
      mu4e-headers-seen-mark      '("S"  . "S")
      mu4e-headers-trashed-mark   '("T"  . "T")
      mu4e-headers-attach-mark    '("a"  . "a")
      mu4e-headers-encrypted-mark '("x"  . "x")
      mu4e-headers-signed-mark    '("s"  . "s")
      mu4e-headers-unread-mark    '("u"  . "u"))

I don’t like the American time format. I really don’t. I prefer much more something more standard, like ISO8601 standard. Not exactly ISO8601, but close to it. Also, fuck the paywalls imposed by ISO.

(setq mu4e-view-date-format "%Y-%m-%d %R"
      mu4e-headers-date-format  "%Y-%m-%d")

Misc

I am unsure yet if this has any effect on mu4e, but this variable should discourage mu4e from reading rich text emails and instead open them as plain text. However, I do not wish to discourage opening HTML emails since I can open them in the browser.

(setq mm-discouraged-alternatives '("text/richtext"))

Miscellaneous

I have a lot of variables that need to be set but don’t fall in any other category, so I’ll collect them here.

I have this regexp for detecting paragraphs.

(setq paragraph-start "\f\\|[ \t]*$\\|[ \t]*[-+*] ")

And this variable for Elcord so the main icon displayed in Discord is the icon representing the current major-mode. I also don’t want to display the small icon, so let’s get rid of that.

(setq elcord-use-major-mode-as-main-icon t
      elcord-show-small-icon nil)

Pinentry

Pinentry should use the loopback mode when communicating with GnuPG. Let’s set it so:

(setq epg-pinentry-mode 'loopback)

Wttr.inopen in new window

Thanks to the wttrin package, I can get the weather forecast in Emacs for a couple of cities. I just need to specify them to Emacs like so:

(setq wttrin-default-cities '("Aubervilliers" "Paris" "Lyon" "Nonières"
                              "Saint Agrève"))

However, the package is currently broken (it was last updated in 2017): wttr.inopen in new window now returns by default an HTML page instead of an ASCII result. In order to fix it, a ?A must be added at the end of the request in order to get a nice output. Also, let’s use the HTTPS protocol while we’re at it.

(defun wttrin-fetch-raw-string (query)
  "Get the weather information based on your QUERY."
  (let ((url-user-agent "curl"))
    (add-to-list 'url-request-extra-headers wttrin-default-accept-language)
    (with-current-buffer
        (url-retrieve-synchronously
         (format "http%s://wttr.in/%s?A"
                 (if (gnutls-available-p) "s" "")
                 query)
         (lambda (status)
           (switch-to-buffer (current-buffer))))
      (decode-coding-string (buffer-string)
                            'utf-8))))

Nov-mode

nov-mode is the mode used in the Epub reader. Here I will write a little function that I will call through a hook each time I’m opening a new EPUB file.

(defun my-nov-font-setup ()
  (face-remap-add-relative 'variable-pitch :family "Charis SIL"
                           :size 16
                           :height 1.0))

Let’s bind this function to the nov-mode hook. By the way, we’ll also enable the visual-line-mode here, just in case.

(mapc (lambda (mode)
        (add-hook 'nov-mode-hook mode))
      '('my-nov-font-setup 'visual-line-mode))

Let’s also set the maximum length of the lines in nov-mode:

(setq nov-text-width 80)

Programming

LSP

When it comes to the LSP layer, there are some options which are not enabled by default that I want to use, especially some modes I want to take advantage of. This is why I enable first the lsp-treemacs-sync-mode so treemacs is LSP aware:

(lsp-treemacs-sync-mode 1)

I also enable some layers related to dap, the Debug Adapter Protocol, which works really nicely with LSP. Let’s enable Dap’s modes:

(dap-mode 1)
(dap-ui-mode 1)
(dap-tooltip-mode 1)

Finally, I also want the documentation tooltip to show up when the cursor is above a documented piece of code or symbol. Let’s enable that too:

(tooltip-mode 1)

ASM configuration

The first thing I will set with my ASM configuration is where the reference PDF is located.

(setq x86-lookup-pdf "~/Documents/code/asm/Intelx86/325383-sdm-vol-2abcd.pdf")

I will also modify what the comment character is, from a ; to a #:

(setq asm-comment-char ?\#)

C/C++

As the C/C++ syntax is checked by flycheck, let’s make sure we are using the latest standard available, that is C++17 and C17, from Clang.

(add-hook 'c-mode-hook
          (lambda ()
            (setq flycheck-clang-language-standard "c17")))
(add-hook 'c++-mode-hook
          (lambda ()
            (setq flycheck-clang-language-standard "c++17")))

Dart configuration

For Dart, I mainly declared some custom shortcuts bound to dart-mode related to flutter, so nothing too exciting here. Some prefix are declared in order to avoid the shortcuts in helm to show up as just custom.

(spacemacs/declare-prefix-for-mode 'dart-mode "mo" "user-defined")
(spacemacs/declare-prefix-for-mode 'dart-mode "mof" "flutter")
(spacemacs/declare-prefix-for-mode 'dart-mode "mofr" "flutter-run")

Now, for the shortcuts themselves:

(spacemacs/set-leader-keys-for-major-mode 'dart-mode
  "ofH" 'flutter-hot-restart
  "ofh" 'flutter-hot-reload
  "ofq" 'flutter-quit
  "ofr" (lambda () (interactive) (flutter-run "-v"))
  "ofs" 'flutter-screenshot)

Emacs Lisp

Here will be stored my configuration directly related to Emacs Lisp, including some functions or default modes.

  1. Enable eldoc-mode by default

    By default, if some Elisp code is opened, I want to enable eldoc-mode so I can easily get some documentation on the symbols in the source code. This is done via the use of hooks.

    (add-hook 'prog-mode-hook 'eldoc-mode)
    
  2. phundrak/write-to-buffer

    I was very surprised when I discovered no such function exists in Elisp. This function basically writes a string into a buffer, and optionally switches the user to the buffer. Here is the code for that function:

    (defun write-to-buffer ($input-string $outputbuf &optional $switchbuf)
      "Writes `$input-string' to the specified `output-buffer'. If
    `switch-buffer' is non-nil, the active buffer will switch to the
    output buffer; otherwise, it will take the user back to their
    initial buffer. Works with `$input-string' as a string or a list
    of strings."
      (let ((oldbuf (current-buffer)))
        (switch-to-buffer $outputbuf)
        (cond ((char-or-string-p $input-string) (insert $input-string))
              ((listp $input-string) (dolist (elem $input-string)
                                      (insert (format "%s\n" elem)))))
        (unless $switchbuf
            (switch-to-buffer oldbuf))))
    

Python

Emacs throws me an error about the python interpreter, let’s silence it:

(setq python-shell-completion-native-disabled-interpreters '("python"))

Rust

I need to point to racer where the source code of Rust is located so I can get some documentation. This is installed with the rust-src component you can get through rustup. To install it, simply run

rustup component add rust-src

Now, the source code for Rust should be included in your installation. I personally prefer to develop with Rust stable, so let’s indicate to Emacs to search for documentation in the stable sources:

(setq racer-rust-src-path
      "~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src")

Rust’s default cargo check command is already very good, however I also enjoy getting some more hints while developping, and clippy does a very good job at it. To get clippy, I need to run the following to install it:

rustup compontent add clippy

And this will get it installed with all of my Rust toolchain, and it will be updated with it. Now, let’s indicate LSP that I want to use that instead of check:

(setq lsp-rust-analyzer-cargo-watch-command "clippy")

Finally, I wish to enable electric-pair-mode and indent-guide-mode for Rust files, so let’s enable that through the use of a hook:

(add-hook 'rust-mode-hook
          '(lambda ()
             (local-set-key (kbd "TAB") #'company-indent-or-complete-common)
             (electric-pair-mode 1)))

Scheme

The Scheme configuration will be very short, I just need to tell Emacs the name of the interpreter since it is not the default one:

(setq geiser-chicken-binary "chicken-csi")

Projectile

Projectile is an awesome utility which helps managing projects within Emacs. It will automatically detect version controlled directories, and will by default assume this is a project I can be working on. However, there are some directories that are version controlled that I do not want to see in my list of projects, namely all the cached AUR packages from my AUR helper, paru. They are all stored in the same parent directory, so let’s ignore that. I will also make Emacs ignore all node_modules directories it could encounter. And for some reason, ~/.emacs.spacemacs is always in my projects list (I now use XDG-compliant directories), so let’s also ignore that.

(setq projectile-ignored-projects '("~/.cache/paru" "~/.emacs.spacemacs" "/tmp"))
(add-to-list 'projectile-globally-ignored-directories "node_modules")

Readers

Thanks to Discord user shanks, I discovered pdf-tools gives us access to a very interesting minor mode: pdf-view-midnight-minor-mode. And this is exactly what I missed in my Emacs PDF reader from Zathura! I still think Zathura is a fantastic tool, but now my PDF reader in Emacs is almost perfect! I just need to adjust some colors:

(with-eval-after-load 'pdf-view
  (setq pdf-view-midnight-colors '("#d8dee9" . "#2e3440")))

Let’s also enable dark mode automatically:

(add-hook 'pdf-tools-enabled-hook 'pdf-view-midnight-minor-mode)

And there we go! A beautiful, dark-mode PDF reader inside Emacs! And with Spacemacs, I can enable or disable this minor mode anytime with the shortcut , n.

Security

This paragraph is about making Emacs and GPG as a whole (since Emacs is always open on my computer) more secure. The first thing I want to make is a function that will close any buffer that contains an open .gpg file –I certainly do not want anyone to be able to read such files on my computer if I leave it even for a couple of minutes.

(defun phundrak/kill-gpg-buffers ()
  "Kill GPG buffers."
  (interactive)
  (let ((buffers-killed 0))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (string-match ".*\.gpg$" (buffer-name buffer))
          (message "Auto killing .gpg buffer '%s'" (buffer-name buffer))
          (when (buffer-modified-p buffer)
            (save-buffer))
          (kill-buffer buffer)
          (setq buffers-killed (+ buffers-killed 1)))))
    (unless (zerop buffers-killed)
      ;; Kill gpg-agent.
      (shell-command "gpgconf --kill gpg-agent")
      (message "%s .gpg buffers have been autosaved and killed" buffers-killed))))

Notice the (shell-command "gpgconf --kill gpg-agent") command there: it kills gpg-agent which will always respawn each time GPG2 is invoked. That way, I know anyone trying to open a GPG file will have to insert my password when trying to do so instead of just hoping I entered it not long ago and they won’t have to.

But surely, if I only define this function and hope to call it each time I leav my computer, surely at one point I will forget to execute it before leaving. I can’t trust myself to always call it manually. Which is why I’ll ask Emacs itself to call it after it detects a minute of idling. It may become from times to times a bit of a pain, but at least I’m now sure I won’t ever have to worry about someone reading my GPG files open in Emacs while I’m out for a quick break.

(run-with-idle-timer 60 t 'phundrak/kill-gpg-buffers)

Snippets

Yasnippet’s snippets tool is extremely powerful and allows me to write very quickly code. For now, we have snippets for two modes. The files you’ll see below are exported to $HOME/.emacs.spacemacs/private/snippets/ and to their respective mode directory. For instance, my caption snippet for org-mode will be exported to $HOME/.emacs.spacemacs/private/snippets/org-mode/caption.

Be aware that on top of these custom snippets, I also use the package yasnippet-snippets which provide plenty of already made snippets.

Rust snippets

I have so far two snippets, the first one is actually just a convenience to make it easier to type a println! macro than the default snippet.

# -*- mode: snippet -*-
# name: println
# key: pln
# --
println!("${1:{}}", $2);

The second one is more interesting: it is used to create a new method for a struct, and it will try to create a function that will assign each argument passed to the method to members of the struct. It relies on the custom function I wrote here.

# -*- mode: snippet -*-
# name: new
# key: _new
# --
fn new(${1:args}) -> Self {
  $0
  Self {
    ${1:$(phundrak-yas-rust-new-assignments yas-text)}
  }
}

Org headers

The first two snippets are used to add HTML or LaTeX attributes to elements in org-mode. The third also has a similar usage, inserting a #+CAPTION header before an element, as well as the fourth which inserts a #+NAME header.

# -*- mode: snippet -*-
# name: ATTR HTML
# key: <ah
# --
#+ATTR_HTML: $0
# -*- mode: snippet -*-
# name: ATTR LATEX
# key: <al
# --
#+ATTR_LATEX: $0
# -*- mode: snippet -*-
# name: caption
# key: <ca
# --
#+CAPTION: $0
# -*- mode: snippet -*-
# name: name
# key: <na
# --
#+NAME: $0

Now, the following is a bit more complex: it is meant to be used as a new org buffer is created. It will insert an org header for the title, which will default to the buffer’s name capitalized minus the dashes or underscores replaced with spaces, it will insert a default author and email based on the user’s parameters, and the date at the moment of the creation of these headers. The user can also add some tags if they wish to.

# -*- mode: snippet -*-
# name: header
# key: header
# --
#+TITLE:  ${1:`(replace-regexp-in-string "-" " " (capitalize (file-name-nondirectory (file-name-sans-extension (buffer-file-name)))))`}
#+AUTHOR: `(user-full-name)`
#+EMAIL:  `user-mail-address`
#+DATE:   `(format-time-string "%Y-%m-%d")`
#+TAGS:   $2

  $0

Org blocks

Now, Let’s write some snippets for org blocks. The first one is for a comment block, then two snippets for an unnamed and a named Elisp source block, and two others for an unnamed and a named Python source block. There are also two block for generic unnamed source blocks and generic named source blocks.

# -*- mode: snippet -*-
# name: comment block
# key: <co
# --
#+BEGIN_COMMENT
$0
#+END_COMMENT
# -*- mode: snippet -*-
# name: emacs-lisp block
# key: <el
# --
#+BEGIN_SRC emacs-lisp
$0
#+END_SRC
# -*- mode: snippet -*-
# name: named emacs-lisp block
# key: <eln
# --
#+NAME: $1
#+BEGIN_SRC emacs-lisp
$0
#+END_SRC
# -*- mode: snippet -*-
# name: python block
# key: <py
# --
#+BEGIN_SRC python
$0
#+END_SRC
# -*- mode: snippet -*-
# name: named python block
# key: <pyn
# --
#+NAME: $1
#+BEGIN_SRC python
$0
#+END_SRC
# -*- mode: snippet -*-
# name: source block
# key: <s
# --
#+BEGIN_SRC $1
$0
#+END_SRC
# -*- mode: snippet -*-
# name: named source block
# key: <sn
# --
#+NAME: $1
#+BEGIN_SRC $2
$0
#+END_SRC

Org misc

Finally, there are a couple of miscellaneous org snippets that insert macros I often use in my conlanging documents. The first one inserts a phonetics macro, while the second one inserts a macro used for my Proto-Ñyqy language.

# -*- mode: snippet -*-
# name: phon
# key: <ph
# --
\{\{\{phon($1)\}\}\} $0
# -*- mode: snippet -*-
# name: nyqy
# key: <ny
# --
\{\{\{nyqy($1)\}\}\} $0

Tramp configuration

Docker

It is completely possible with Tramp to connect ot a docker container and modify files inside of it. It is not supported natively, but we can add it quite easily. Be aware, I am not the author of this code, you can find its original source hereopen in new window. First, let’s add the Docker protocol to Tramp:

(push
 (cons
  "docker"
  '((tramp-login-program "docker")
    (tramp-login-args (("exec" "-it") ("%h") ("/bin/bash")))
    (tramp-remote-shell "/bin/sh")
    (tramp-remote-shell-args ("-i") ("-c"))))
 tramp-methods)

Now that the method has been added, let’s add some autocompletion for when we want to connect to a Docker container:

(defadvice tramp-completion-handle-file-name-all-completions
    (around dotemacs-completion-docker activate)
  "(tramp-completion-handle-file-name-all-completions \"\" \"/docker:\" returns
    a list of active Docker container names, followed by colons."
  (if (equal (ad-get-arg 1) "/docker:")
      (let* ((dockernames-raw (shell-command-to-string "docker ps --format '{{.Names}}:'"))
             (dockernames (cl-remove-if-not #'(lambda (dockerline)
                                                (string-match ":$" dockerline))
                                            (split-string dockernames-raw "\n"))))
        (setq ad-return-value dockernames))
    ad-do-it))

And that’s it! it is now possible to connect to a docker container with something like /docker:conlangdict_server/ as the path given in find-file.

Yadm

yadm is the utility I use for managing my dotfiles, and it is a wrapper In order to manage my dotfiles, I use the following shortcut to launch Magit Status for yadm:

(spacemacs/declare-prefix "oy" "yadm status")
(spacemacs/set-leader-keys "oy" (lambda () (interactive) (magit-status "/yadm::")))

yadm wraps around git. Logically, it means Magit could theoretically manage my yadm repo. And it is indeed possible, according to this pageopen in new window using TRAMP. I just need to add the following 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"))))

Visual configuration

Battery mode line

I want to see by default how much battery my computer has, so let’s enable it:

(spacemacs/toggle-mode-line-battery-on)

Better faces

Sometimes, some visual properties just don’t fit right for me and I need to edit them. This is the case for example for org-mode for which I want to have a mix of fixed and variable pitches. Below you can see the code that does that for me, I’ll get into more detail below this code block.

(let (
      (orgfont `(  :height 120 :font "Charis SIL" ))
      (head `(  :inherit default :weight bold  ))
      (fixed `(  :height 0.8 :font "Cascadia Code" ))
      )
  (custom-theme-set-faces
   'user
   `(ediff-current-diff-A ((t (  :background ,phundrak-nord11  ))) t)
   `(ediff-current-diff-C ((t (  :background ,phundrak-nord13  ))) t)
   `(ediff-current-diff-C ((t (  :background ,phundrak-nord14  ))) t)
   `(mu4e-highlight-face ((t (  :foreground ,phundrak-nord0 :background ,phundrak-nord9  ))) t)
   `(org-level-1 ((t (,@orgfont ,@head  :height 1.75 :foreground ,phundrak-nord15  ))) t)
   `(org-level-2 ((t (,@orgfont ,@head  :height 1.5 :foreground ,phundrak-nord10  ))) t)
   `(org-level-3 ((t (,@orgfont ,@head  :height 1.25 :foreground ,phundrak-nord9  ))) t)
   `(org-level-4 ((t (,@orgfont ,@head  :height 1.1 :foreground ,phundrak-nord15  ))) t)
   `(org-level-5 ((t (,@orgfont ,@head  :foreground ,phundrak-nord8  ))) t)
   `(org-level-6 ((t (,@orgfont ,@head  :foreground ,phundrak-nord7  ))) t)
   `(org-level-7 ((t (,@orgfont ,@head  :foreground ,phundrak-nord15  ))) t)
   `(org-level-8 ((t (,@orgfont ,@head  :foreground ,phundrak-nord6  ))) t)
   `(org-document-title ((t (,@orgfont ,@head  :height 2.0 :foreground ,phundrak-nord11  ))) t)
   `(variable-pitch ((t (,@orgfont   ))) t)
   `(org-block ((t (,@fixed  :background ,phundrak-nord1  ))) t)
   `(org-block-begin-line ((t (,@fixed  :background ,phundrak-nord1  ))) t)
   `(org-block-end-line ((t (,@fixed  :background ,phundrak-nord1  ))) t)
   `(org-indent ((t (,@fixed   ))) t)
   `(org-formula ((t (,@fixed   ))) t)
   `(org-macro ((t (,@fixed   ))) t)
   `(org-target ((t (,@fixed   ))) t)
   `(org-property-value ((t (,@fixed   ))) t)
   `(org-drawer ((t (,@fixed  :foreground ,phundrak-nord10  ))) t)
   `(org-table ((t (,@fixed  :foreground ,phundrak-nord14  ))) t)
   `(org-date ((t (,@fixed  :foreground ,phundrak-nord13  ))) t)
   `(org-code ((t (,@fixed  :inherit shadow  ))) t)
   `(org-verbatim ((t (,@fixed  :inherit shadow  ))) t)
   `(org-document-info-keyword ((t (,@fixed  :inherit shadow  ))) t)
   `(org-tag ((t (,@fixed  :inherit shadow :weight bold  ))) t)
   `(org-meta-line ((t (,@fixed  :inherit font-lock-comment-face :height 0.8  ))) t)
   `(org-special-keyword ((t (,@fixed  :inherit font-lock-comment-face :height 0.8 :foreground ,phundrak-nord15  ))) t)
   `(org-checkbox ((t (,@fixed  :inherit (org-todo shadow fixed-pitch)  ))) t)
   `(org-document-info ((t (  :foreground ,phundrak-nord12  ))) t)
   `(org-link ((t (  :foreground ,phundrak-nord8 :underline t  ))) t)
   ))
  1. Diff and Magit

    Apparently, diff and Magit faces do not follow the nord theme’s color scheme, so let’s redefine their background and sometimes their foreground.

    Namebackground
    ediff-current-diff-A,phundrak-nord11
    ediff-current-diff-C,phundrak-nord13
    ediff-current-diff-C,phundrak-nord14
  2. Mu4e

    The nord theme is great and all, but for some reason some faces in mu4e aren’t displayed properly, such as the mu4e-highlight-face. Let’s fix that!

    Namebackgroundforeground
    mu4e-highlight-face,phundrak-nord9,phundrak-nord0
  3. Org-mode

    Fonts will play an important part in this, but so will colors and font size. The following code is largely based on the one found on this blog postopen in new window and this oneopen in new window. First here are some common properties that will be reused in faces below:

    | Name | inherit || font | height | weight | |------- |------- |------------- |------ |------ | | orgfont | || Charis SIL | 120 | | | head | default || | | bold | | fixed | || Cascadia Code | 0.8 | |

    | Name || additional | inherit | foreground | background | height | weight | italic | underline | |------------------------- |---------------- |----------------------------- |---------------- |--------------- |------ |------ |------ |--------- | | org-level-1 || ,@orgfont ,@head | | ,phundrak-nord15 | | 1.75 | | t | | | org-level-2 || ,@orgfont ,@head | | ,phundrak-nord10 | | 1.5 | | t | | | org-level-3 || ,@orgfont ,@head | | ,phundrak-nord9 | | 1.25 | | t | | | org-level-4 || ,@orgfont ,@head | | ,phundrak-nord15 | | 1.1 | | t | | | org-level-5 || ,@orgfont ,@head | | ,phundrak-nord8 | | | | t | | | org-level-6 || ,@orgfont ,@head | | ,phundrak-nord7 | | | | t | | | org-level-7 || ,@orgfont ,@head | | ,phundrak-nord15 | | | | t | | | org-level-8 || ,@orgfont ,@head | | ,phundrak-nord6 | | | | t | | | org-document-title || ,@orgfont ,@head | | ,phundrak-nord11 | | 2.0 | | t | | | variable-pitch || ,@orgfont | | | | | | | | | org-block || ,@fixed | | | ,phundrak-nord1 | | | | | | org-block-begin-line || ,@fixed | | | ,phundrak-nord1 | | | | | | org-block-end-line || ,@fixed | | | ,phundrak-nord1 | | | | | | org-indent || ,@fixed | | | | | | | | | org-formula || ,@fixed | | | | | | | | | org-macro || ,@fixed | | | | | | | | | org-target || ,@fixed | | | | | | | | | org-property-value || ,@fixed | | | | | | | | | org-drawer || ,@fixed | | ,phundrak-nord10 | | | | | | | org-table || ,@fixed | | ,phundrak-nord14 | | | | | | | org-date || ,@fixed | | ,phundrak-nord13 | | | | | | | org-code || ,@fixed | shadow | | | | | | | | org-verbatim || ,@fixed | shadow | | | | | | | | org-document-info-keyword || ,@fixed | shadow | | | | | | | | org-tag || ,@fixed | shadow | | | | bold | | | | org-meta-line || ,@fixed | font-lock-comment-face | | | 0.8 | | | | | org-special-keyword || ,@fixed | font-lock-comment-face | ,phundrak-nord15 | | 0.8 | | | | | org-checkbox || ,@fixed | (org-todo shadow fixed-pitch) | | | | | | | | org-document-info || | | ,phundrak-nord12 | | | | | | | org-link || | | ,phundrak-nord8 | | | | | t |

Info colors

The package info-colors adds colors to Emacs’ info mode. Let’s enable it:

(add-hook 'Info-selection-hook 'info-colors-fontify-node)

Prettified symbols

Just because it is pleasing to the eye, some symbols in source code get prettified into simpler symbols. Here is the list of symbols that are to be prettified. You can see in the corresponding comment what symbol will be displayed.

(setq prettify-symbols-alist '(("lambda" . 955) ; λ
                               ("mapc" . 8614)  ; ↦
                               ("map" . 8614)   ; ↦
                               (">>" . 187)     ; »
                               ("<<" . 171)     ; «
                               ))

Let’s enable this mode globally.

(global-prettify-symbols-mode 1)

Misc

Emacs is already silent, but let’s set the bell as visible:

(setq visible-bell t)

I would also like to disable the global hl-mode, I find it quite annoying.

(global-hl-line-mode -1)

Footnotes

1 labs.phundrak.com/phundrak/dotfiles/src/branch/master/org/config/emacs.orgopen in new window

2 labs.phundrak.com/phundrak/dotfiles/src/branch/master/.spacemacsopen in new window