danilevy1212 / doom

Doom Emacs Configuration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dan’s DOOM Emacs configuration

Rationale

During my undergraduate years, I remember having trouble setting up a postfix server for a practical exercise in one of my classes. I had to edit the default configuration file to harden the security of the system. My server was wrongly configured, and I couldn’t figure out what part of my edits I had messed up. We had to do all the changes to configuration file through a tty in an FreeBDS virtual machine, so I had to use a terminal editor. nano was my choice since it was similar to the other GUI editors I had used at the time. After much frustration, I asked a professor of mine to please help me out. He took over my seat, closed my nano session and reopened the configuration file with a different editor. He was pressing all sorts of keys on the keyboard at an amazing speed, the cursor was flying all over the screen. I was simply at awe.

The professor found what was wrong with my configuration and fixed it, telling me what he had changed. When he left, I tried to look up what command he had run to edit the file. This was my first experience with… vim. I got introduced to emacs some months later, when I saw a friend of mine taking some notes with it.

In any case, this is the earliest point that I can remember thinking that the efficiency of text-editing, just like with any other task, is fully dependent on the tool used to achieve it. Considering that I was on a professional path to become a software engineer, a job that consists of reading documentation, source code and editing, I figured that it would pay off in the long run to take some time to explore what editors were out there and make an educated choice for a text editor, instead of just using whatever was popular.

(message "Hello, %s!" "emacs")

Having taken that decision, I played around with vim and later on with emacs, finally settling with the former to write the code for my thesis. I started out with a vanilla configuration, a single .emacs file where I would copy and paste Emacs Lisp from other people’s configuration without much thinking. It was ugly, messy and buggy, but It was also fun. I loved the idea of using code to configure the tool that I would use to edit code, so I stuck with it.

My knowledge of Emacs would come from Google searches, blog posts and the online GNU Emacs documentation. I still hadn’t learned how to ask Emacs the things that I didn’t know (The invaluable C-h _). Embarrassingly, I didn’t learn that until late into my Emacs usage history. My editing needs were minimal so even if my configuration didn’t have all the bells and whistles of other editors, It was good enough for me. I also knew, at least on a theoretical level, that Emacs had almost no limits when it came to extensibility. Emacs can interact with the OS to do just about anything, which explains part of the popular saying that refers to Emacs as:

A great OS, if only missing a good text editor.

When I got frustrated with Emacs because I broke it somehow, or I didn’t know how to do something with it, the potential of Emacs as an almost infinitely extensible editor always kept me from switching editors and moving on. Even now, is this fact that keeps me going back to it.

Distro-hopping in Emacs

Life changes and so do our needs. I got my first full-time job as Web Developer and Emacs had to level up to a robust daily driver. I had to use different technologies in a project with continuously changing requirements. My cute emacs experiment could not keep up. I had to use other IDEs to be able to push the work-out. However, I was always missing aspects of Emacs when using other editors, like the frame, buffer and window model, the automatic backup files or just the simple fact that I could change almost any aspect of my emacs if I so desired.

I would still open up Emacs for magit when interacting with git or for org-mode as my todo app. However, I wanted to use Emacs for more than that. Emacs shines when you know what you want from it. However, It can be daunting and take a lot of work to set up a configuration for general programming purposes. Which packages are good? Which are compatible with what you have? When two or more packages are similar, how do you decide which one to use? If Emacs was to be my daily driver, I would have to answer this questions often, which implies research, time and effort.

Why work on problems that others have already solved? With this in mind, I decided to try out spacemacs, a community developed Emacs distribution full of pre-configured packages grouped by layers. And for a while, It worked out great. The defaults were good, it was functional enough, and could keep up with the technologies I was using at work. What was not to love? Well, with continuous use the wrinkles started to appear. For one, spacemacs was slow. It would take a long time to startup and commands were noticeably sluggish. It was also pretty buggy. Sure, the layers for different languages worked reasonably well but from time to time Emacs would behave in unexpected ways and I would have no idea why.

Looking into ways of making spacemacs faster, I stumbled upon doom and decided to try it out. It is fast, at startup and during use. It feels quick and snappy. Installing doom modules was similar enough to configuring spacemacs layers. While doom was not by any means buggy, I would get frustrated with its opinionated defaults. doom would have some keybinding I didn’t like, or some package had some extra behavior different to what I expected, and I wouldn’t know how to change it to what I wanted.

Back to vanilla.

At this point it was clear to me what was the real problem. My ignorance of the inner workings of Emacs was my only limiting factor. So I took an extreme position, I went back to pure vanilla again, discarding all my previous configurations and starting from scratch, while using other editors for my job. My objective was to recreate the aspects that I liked from doom from scratch in my own configuration.

I took it slow, researching Emacs thoroughly and little by little building my configuration, but trying to understand everything that I was changing. I read Mickey Peterson’s Mastering Emacs, where I finally learned how to ask Emacs about the things I don’t understand. Xah’s ergoemacs blog was an awesome reference I keep consulting even now. It has all sorts of information on Emacs lisp syntax, Emacs concepts and configuration tips and tricks.

Other members of the awesome Emacs community I started follow where Protesilaos Stavrou, a long term vim user transformed into a hardcore Emacs user, whose videos explaining his configuration where a great inspiration for me and taught me to favor built-in packages over third party packages and most importantly, how to build my own criteria for what packages I should use.

It took at least two months until I made a configuration that I could use at work again, but it felt great when I finally managed to have something that was reasonably fast, reproducible in any computer running Linux and was functional enough for my use cases that I had made, and I intimately understood! Although I am nowhere near an Emacs expert, if such a thing exists, and a lot of details still escape me. I learned a LOT about not only Emacs, but about lisp, functional programming and free (as in freedom) extensible software! Going back to basics paid of in spades.

Why DOOM?

I’ll ask again, Why work on problems that others have already solved?. Well, in my case, It was to learn more about the problem-context. The thing is, Emacs is truly immense, even if we don’t take into account all the third party packages written for it. It has its own lisp dialect for extensibility, a mode system for defining unique behavior in each buffer, with major modes (one per buffer) and minor modes (can be multiple or none in a buffer) that change the visual elements, available commands and keybindings, and it has different systems to detect when and which of these modes it should activate at any given time. It also has different ways of running system commands synchronously or asynchronously and processing their output, including a process manager for the programs that are running under Emacs!

I’m not even being exhaustive. Add to that 40 years of packages and multiple Emacs releases! This wouldn’t be so troublesome if it weren’t for the terrible defaults with which Emacs installs. Ugly default theme and questionable default bindings aside, it is terribly unoptimized for modern systems slowing down performance. During my vanilla adventure, a lot of my time was just spent trying to make Emacs feel more modern and fast, which is time-consuming. It’s surprising that packages such as gchm-mode and use-package don’t come preinstalled with Emacs as they are incredible time savers, not just with performance but also in configuration time…

At the beginning of re-configuring my vanilla Emacs, after addressing the terrible defaults, the problems I was trying to solve were interesting, perhaps because it was my first time trying to solve them. Things like: What’s the best moment to lazy load this package?, How do I write Spanish accents in Emacs?, How should I structure my *.el files directory?. As the configuration grew, more and more issues started appearing. Nothing major that broke my workflow but annoyances nonetheless. I would write FIXME comments in my .el files to keep track of these issues, so I could fix them later.

When I wanted to set up Emacs for a new language environments, I would spend a lot of time checking out what community packages there were for that specific environment, putting TODO comments with the projects’ repository URL, so I could try out and configure it out later on. Quickly It became the case that for every FIXME or TODO comment I would solve, two or three more would appear.

The FIXME were not such a big deal, I like hunting bugs and fixing them, since I always feel like I at least learn something in the process. The big problem were the TODO, which were not remotely as interesting to solve. Looking for packages is time-consuming and I would end up not using what I tried out. Other times, the packages were so massive I never wanted to because I knew It would take a long time to really configure it like I wanted to. Honorable mentions in these categories are lsp and treemacs.

Unresolved issues in my vanilla configuration.

So, Why work on problems that others have already solved?. Not all problems are equal, and some problems are just tedious to solve, this is the principal reason why I choose to go back to doom. Another reason is that I strongly agree with the project guiding principles. doom is not and IDE replacement or a what-you-see-is-what-you-get type of editor. It’s fully expected of its users to customize it and all its functionality is open to the user, so it can be tinkered with. No magic, just well-thought-out Emacs lisp macros and hooks!

This is perhaps what I like the most about doom, its true power resides in it’s core module, where all the macros, functions and hooks used to help the user extend Emacs resides. The modules in doom just use those set of tools to offer configuration options for specific use cases. This offers a mix of the best of both the worlds of vanilla Emacs and spacemacs. With doom I can try out a module, see what I like, bring it over to my configuration, disable packages that I don’t like and mix them with my own packages in a quick and reliable manner, much more so that If I were back in vanilla Emacs.

TL;DR:

It offers reasonable defaults and lots of functionality without sacrificing extensibility or performance

Installation

Prerequisites

  • Git 2.23+
  • Emacs 26.1+ (27.x is recommended)
  • ripgrep 11.0+
  • GNU Find
  • (Optional) fd 7.3.0+ (known as fd-find on Debian, Ubuntu & derivatives) – improves performance for many file indexing commands

Additionally, the doom executable (located at user-emacs-directory/bin/doom) can be called with the doctor argument to obtain information of possible missing dependencies used by the modules.

Steps

First, clone this repository in your DOOMDIR. DOOMDIR is an environment variable that points to the location of your private configuration. If DOOMDIR does not exist, doom will look for your configuration in doom.d.

export DOOMDIR=/path/to/doom/dir

With the following command you can clone the repository in either case:

git clone https://github.com/danilevy1212/doom.git ${DOOMDIR:-~/.doom.d}

Then, just follow the instructions for installing doom emacs. Run doom env, then doom tangle and finally doom install.

Configuration

Blocks preceded with IE are just examples that are not evaluated, the rest of the blocks are put in the filename of the corresponding heading.

init.el

This file controls what Doom modules are enabled and what order they load in. Remember to run doom sync after modifying it!

Lexical binding.

Emacs lisp by default has dynamic-scope, which is fine if a little weird. However, dynamic scope comes with a performance penalty. Optional lexical scope has to be activated with a file parameter, as such:

;;; $DOOMDIR/init.el -*- lexical-binding: t; -*-

This option must be set in each individual file, so it’s hardly the last time we will use these block of code.

doom! modules

The doom! macro controls which modules are loaded into Emacs. Modules are package configurations made by the community. In the spirit of Emacs, all the configuration that comes with a particular module can be extended or even overwritten by your private configuration.

Modules are open for discovery. Press SPC h d h (or C-h d h for non-vim users) to access Doom’s documentation. There you’ll find a Module Index link where you’ll find a comprehensive list of Doom’s modules and what flags they support.

Alternatively, press gd (or C-c c d) on a module to browse its directory (for easy access to its source code).

The doom! macro is capable of some conditional logic, thanks to the :if and :cond keywords. Unfortunately, these keywords are not well documented beyond and example in the docs. The rest of the keywords match with a directory location. The symbols following a keyword are a module that reside in said directory.

A module is structurally similar to the $DOODIR folder. Defines a packages.el and config.el, plus some extra files that integrates with autoloads or doctor. Some modules come with a README.org for documentation purposes, others are not, so it’s important to take a look at the source code, see what they define and configure, before deciding to use a module.

Some modules can be wrapped in a list and given ‘flags’, that activate extra optional configuration. The list must have the module name as the head, the flags as the tail.

:input

In the japanese module only pangu spacing seems like a package I could use, so I rather install it standalone.

(doom! :input
       ;;bidi              ; (tfel ot) thgir etirw uoy gnipleh
       ;;chinese
       ;;japanese
       ;;layout            ; auie,ctsrnm is the superior home row

:completion

Company Mode

In my opinion, this package offers such a boost in productivity it’s almost essential. Sure, the overlay can be distracting for some, but it’s unobtrusive and optional while being a good tool for discoverability.

:completion
(company +childframe)  ; the ultimate code completion backend

Doom offers a bunch of neat little extras. For starters, +childframe flag configures the company overlay to live in its own frame, which looks nicer in the GUI.

By default, completion starts after a short idle period or with the C-SPC key. While the popup is visible, the following keys are available:

KeybindDescription
C-nGo to next candidate
C-pGo to previous candidate
C-j(evil) Go to next candidate
C-k(evil) Go to previous candidate
C-hDisplay documentation (if available)
C-uMove to previous page of candidates
C-dMove to next page of candidates
C-sFilter candidates
C-S-sSearch candidates with helm/ivy
C-SPCComplete common
TABComplete common or select next candidate
S-TABSelect previous candidate

In the spirit of Vim’s omni completion, the following insert mode key binds are available to evil users to access specific company backend:

KeybindDescription
C-x C-]Complete etags
C-x C-fComplete file path
C-x C-kComplete from dictionary/keyword
C-x C-lComplete full line
C-x C-oInvoke complete-at-point function
C-x C-nComplete next symbol at point
C-x C-pComplete previous symbol at point
C-x C-sComplete snippet
C-x sComplete spelling suggestions

Completion candidates are supplied by the functions defined in company-backends. Doom offers a helper macro, set-company-backend! to change the value of a company-backends for a specific major/minor mode locally in the buffer. Some examples of how to use it can be found in the set-company-backend! documentation.

vertico

This module is a combination of several modular packages that enhanced emacs built-in completion capabilities. This approach is different to those of ivy or helm, which offer their own separate ecosystem.

  • Vertico, which provides the vertical completion user interface
  • Consult, which provides a suite of useful commands using completing-read
  • Embark, which provides a set of minibuffer actions
  • Marginalia, which provides annotations to completion candidates
  • Orderless, which provides better filtering methods

Some important keybindings.

When in an active Vertico completion session, the following doom added keybindings are available:

KeybindDescription
C-pGo to previous candidate
C-nGo to next candidate
C-k(evil) Go to previous candidate
C-j(evil) Go to next candidate
C-; or <leader> aOpen an embark-act menu to choose a useful action
C-c C-;export the current candidate list to a buffer
C-SPCPreview the current candidate
C-M-k(evil) Go to previous candidate and preview.
C-M-j(evil) Go to next candidate and preview.

embark-act will prompt you with a which-key menu with useful commands on the selected candidate or candidate list, depending on the completion category. Note that you can press C-h instead of choosing a command to filter through the options with a Vertico buffer, that also has slightly more detailed descriptions due to Marginalia annotations.

This module offers a lot unique search commands through the SPC s and SPC f prefixes. If the commands come prefixed with the universal command (SPC u), their result with include hidden files.

Marginalia toggle:

KeybindDescription
M-ACycle between annotation levels

If you want to further configure this module, here are some good places to start:

  • Vertico provides several extentions that can be used to extend it’s interface
  • You can add more Marginalia annotation levels and change the existing ones by editing marginalia-annotator-registry
  • You can change the available commands in Embark for category $cat by editing embark-$cat-map, and even add new categories. Note that you add categories by defining them through marginalia, and embark picks up on them.
  • +icons Adds icons to file and buffer category completion selections.
(vertico +icons)           ; the search engine of the future

:ui

That DOOM feel.

Most of what makes doom feel like doom is in the doom, doom-dashboard and doom-quit.

:ui
;;deft              ; notational velocity for Emacs
doom              ; what makes DOOM look the way it does
doom-dashboard    ; a nifty splash screen for Emacs
doom-quit         ; DOOM quit-message prompts when you quit Emacs
emoji

Not really necessary, but they are fun. Use the emojify-insert-emoji function (SPC i e) to insert and emoji and emojify-apropos-emoji to search for them.

(emoji +unicode)  ; 🙂
hl-todo

hl-todo not highlights `TODO` comments in buffers, but also comes some handy keybindings:

keybinddescription
]tgo to next TODO item
[tgo to previous TODO item
SPC p tshow all TODO items in a project
SPC s psearch project for a string
SPC s bsearch buffer for string
;;fill-column       ; a `fill-column' indicator
hl-todo           ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
hydra

The hydra module activates a convenient hydras for window controls and text zoom level.

hydra
indent-guides

Some visual help to quickly understand the indent levels in your code.

indent-guides     ; highlighted indent columns
ligatures

When using emacs-major-version >= 28, enable ligatures, since they can be composed by hardfuzz.

(:if (>= emacs-major-version 28) ligatures)         ; ligatures and symbols to make your code pretty again
Mode line

Doom ain’t doom without its mode line.

;; minimap           ; show a map of the code on the side
modeline          ; snazzy, Atom-inspired modeline, plus API
nav-flash

To help with getting lost in big buffers, use the nav-flash module:

nav-flash         ; blink cursor line after big motions
ophints

This module give a visual hint when selecting or doing operations over text-objects.

;;neotree           ; a project drawer, like NERDTree for vim
ophints           ; highlight the region an operation acts on
popup

Using display-buffer-alist under the hood, doom has an emergent window (or pop-up) management system. It is well documented, and offers an API to arbitrarily extend it.

(popup +defaults)   ; tame sudden yet inevitable temporary windows
;;tabs              ; a tab bar for Emacs
;; unicode           ; extended unicode support for various languages
vc-gutter

This module integrates with git to show hinges on the side of the buffer that indicate the difference between its contents and the version control version.

(vc-gutter +pretty)         ; vcs diff in the fringe
vi-tilde-fringe

Add a small ~ indicating an empty line, like vi.

vi-tilde-fringe   ; fringe tildes to mark beyond EOB
window select

Configuration for ace-window and winum. These packages associate windows in the frame with number, offering a quick and convenient way of selecting a specific window in the frame.

To use ace-window use C-w C-w. You can short-cut to the associated window number using winum, with SPC w {window number}.

(window-select +numbers)     ; visually switch windows
workspaces

Workspaces is a wrapper over persp-mode. It offers isolated buffers and windows setups that can be saved into a file a loaded for persistent configurations. Commands associated with workspaces are under the SPC TAB prefix.

It also has a API for programmatic access.

workspaces        ; tab emulation, persistence & separate workspaces
zen

Using writeroom-mode, changes the UI elements of a buffer so its contents become the main focus. It can be toggled on and off with SPC t z.

zen               ; distraction-free coding or writing

:editor

evil

I prefer vim’s keybindings to Emacs and thankfully, doom offers first class support for evil-mode, a vim emulator inside Emacs, making it easy to get the benefits of both Emacs and vim.

:editor
(evil +everywhere); come to the dark side, we have cookies

Evil is quite complex, and customizing it beyond the default settings can be tricky, as it’s finer details are not well documented. Luckily, the community has covered some of these points, which make the source code of evil much more bearable.

doom comes with emulation for some popular vim plugins:

Vim PluginEmacs PluginKeybind(s)
vim-commentaryevil-nerd-commenteromap gc
vim-easymotionevil-easymotionomap gs
vim-lionevil-lionomap gl / gL
vim-seek or vim-sneakevil-snipemmap s / S, omap z / Z & x / X
vim-surroundevil-embrace and evil-surroundvmap S, omap ys

Along with some extra text objects:

  • a C-style function arguments (provided by evil-args)
  • B any block delimited by braces, parentheses or brackets (provided by evil-textobj-anyblock)
  • c Comments
  • f For functions (but relies on the major mode to have sane definitions for beginning-of-defun-function and end-of-defun-function)
  • g The entire buffer
  • i j k by indentation (k includes one line above; j includes one line above and below) (provided by evil-indent-plus)
  • q For quotes (any kind)
  • u For URLs
  • x XML attributes (provided by exato)

And custom Ex commands.

Ex CommandDescription
:@Apply macro on selected lines
:al[ign][!] REGEXPAlign text to the first match of REGEXP. If BANG, align all matches on each line
:cp[!] NEWPATHCopy the current file to NEWPATH
:dash QUERYLook up QUERY (or the symbol at point) in dash docsets
:dehtml [INPUT]HTML decode selected text / inserts result if INPUT is given
:enhtml [INPUT]HTML encode selected text / inserts result if INPUT is given
:iedit REGEXPInvoke iedit on all matches for REGEXP
:k[ill]all[!]Kill all buffers (if BANG, affect buffer across workspaces)
:k[ill]bKill all buried buffers
:k[ill]m[!] REGEXPKill buffers whose name matches REGEXP (if BANG, affect buffers across workspaces)
:k[ill]oKill all other buffers besides the selected one
:k[ill]Kill the current buffer
:lo[okup] QUERYLook up QUERY on an online search engine
:mc REGEXPInvoke multiple cursors on all matches for REGEXP
:mv[!] NEWPATHMove the current file to NEWPATH
:na[rrow]Narrow the buffer to the selection
:padOpen a scratch pad for running code quickly
:ral[ign][!] REGEXPRight-Align text that matches REGEXP. If BANG, align all matches on each line
:replOpen a REPL and/or copy the current selection to it
:retabConvert indentation to the default within the selection
:rev[erse]Reverse the selected lines
:rm[!] [PATH]Delete the current buffer’s file and buffer
:tcd[!]Send cd X to tmux. X = the project root if BANG, X = default-directory otherwise
file-templates

Like yas-snippets, but for empty files. Includes a mechanism to insert software licenses as well, through M-x +file-templates/insert-license. The module documentation gives extra information on customization of the snippets.

file-templates    ; auto-snippets for empty files
fold
fold              ; (nigh) universal code folding

This module marries hideshow, vimish-fold and outline-minor-mode to bring you marker, indent and syntax-based code folding for as many languages as possible.

Some keybindings include:

KeybindDescription
z fFold region
z oUnfold region
z aToogle fold
z dDelete folded region
z mRefold all regions
z rUnfold all regions
z EDelete all folded regions
z jJump to next fold
z kJump to previous fold
format

This module integrates code formatters into Emacs.

For setting my own formatter, there are two options:

  • Use the set-formatter! macro.
  • Set the buffer-local variable +format-with to the name of the formatter to

use. e.g.

(setq-hook! 'python-mode-hook +format-with 'html-tidy)

;; Or set it to `:none' to disable formatting
(setq-hook! 'python-mode-hook +format-with :none)

Formatters are referred to by the name they were defined with. They can be looked up in the format-all-mode-table hash table or in format-all’s source code.

format          ; automated prettiness
lispy

Lisp aware vim, brought to you by lispyville. It brings changes to evil’s movement and text objects in lisps. Only bad thing is that evil-goggles doesn’t work with lispyville’s commands. Bummer.

lispyville is automatically activated for:

  • Common Lisp
  • Emacs Lisp
  • Scheme
  • Racket
  • Hy
  • LFE
  • Clojure
;;god             ; run Emacs commands without modifier keys
lispy             ; vim for lisp, for people who don't like vim
multiple-cursors

This module adds multiple cursors through two plugins, evil-mc and evil-multiedit.

multiple-cursors    ; editing in many places at once
evil-multiedit

Keybindings:

Keybindingcommand
M-devil-multiedit-match-symbol-and-next
M-Devil-multiedit-match-symbol-and-prev
Revil-multiedit-match-all (visual)
C-M-devil-multiedit-restore

Region active bidings:

KeybindingEffect
DClear region
M-DClear to end-of-region and go to insert mode
AGo into insert mode at end-of-region
IGo into insert mode at start-of-region
VSelect the region
PReplace the iedit region with the contents of the clipboard
$Go to end-of-region
0 / ^Go to start-of-region
gg / GGo to the first/last region
evil-mc

Keybindings

Keybindingcommand
gzdevil-mc-make-and-goto-next-match
gzDevil-mc-make-and-goto-prev-match
gzjevil-mc-make-cursor-move-next-line
gzkevil-mc-make-cursor-move-prev-line
gzmevil-mc-make-all-cursors
gznevil-mc-make-and-goto-next-cursor
gzNevil-mc-make-and-goto-last-cursor
gzpevil-mc-make-and-goto-prev-cursor
gzPevil-mc-make-and-goto-first-cursor
gzqevil-mc-undo-all-cursors
gzsevil-mc-skip-and-goto-next-match
gzSevil-mc-skip-and-goto-prev-match
gzcevil-mc-skip-and-goto-next-cursor
gzCevil-mc-skip-and-goto-prev-cursor
gzt+multiple-cursors/evil-mc-toggle-cursors
gzu+multiple-cursors/evil-mc-undo-cursor
gzz+multiple-cursors/evil-mc-toggle-cursor-here
gzIevil-mc-make-cursor-in-visual-selection-beg (visual)
gzAevil-mc-make-cursor-in-visual-selection-end (visual)
parinfer

Parinfer is a proof-of-concept editor mode for Lisp programming languages. It will infer some changes to keep Parens and Indentation inline with one another.

https://raw.githubusercontent.com/DogLooksGood/parinfer/a7c041454e05ec2b88333a73e72debaa671ed596/images/demo.gif

Basically, it’s a another editing helper package for lisp, in particular:

  • Emacs Lisp
  • Clojure
  • Scheme
  • Lisp
  • Racket
  • Hy
;;objed               ; text object editing for the innocent
(:if IS-LINUX parinfer) ; turn lisp into python, sort of
snippets

This module adds snippets to Emacs, powered by yasnippet.

;;rotate-text     ; cycle region at point between text candidates
snippets          ; my elves. They type so I don't have to
word-wrap

This module adds a minor-mode +word-wrap-mode, which intelligently wraps long lines in the buffer without modifying the buffer content.

word-wrap         ; soft wrapping with language-aware indent

:emacs

editor
:emacs
(dired +icons +dirvish)    ; making dired pretty [functional]

dired-mode, as configured in the dired module, has only a few extra bells and whistles added. Apart from aesthetic stuff, there are some extra keybindings:

KeybindDescription
SPC f dFind directory with dired
qExit dired buffer
C-c C-rRun dired-rsync
C-c C-eRename entries with wdired

This complements the default keybindings. Additionally, the dirvish package makes dired feel more like ranger by building on top of dired and emacs built-ins. It is also fully extensible.

electric

Built-in automated indentation.

electric          ; smarter, keyword-based electric-indent
ibuffer

Project-aware buffer management. Similar to dired, but for buffers. Toggled on by the SPC b i keybinding.

(ibuffer +icons)  ; interactive buffer management
;; undo           ; persistent, smarter undo for your inevitable mistakes
undo

This module augments Emacs’ built-in undo system to be more intuitive and to persist across Emacs sessions.

undo           ; persistent, smarter undo for your inevitable mistakes
vc

This module augments Emacs built-in version control support and provides better integration with git

It offers different modes for .git{ignore,info,attributes,config} files, a way to easily visit the remote file of a repo, ~M-x browse-at-remote, bind to SPC g o o.

vc                ; version-control and Emacs, sitting in a tree

:term

eshell

An emacs alternative to the traditional shell. From this shell, you have access to all of Emacs internal functions and variables. The features of the eshell are too many to explain here.

:term
eshell            ; the elisp shell that works everywhere
vterm

A traditional terminal emulator, powered by libvterm and Emacs c modules.

;;shell             ; simple shell REPL for Emacs
;;term              ; basic terminal emulator for Emacs
vterm               ; the best terminal emulation in Emacs

:checkers

syntax

Setup flycheck, the unofficial general programming language checker of Emacs.

:checkers
(syntax +childframe) ; tasing you for every semicolon you forget
spell

Don’t misspell, ever again!

(spell +aspell +everywhere)             ; tasing you for misspelling mispelling
grammar

This module adds grammar checking to Emacs to aid your writing by combining lang-tool and writegood-mode.

My english is not the best, neither is my spanish or my 日本語 for that matter. Maybe this module can help!

grammar           ; tasing grammar mistake every you make

:tools

direnv

This module integrates direnv into Emacs.

:tools
;;ansible
;;biblio                 ; Writes a PhD for you (citation needed)
;;collab                 ; buffers with friends
;;(debugger +lsp)        ; FIXME stepping through code, to help you add bugs
direnv
docker

This module allows you to manipulate Docker images, containers & more from Emacs.

Provides a major dockerfile-mode to edit Dockerfiles. Additional convenience functions allow images to be built easily.

docker-tramp.el offers a TRAMP method for Docker containers.

(docker +lsp)
editorconfig

This module integrates EditorConfig into Emacs, allowing users to dictate code style on a per-project basis with an .editorconfig file (formal specification).

editorconfig        ; let someone else argue about tabs vs spaces
;;ein               ; tame Jupyter notebooks with emacs
eval

This modules adds inline code evaluation support to Emacs and a universal interface for opening and interacting with REPLs.

Most important features.

  1. Inline code evaluation.

    Quickrun can be invoked via:

    • M-x +eval/buffer (or gR, or M-r)
    • M-x +eval/region
    • M-x +eval/region-and-replace
    • Evil users can use the gr operator to select and run a region.

    Evaluation handlers can be set with set-eval-handler! function.

  2. REPLs
    Invoked via:
    + =SPC o r= or ~:repl~ will open a REPL in a popup window. =SPC o R= or ~:repl!~
      will open a REPL in the current window. If a REPL is already open and a
      selection is active, it will be sent to the REPL.
    + ~M-x +eval/open-repl-other-window~ (=SPC o r=)
    + ~M-x +eval/open-repl-same-window~ (=SPC o R=)
    + ~M-x +eval/send-region-to-repl~ (=SPC c s=) while a selection (and REPL) is
      active
        

    REPLs can be registered with set-repl-handler! function.

More about it’s features can be learned in here.

(eval +overlay)     ; run code, run (also, repls)
;;gist              ; interacting with github gists
lookup

This module adds code navigation and documentation lookup tools to help you quickly look up definitions, references, documentation, dictionary definitions or synonyms.

  • Jump-to-definition and find-references implementations that just work.
  • Powerful xref integration for languages that support it.
  • Search online providers like devdocs.io, stackoverflow, google, duckduckgo or youtube (duckduckgo and google have live suggestions).
  • Integration with Dash.app docsets.
  • Support for online (and offline) dictionaries and thesauruses.
(lookup +docsets +dictionary +offline)              ; navigate your code and its documentation
lsp

The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool.

(lsp +peek)
magit

The best porcelain for git, in emacs!

magit             ; a git porcelain for Emacs
;;make              ; run make tasks from Emacs
;;pass              ; password manager for nerds
;;pdf               ; pdf enhancements
;;prodigy           ; FIXME managing external services & code builders
;;rgb               ; creating color strings
;;taskrunner        ; taskrunner for all your projects
;;terraform         ; infrastructure as code
;;tmux              ; an API for interacting with tmux
tree-sitter

Tree-sitter is a general programming language parser that efficiently builds and updates Abstract Syntax Trees (AST) for your code. Basically, it can read programming languages and understand the structure and meaning of code without having to execute it. Among many amazing things, one of its best and simplest features to take advantage of is richer syntax highlighting.

https://hungyi.net/posts/use-emacs-tree-sitter-doom-emacs/

Additionally, this module adds new text objects for evil-mode:

keytext object
Aparameter list
ffunction definition
Ffunction call
Cclass
ccomment
vconditional
lloop

You can jump directly to any of this nodes with the [g and ]g motion commands.

tree-sitter       ; syntax and parsing, sitting in a tree.. .
;;upload            ; map local to remote projects via ssh/ftp

:os

macos

I use a macbook for work, so this module adds some niceties.

:os
(:if IS-MAC macos)  ; improve compatibility with macOS
tty

Better integration of Emacs with the terminal emulator, particularly:

  • System clipboard integration (through an external clipboard program or OSC-52 escape codes in supported terminals).
  • Fixes cursor-shape changing across evil states in terminal that support it.
  • Mouse support in the terminal.
tty               ; improve the terminal Emacs experience

:lang

data

Occasionally I will have to edit .csv files manually. The data module comes in handy for this task.

:lang
;;agda              ; types of types of types of types...
;;beancount
;;cc                ; C/C++/Obj-C madness
;;(clojure +lsp)      ; java with a lisp
;;common-lisp       ; if you've seen one lisp, you've seen them all
;;coq               ; proofs-as-programs
;;crystal           ; ruby at the speed of c
;;csharp            ; unity, .NET, and mono shenanigans
data                ; config/data formats
emacs-lisp

This module extends support for Emacs Lisp in Doom Emacs.

  • Macro expansion
  • Go-to-definitions or references functionality
;;(dart +flutter)   ; paint ui and not much else
;;dhall
;;elixir            ; erlang done right
;;elm               ; care for a cup of TEA?
emacs-lisp          ; drown in parentheses
;;erlang            ; an elegant language for a more civilized age
;;ess               ; Emacs speaks statistics
;;faust             ; dsp, but you get to keep your soul
;;fortran           ; in FORTRAN, GOD is REAL (unless declared INTEGER)
;;fsharp            ; ML stands for Microsoft's Language
;;fstar             ; (dependent) types and (monadic) effects and Z3
;;gdscript          ; the language you waited for
;;(go +lsp +tree-sitter)           ; the hipster dialect
;;(graphql +lsp)    ; Give queries a REST
;;(haskell +lsp)      ; a language that's lazier than I am
;;hy                ; readability of scheme w/ speed of python
;;idris             ;
json
(json +lsp +tree-sitter)              ; At least it ain't XML
;;(java +lsp) ; the poster child for carpal tunnel syndrome
javascript
(javascript +lsp +tree-sitter)          ; all(hope(abandon(ye(who(enter(here)))))
;;julia             ; a better, faster MATLAB
;;kotlin            ; a better, slicker Java(Script)
latex
(latex +lsp)               ; writing papers in Emacs has never been so fun
;;lean
;;factor
;;ledger            ; an accounting system in Emacs
;;(lua +lsp +fennel)  ; one-based indices? one-based indices
markdown

This module provides Markdown support for Emacs. The +grip flag enables grip support (on <localleader> p), to provide live github-style previews of your markdown (or org) files.

(markdown +grip)          ; writing docs for people to ignore
nix

This module adds support for the Nix language to Doom Emacs, along with tools for managing Nix(OS).

Includes:

  • Syntax highlighting
  • Completion through company and/or helm
  • Nix option lookup
  • Formatting (nixfmt)
;;nim               ; python + lisp at the speed of c
(nix +tree-sitter +lsp)               ; I hereby declare "nix geht mehr!"
;;ocaml             ; an objective camel
org
(org +dragndrop +pretty)               ; organize your plain life in plain text
;;php               ; perl's insecure younger brother
;;plantuml          ; diagrams for confusing people more
;;purescript        ; javascript, but functional
;;(python +lsp +pyright +poetry +tree-sitter) ; beautiful is better than ugly
;;qt                ; the 'cutest' gui framework ever
;;racket            ; a DSL for DSLs
;;raku              ; the artist formerly known as perl6
rest

💡 restclient-mode is tremendously useful for automated or quick testing REST APIs. My workflow is to open an org-mode buffer, create a restclient source block and hack away. restclient-mode and company-restclient power this arcane wizardry.

(rest +jq)          ; Emacs as a REST client
;;rst               ; ReST in peace
;;(ruby +rails)     ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
rust

Rustic mode is great and the integrates really well with cargo. The defaults are reasonable and with the +lsp it integrates nicely with lsp-mode, what’s not to love?

(rust +lsp +tree-sitter)         ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
sh

This module adds support for shell scripting languages.

;;scala                 ; java, but good
;;scheme                ; a fully conniving family of lisps
(sh +tree-sitter +lsp)  ; she sells {ba,z,fi}sh shells on the C xor
;;sml
;;solidity          ; do you need a blockchain? No.
;;swift             ; who asked for emoji variables?
;;terra             ; Earth and Moon in alignment for performance.
;;(web +lsp)        ; the tubes
yaml
(yaml +lsp)         ; JSON, but readable

:email

:email
;;(mu4e +org +gmail)
;;notmuch
;;(wanderlust +gmail)

:app

calendar

This module adds a calendar view for Emacs, with org and google calendar sync support.

:app
calendar
everywhere
;;emms
everywhere        ; *leave* Emacs!? You must be joking
;;irc               ; how neckbeards socialize
;;(rss +org)        ; Emacs as an RSS reader
;;twitter           ; twitter client https://twitter.com/vnought

:config

literate

The meat and potatoes of this configuration. This module tangles the source code blocks in $DOOMDIR/config.org.

:config
literate
Better Defaults

This module provides a set of reasonable defaults, including:

  • A Spacemacs-esque keybinding scheme
  • Extra Ex commands for evil-mode users
  • A yasnippet snippets library tailored to Doom emacs
  • A configuration for (almost) universally repeating searches with ; and =,=

Alongside the reasonable defaults, this module offers tons of convenience commands, under the +default/ prefix.

(default +bindings +smartparens))

packages.el

How does packages.el work?

To install a package with Doom you must declare them here and run doom sync on the command line, then restart Emacs for the changes to take effect – or use M-x doom/reload.

To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:

ie:

(package! some-package)

To install a package directly from a remote git repo, you must specify a :recipe. You’ll find documentation on what :recipe accepts here: https://github.com/raxod502/straight.el#the-recipe-format

ie:

(package! another-package
  :recipe (:host github :repo "username/repo"))

If the package you are trying to install does not contain a PACKAGENAME.el file, or is located in a sub-directory of the repository, you’ll need to specify :files in the :recipe:

ie:

(package! this-package
  :recipe (:host github :repo "username/repo"
           :files ("some-file.el" "src/lisp/*.el")))

If you’d like to disable a package included with Doom, you can do so here with the :disable property:

ie:

(package! builtin-package :disable t)

You can override the recipe of a built-in package without having to specify all the properties for :recipe. These will inherit the rest of its recipe from Doom or MELPA/ELPA/Emacsmirror:

ie:

(package! builtin-package :disable t)

You can override the recipe of a built in package without having to specify all the properties for :recipe. These will inherit the rest of its recipe from Doom or MELPA/ELPA/Emacsmirror:

ie:

(package! builtin-package :recipe (:nonrecursive t))
(package! builtin-package-2 :recipe (:repo "myfork/package"))

Specify a :branch to install a package from a particular branch or tag. This is required for some packages whose default branch isn’t master (which our package manager can’t deal with; see radian-software/straight.el#279)

ie:

(package! builtin-package :recipe (:branch "develop"))

Use :pin to specify a particular commit to install. ie:

(package! builtin-package :pin "1a2b3c4d5e")

Doom’s packages are pinned to a specific commit and updated from release to release. The unpin! macro allows you to unpin single packages…

ie:

(unpin! pinned-package)
; ...or multiple packages
(unpin! pinned-package another-pinned-package)
; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
(unpin! t)

Declarations

For convenience, packages are declared in code blocks close to their configuration code blocks. Package declaration blocks actually go to into packages.el. Package declarations blocks can be distinguished for only containing the package! macro.

We don’t permit the package.el file to be byte compiled and declare its lexical binding.

;; -*- no-byte-compile: t; lexical-binding:t; -*-
;;; $DOOMDIR/packages.el

Auto-load folder

Auto-loads blocks go into different files in the autoload folder. In this folder there are files which define functions that and values that whose evaluation is the entry point into loading other packages. This permits packages to be loaded exactly when they are needed.

This is all made possible thanks to the auto-load cookie: ;;;###autoload. Placing this on top of a lisp form will do one of two things:

  1. Add a autoload call to Doom’s auto-load file (found in ~/.emacs.d/.local/autoloads.el, which is read very early in the startup process).
  2. Or copy that lisp form to Doom’s auto-load file verbatim (usually the case for anything other than def* forms, like defun or defmacro).

Doom’s auto-load file is generated by scanning these files when you execute doom sync.

As with package declarations blocks, auto-load code blocks will be placed close to their related configuration blocks. These will be placed in an auto-load subheading within the corresponding package heading.

config.el

Most of the configuration actually takes place here. In config.el we further customize the packages from the different modules and in packages.el. In other words, the real fun starts here. As always, we start by declaring the lexical binding:

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-

after-save-hook to make scripts executable.

Here’s a simple trick to make she-banged scripts executable auto-magically by default.

(add-hook! 'after-save-hook
           #'executable-make-buffer-file-executable-if-script-p)

alert.el

Alert is a Growl-workalike for Emacs which uses a common notification interface and multiple, selectable “styles”, whose use is fully customizable by the user.

(package! alert)

Depending if I am on linux or macos, choose the notification style.

(use-package! alert
  :defer t
  :custom
  (alert-default-style (if IS-LINUX 'libnotify 'osx-notifier)))

annotate.el

This package provides a minor mode annotate-mode, which can add annotations to arbitrary files without changing the files themselves. This is very useful for code reviews.

(package! annotate)

Activate annotate-mode in file buffers that have annotations.

(use-package! annotate
  :commands (annotate-load-annotation-data))

(add-hook! find-file
           (let ((file-name (buffer-file-name))
                 (annotation-files (mapcar #'car (annotate-load-annotation-data t))))
             (when (and file-name
                        (member file-name annotation-files))
               (annotate-mode +1))))

The current database for annotations is contained in the file indicated by the variable annotate-file.

(after! annotate
  (setq annotate-file (expand-file-name "annotate" doom-cache-dir)))

Blacklist org-mode

(setq annotate-blacklist-major-mode '(org-mode))

Add keybindings.

(after! annotate
  (setq annotate-mode-map (make-sparse-keymap))
  (map! :map annotate-mode-map
        :leader
        :prefix ("b a" . "annotate")
        "a" #'annotate-annotate
        "d" #'annotate-delete-annotation
        "s" #'annotate-show-annotation-summary
        "]" #'annotate-goto-next-annotation
        "[" #'annotate-goto-previous-annotation))

awesome-client

A hidden feature of the eval module is that any function whose name matches with the regex ^\\(?:\\+\\)?\\([^/]+\\)/open-\\(?:\\(.+\\)-\\)?repl$, will appear as an option in the +eval-open-repl.

With this, we can create a repl for the awesome-client.

;;;###autoload
(defun +lua/open-awesome-client-repl ()
   (interactive)
   (pop-to-buffer (make-comint "repl:awesome-client" "awesome-client" nil)))

bookmarks

Share bookmarks between hosts.

(after! bookmark
  (setq bookmark-default-file (expand-file-name "bookmark" "~/Cloud")))

Save bookmarks to a file as soon as they are modified.

(after! bookmark
  (setq bookmark-save-flag 1))

Browse Url

Some of my RSS feeds offer links to lbry, which cannot be open directly with a browser. The following advice takes care of this edge case.

(after! browse-url
  (defadvice! dan/browse-url-encode-url--parse-lbry-url (args)
    "Process a `lbry://' link so it can be opened with `browse-url'."
    :filter-args #'browse-url-xdg-open
    :filter-args #'browse-url-default-macosx-browser
    (list
     (replace-regexp-in-string "^lbry:\/\/" "https://odysee.com/" (car args)))))

calfw

Until 134 gets merged, this fixes repeating entries in range periods.

(after! calfw
  (defadvice! dan/cfw:org-get-timerange (text)
    "Return a range object (begin end text).
    If TEXT does not have a range, return nil."
    :override #'cfw:org-get-timerange
    (let* ((dotime (cfw:org-tp text 'dotime)))
      (and (stringp dotime) (string-match org-ts-regexp dotime)
           (let ((date-string  (match-string 1 dotime))
                 (extra (cfw:org-tp text 'extra)))
             (if (string-match "(\\([0-9]+\\)/\\([0-9]+\\)): " extra)
                 (let* ((cur-day (string-to-number
                                  (match-string 1 extra)))
                        (total-days (string-to-number
                                     (match-string 2 extra)))
                        (start-date (org-read-date nil t date-string))
                        (end-date (time-add
                                   start-date
                                   (seconds-to-time (* 3600 24 (- total-days 1))))))
                   (unless (= cur-day total-days)
                     (list (calendar-gregorian-from-absolute (time-to-days start-date))
                           (calendar-gregorian-from-absolute (time-to-days end-date)) text)))))))))

Finally, we create an entrance point to the calendar view.

(map! :leader
      :desc "Calfw" "o c" #'=calendar)

Disable evil-snipe in this mode, since It overrides some of the keybindings.

(after! evil-snipe
  (add-to-list 'evil-snipe-disabled-modes 'cfw:calendar-mode))

cape

Cape provides Completion At Point Extensions which can be used in combination with the Corfu completion UI or the default completion UI

(package! cape)

Until completion stops working in eshell when typing quote characters is fixed, we apply the fix ourselves. See here.

(use-package! cape
  :init
  (after! eshell
    (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
    (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify))
  :defer t)

company-mode

Begin giving candidates as soon as something is typed. This can be slow sometimes, so it might be a good idea to change it back to it’s default value of 3. Also reduce the idle delay so it feels more responsive.

(after! company
  (setq company-minimum-prefix-length 2
        company-idle-delay 0.05))

Compilation

Compilation URLs should be followable.

(add-hook! compilation-mode #'goto-address-mode)

Enable true color support in compilation buffers.

(after! compile
  (setq compilation-environment '("TERM=xterm-256color"))
  (defadvice! dan/compilation-filter--xterm-color (f proc string)
    :around #'compilation-filter
    (funcall f proc (xterm-color-filter string))))

consult-company

A _consult_ing-read interface for company-mode.

(package! consult-company)

First, let’s ensure the package is only loaded on demand, by creating a consult-company auto-command.

(use-package! consult-company
  :commands consult-company)

This package remaps completion-at-point to use consult’s interface. Keybind to C-S-s.

(after! (company consult)
  (map! :map company-active-map
        "C-S-s" #'consult-company))

consult-dir

Here is a source that adds directory paths provided by the shell tool Fasd:

(after! consult-dir
 (defun consult-dir--fasd-dirs ()
  "Return list of fasd dirs."
  (split-string (shell-command-to-string "fasd -ld") "\n" t))

 (defvar consult-dir--source-fasd
    `(:name     "Fasd dirs"
        :narrow   ?f
        :category file
        :face     consult-file
        :history  file-name-history
        :enabled  ,(lambda () (executable-find "fasd"))
        :items    ,#'consult-dir--fasd-dirs)
   "Fasd directory source for `consult-dir'.")

 (add-to-list 'consult-dir-sources 'consult-dir--source-fasd t))

consult-projectile

A package to incorporate projectile into consult. This allows to choose a project, when none is selected or choose a project buffer/file.

(package! consult-projectile)

consult-projectile also allows for narrowing in emacs >= 28, making it more useful than the default projectile command in doom. Narrow selection with B for buffer, F for file or P for project.

(use-package! consult-projectile
  :defer t
  :init
  (map! :leader
        :desc "Project find" "SPC" #'consult-projectile))

Incorporate the same behavior as projectile-switch-project when changing project:

(after! consult-projectile
  (setq consult-projectile-use-projectile-switch-project t))

Customize Group

An essential interface to know what to customize!

(use-package! cus-edit
  :defer t

I use it to know the customizable options in a package, changing the values within this configuration. So, let’s make it show the actual real values.

:custom
(custom-unlispify-menu-entries nil)
(custom-unlispify-tag-names nil)
(custom-unlispify-remove-prefixes nil))

Default font

Doom exposes five (optional) variables for controlling fonts in Doom. Here are the three important ones:

  • doom-font
  • doom-variable-pitch-font
  • doom-big-font – used for doom-big-font-mode; use this for presentations or streaming.

They all accept either a font-spec, font string (“Input Mono-12”), or xlfd font string. You generally only need these two:

ie:

(setq doom-font (font-spec :family "monospace" :size 12 :weight 'semi-light)
      doom-variable-pitch-font (font-spec :family "sans" :size 13))

Let’s choose our monospaced font, Sarasa Mono J goodness:

(setq doom-font (font-spec :family "Sarasa Mono J" :size 18 :weight 'semi-light))

And our variable pitch font, Sarasa UI J:

(setq doom-variable-pitch-font (font-spec :family "Sarasa UI J" :size 18 :weight 'extra-light))

Comments and keywords should pop more…

(custom-set-faces!
  '(font-lock-comment-face :slant italic)
  '(font-lock-keyword-face :slant italic))

When in zen mode, scale text just a bit.

(after! writeroom-mode
  (setq +zen-text-scale 1.25))

Not everything fits in the mode-line, so let’s make fonts and icons smaller:

(custom-set-faces!
  '(mode-line :height 90 :inherit 'variable-pitch)
  '(mode-line-inactive :height 80 :inherit 'variable-pitch))

(after! all-the-icons
  (setq all-the-icons-scale-factor 1.1))

The filename in the mode line occupies way too much space.

(after! doom-modeline
  (setq doom-modeline-buffer-file-name-style 'truncate-with-project))

Default theme

There are two ways to load a theme. Both assume the theme is installed and available. You can either set doom-theme or manually load a theme with the load-theme function. This is the default:

(setq doom-theme 'doom-nord)

Nord powered aesthetics.

Let’s add some small customization to make everything a bit brighter and bigger:

(use-package! doom-nord-theme
  :defer t
  :custom
  (doom-nord-brighter-modeline t)
  (doom-nord-padded-modeline t)
  (doom-nord-region-highlight 'frost))

Detached

Detached, or Detach Emacs, is a package to run shell commands completely detached from Emacs.

(package! detached)

The detachable nature of the package means that commands started with it can outlive Emacs, which also works on remote hosts, essentially offering a lightweight alternative to Tmux or GNU Screen.

(use-package! detached
  :after-call (compile dired dired-rsync embark-act eshell org-mode projectile-mode shell vterm)
  :config
  (setq detached-notification-function (if IS-LINUX
                                           #'detached-state-transition-notifications-message
                                         #'detached-extra-alert-notification)
        detached-db-directory doom-cache-dir
        detached-init-block-list   '(dired-rsync dired)
        detached-session-directory (temporary-file-directory)))

detached-shell-command acts as a replacement to async-shell-command.

(map! :g "M-&" #'detached-shell-command)

detached-consult allows us to manage the active sessions behind detached.

(use-package! detached-consult
  :defer t
  :init
  (map! :leader
        :desc "Detached Sessions" :g "o s" #'detached-consult-session))

The commands detached-compile and detached-compile-recompile can act as drop in replacements for compile and compile-recompile, respectively.

(use-package! detached-compile
  :defer t
  :init
  (map! :leader
        :desc "Compile" :g  "c c" #'detached-compile
        :desc "Recompile" :g "c C" #'detached-compile-recompile))

Use detached-list-sessions to manage multiple sessions.

(use-package! detached-list
  :defer t
  :init
  (map! :leader
        :desc "Detached Manage Sessions" :g "o S" #'detached-list-sessions)
  :config
  (evil-set-initial-state 'detached-list-mode 'emacs))

We start detached integration.

(after! detached
  (detached-init))

Dired

Most of the information dired throws at you is not really necessary, so let’s hide it by default. One can toggle this information on/off with ( keybinding.

(add-hook! dired-mode #'dired-hide-details-mode)

Opening files from dired with an external program is a bit of drag by default, so we add the dired-open package to take care of that.

(package! dired-open)

The variable dired-open-guess-shell-alist determines if the file is opened with an external program.

(use-package! dired-open
  :after dired
  :custom
  (dired-open-functions (list #'dired-open-guess-shell-alist
                              #'dired-open-by-extension
                              #'dired-open-subdir))
  (dired-guess-shell-alist-user '(("\\.\\(?:docx\\|djvu\\|eps\\)\\'" "xdg-open")
                                  ("\\.\\(?:\\|gif\\|xpm\\)\\'" "xdg-open")
                                  ("\\.\\(?:xcf\\)\\'" "xdg-open")
                                  ("\\.csv\\'" "xdg-open")
                                  ("\\.tex\\'" "xdg-open")
                                  ("\\.\\(?:mp4\\|mkv\\|avi\\|flv\\|rm\\|rmvb\\|ogv\\|mov\\)\\(?:\\.part\\)?\\'" "xdg-open")
                                  ("\\.\\(?:mp3\\|flac\\)\\'" "xdg-open")
                                  ("\\.html?\\'" "xdg-open")
                                  ("\\.md\\'" "xdg-open"))))

The following packages reduce the dired buffer clutter by condensing the information into a single buffer.

(package! dired-subtree)

dired-subtree enables a tree view of te directory, which puts the information of sub-directories contents in the same buffer.

(use-package! dired-subtree
  :after dired)

dired can get hung up in some operations. Luckily, dired-async can do the same procedures without blocking.

(add-hook! dired-mode #'dired-async-mode)

Add keybindings overriding blocking operations.

(after! dired
  (map! :map dired-mode-map
        :n "R" #'dired-async-do-rename
        :n "C" #'dired-async-do-copy
        :n "S" #'dired-async-do-symlink
        :n "H" #'dired-async-do-hardlink))

Use most recently used dired window as default directory for file operations.

(after! dired
  (setq dired-dwim-target #'dired-dwim-target-recent))

Dirvish

Dirvish is an improved version of the Emacs inbuilt package Dired. It not only gives Dired an appealing and highly customizable user interface, but also comes together with almost all possible parts required for full usability as a modern file manager.

Override default layouts.

(after! dirvish
  (setq dirvish-default-layout '(0 0 0.4)
        dirvish-layout-recipes '((1 0.11 0.55)
                                 (0 0    0.40))))

Add file size to display.

(after! dirvish
  (pushnew! dirvish-attributes 'file-size))

Override dired-jump with dirvish!

(map! :leader
      :desc "Dired" "o -" #'dired-jump)

Dotenv

Emacs is missing a mode to edit .env files. So let’s add it one:

(package! dotenv-mode)

Now let’s activate it when opening a .env:

(use-package! dotenv-mode
  :mode ("\\.env\\.?.*\\'" . dotenv-mode))

Dumb Jump

(after! dumb-jump
  (setq dumb-jump-selector 'completing-read))

envrc

home-manager overwrites the PATH variable in zshrc, so we cannot use zsh as our default shell for emacs processes if we want conserve the PATH set in each project.

(setq-default shell-file-name "/bin/sh")

eshell

The keybinding M-SPC m b inserts the name of a buffer in the eshell syntax. However, sometimes it’s useful to refer to the buffer by its string name representation. So let’s make an extra keybinding for said case:

(after! eshell
  (map! :map eshell-mode-map
        (:localleader
         :desc "Insert Symbolic Buffer Name" "B" #'eshell-insert-buffer-name
         :desc "Insert String Buffer Name" "b" #'dan/eshell-insert-buffer-name))

Do not use bash for auto-completion backend.

(setq fish-completion-fallback-on-bash-p nil))

Hack into eshell to get proper 256 color support. Make commands run through eshell show color codes that emacs can understand but only in eshell buffers, so other shell-facing commands have the default TERM value.

(after! eshell
  (add-hook! 'eshell-banner-load-hook
    (setq-local xterm-color-preserve-properties t))
  (add-hook! 'eshell-before-prompt-hook
    (setq-local process-environment `(,@(seq-remove (lambda (e)
                                                      (string-match-p "\\`TERM=" e))
                                                    process-environment)
                                      "TERM=xterm-256color")))
  (add-to-list 'eshell-preoutput-filter-functions #'xterm-color-filter)
  (delq! #'eshell-handle-ansi-color eshell-output-filter-functions))

Remote servers are a bit slow for auto-completion.

(after! eshell
  (add-hook! 'eshell-before-prompt-hook
    (company-mode (if (file-remote-p default-directory) -1 +1))))

Auto-loads

Here I define the slightly modified version of eshell-insert-buffer-name.

;;; $DOOMDIR/autoload/eshell.el -*- lexical-binding: t; -*-

;;;###autoload
(defun dan/eshell-insert-buffer-name (buffer-name)
  "Insert BUFFER-NAME into the current buffer at point.

The BUFFER-NAME is given as string surrounded by \"\"."
  (interactive "BName of buffer: ")
  (insert-and-inherit "\"" buffer-name "\""))

eshell-vterm

An emacs global minor mode allowing eshell to use vterm for visual commands.

(package! eshell-vterm)

Add an alias to eshell to be able to run any command in visual mode, vt.

(use-package! eshell-vterm
  :after eshell
  :config
  (eshell-vterm-mode)
  (defalias 'eshell/vt 'eshell-exec-visual))

Hack while visual commands not inheriting environment bug gets resolved.

(after! eshell-vterm
  (inheritenv-add-advice #'eshell-vterm-exec-visual))

evil

Vim keybindings are hard to let go once you are used to them. Luckily, doom comes with much of the heavy lifting already done when it comes to evil mode. We just gotta customize some minor details.

(use-package! evil
  :defer t
  :custom

Make horizontal motions move to other lines.

(evil-cross-lines t)

Remove highlighted items after search is finished.

(evil-ex-search-persistent-highlight nil)

Don’t continue commented lines with o/O.

(+evil-want-o/O-to-continue-comments nil)

Don’t override the contents of the ” register after pasting on a block.

(evil-kill-on-visual-paste nil)

Implicit /g flag on evil ex substitution. Put g flag to reverse it.

(evil-ex-substitute-global t)

Yank by actual lines of text and not by screen lines, less confusing.

:init
(setq evil-respect-visual-line-mode nil)

Universal argument mapped to M-u instead.

:config
(map! :g "M-u" #'universal-argument

Remove highlighted items after a search.

:m "C-l" #'evil-ex-nohighlight))

Center the cursor after jumping to a new search entry.

(after! evil
  (defun dan/center-after-move (&rest _)
    "Center screen after search."
    (evil-scroll-line-to-center nil))

  (dolist (command '(evil-ex-search-next
                     evil-ex-search-previous))
    (advice-add command :after #'dan/center-after-move))

  (after! evil-snipe
    (dolist (command '(evil-snipe-repeat
                       evil-snipe-seek))
      (advice-add command :after #'dan/center-after-move))))

Create undo boundaries after certain key presses in insert-mode.

(after! evil
  (add-hook! 'post-self-insert-hook
    (when (and (evil-insert-state-p)
               (memq (char-before)
                     (seq-concatenate 'list
                                      ;; English and Spanish
                                      '(?. ?, ?! ?\( ?\{ ?\[ ??)
                                      ;; 日本語
                                      '(?。 ?、 ?\「 ?\( ?\{ ?・))))
      (evil-end-undo-step)
      (evil-start-undo-step))))

evil-quick-diff

Quickly diff to regions of text.

(map! :leader
      :prefix "b"
      "q" #'evil-quick-diff
      "Q" #'evil-quick-diff-cancel)

evil-snipe

evil-snipe is a simple but powerful plugin, that adds a snipe two character motion, plus the possibility of make builtin motions work further than a single line.

(after! evil-snipe
  (setq evil-snipe-scope 'whole-buffer))

Flycheck

Hack to add buffer local flycheck checkers after lsp for different major modes, taken from this issue.

(defvar-local dan/flycheck-local-cache nil)

(after! flycheck
  (defadvice! dan/flycheck-local-checker-get (fn checker property)
     "Check the buffer local cache for the LSP checker."
    :around #'flycheck-checker-get
    (if (eq checker 'lsp)
        (or (alist-get property dan/flycheck-local-cache)
            (funcall fn checker property))
      (funcall fn checker property))))

Add the ability to use a checker in a per-directory basis, thanks to .dir-locals.el

(put 'flycheck-checker 'safe-local-variable #'symbolp)

Harpoon

Harpoon plugin for emacs, based on the plugin from ThePrimeagen.

This plugin offers quick bookmarks separated by project and branch. You can quick navigate between your working files and forget about that files opened that you will not use anymore.

Harpoon persists between emacs sessions.

(package! harpoon
  :recipe (:host github :repo "otavioschwanck/harpoon.el"))

Define quick access keybindings.

(use-package harpoon
  :defer t
  :custom
  (harpoon-separate-by-branch t)
  :init
  (map! :leader
        :prefix ("j" . "harpoon")
        "c" 'harpoon-clear
        "f" 'harpoon-toggle-file
        "a" 'harpoon-add-file
        "m" 'harpoon-toggle-quick-menu
        "1" 'harpoon-go-to-1
        "2" 'harpoon-go-to-2
        "3" 'harpoon-go-to-3
        "4" 'harpoon-go-to-4
        "5" 'harpoon-go-to-5
        "6" 'harpoon-go-to-6
        "7" 'harpoon-go-to-7
        "8" 'harpoon-go-to-8
        "9" 'harpoon-go-to-9))

Hippie Expand

An manual completion system, bind to M-/. Useful for expanding in modes that don’t have full context, like a fundamental-mode buffer.

(map! :g [remap dabbrev-expand] #'hippie-expand)

Hydra

Let’s make the hydra module’s functions easily accessible:

(map! :leader
      :desc "Navigate/Hydra"  :m "w N" #'+hydra/window-nav/body
      :desc "Text-Zoom/Hydra" :m "w f" #'+hydra/text-zoom/body)

image

Help emacs interpret exotic image formats using external programs, like ffmpeg.

(setq image-use-external-converter t)

imenu-list

This Emacs minor-mode creates an automatically updated buffer called Ilist that is populated with the current buffer’s imenu entries. The Ilist buffer is typically shown as a sidebar (Emacs vertically splits the window).

(package! imenu-list)

Keybindings:

CommandKeybindingDescription
imenu-listSPC o iOpen/Toggle Ilist buffer.
imenu-list-go-entryRETGo to imenu entry.
imenu-list-display-entrydDisplay the imenu entry in other buffer.
imenu-list-quit-windowqQuit Ilist buffer.
hs-toggle-hidingTABToggle fold/unfold imenu entry.
(use-package! imenu-list
  :defer t
  :init
  (map! :leader
        :desc "Index" :g "o i" #'imenu-list-smart-toggle))

Reduce the delay to update the buffer when idle.

(setq imenu-list-idle-update-delay 0.2)

Take less screen real-state.

(setq imenu-list-size 0.2)

Force size we just defined.

(setq imenu-list-auto-resize t)

Recenter window after jumping to an entry.

(after! imenu-list
 (setq imenu-list-after-jump-hook '(recenter-top-bottom)))

indent-guides

Use the bitmap to display the indent level if we are in a graphic interface.

(after! highlight-indent-guides
  (setq highlight-indent-guides-method (if (display-graphic-p)
                                           'bitmap
                                         'character)
        highlight-indent-guides-responsive 'top))

inheritenv

inheritenv provides a couple of tools for dealing with this issue:

  1. Library authors can wrap code that plans to execute processes in temporary buffers with the inheritenv macro.
  2. Users can modify commands like shell-command-to-string using the inheritenv-add-advice macro.
(package! inheritenv)

Make an autoload out of inheritenv-add-advice.

(use-package! inheritenv
  :commands (inheritenv-add-advice))

Intellij

I have to use Intellij Idea for some tasks work tasks. This function makes it possible to open the current file in Intellij Idea, provided the scripts from Jetbrains Toolbox are installed and in path.

;;; $DOOMDIR/autoload/idea.el -*- lexical-binding: t; -*-

;;;###autoload
(defun dan/open-in-intellij-idea (file line column)
  (interactive (list
                (buffer-file-name)
                (format "%d" (line-number-at-pos))
                (format "%d" (current-column))))
  (unless (and file line column)
    (throw 'user-error "Needs to be called on a real file"))
  (start-process "idea" nil "idea" "--line" line "--column" column file))

Bind it to SPC o j.

(when IS-MAC
  (map! :leader
        :desc "Open in Intellij" :g "o j" #'dan/open-in-intellij-idea))

Language Tool

Do not run the jar file! Run the binary.

(setq langtool-bin "languagetool-commandline")

Most of the time, assume I speak English.

(setq langtool-mother-tongue "en")

Line-numbers

This determines the style of line numbers in effect. If set to nil, line numbers are disabled. For relative line numbers, set this to relative.

(setq display-line-numbers-type 'relative)

Lsp

Allow per-directory disabling of certain lsp-clients.

(put 'lsp-disabled-clients 'safe-local-variable #'sequencep)

Lua

Search for the lsp-server in path:

(after! lsp-lua
   (setq lsp-clients-lua-language-server-command "lua-language-server"))

Add lsp-lua-diagnostics-globals as a safe-local-variable.

(put 'lsp-lua-diagnostics-globals
     'safe-local-variable
     (lambda (e)
       (and (vectorp e)
            (--all?
             (and (stringp it)
                  (not (string-empty-p it)))
             (seq-concatenate 'list e)))))

Add lsp-lua-workspace-library as a safe-local-variable.

(put 'lsp-lua-workspace-library 'safe-local-variable
     (lambda (e)
       (let ((is-dir-p (lambda (dir) (and (stringp dir))
                         (file-directory-p dir))))
         (and (listp e)
              (--all? (pcase it
                        (`(,first . ,last) (and (funcall is-dir-p first)
                                                (or (eq last t)
                                                    (and (listp last)
                                                         (-all? is-dir-p
                                                                last))))))
                      e)))))

Due to the limited number of file descriptors available in the MacOS version of emacs, we need to be conservative with their use. This means turning off automatic file-watching in lsp.

(setq lsp-enable-file-watchers (not IS-MAC))

magit-delta

This emacs package provides a minor mode which configures magit to use delta when displaying diffs.

(package! magit-delta)
(use-package! magit-delta
  :custom (magit-delta-default-dark-theme "Nord")
  :hook   (magit-mode . magit-delta-mode))

magit-delta-mode can be slow on big diffs. Luckily, we can advice the appropiate functions to deactivate the mode on big buffers.

(after! magit-delta
  (defcustom dan/magit-delta-point-max 50000
    "Maximum length of diff buffer which `magit-delta' will tolerate."
    :group 'magit-delta
    :type  'natnum)
  (defadvice! dan/magit-delta-colorize-maybe (fn &rest args)
    "Disable mode if there are too many characters."
    :around #'magit-delta-call-delta-and-convert-ansi-escape-sequences
    (if (<= (point-max) dan/magit-delta-point-max)
        (apply fn args)
      (magit-delta-mode -1))))

Re-enable mode after magit-refresh if there aren’t too many characters.

(after! magit
  (add-hook! 'magit-post-refresh-hook
    (when (and (not magit-delta-mode)
               (<= (point-max) dan/magit-delta-point-max))
      (magit-delta-mode +1))))

Man

The default popup-rule makes the buffer appear in the lower half of the screen. man pages use more vertical space, so we push it to the right side of the frame.

(add-transient-hook! 'doom-first-input-hook
           (let ((cell (assoc 'side
                              (assoc "^\\*\\(?:Wo\\)?Man " display-buffer-alist))))
             (setcdr cell 'right)))

Modeline

The default doom-modeline is great, the only thing is that I want it to show me the evil state I am in with a letter instead of an icon:

(use-package! doom-modeline
  :defer t
  :custom
  (doom-modeline-modal-icon nil))

New Lines

Emacs aggressively adds a \n to files, which is technically a good practice. However, lots of other IDEs don’t do this. In collaborative version controlled projects, this can result in emacs stubbornly adding a hunk at the end of the file, which can lead to problems, from strange commits diffs to tests failing because files where not expected to have a trailing new line.

We change it so emacs simply asks us first if we want to add the \n before saving.

(setq require-final-newline 'ask)

This little hack prevents persp-mode to bother me when saving its autosave file. We advice basic-save-buffer so files in persp-save-dir insert a breakline if missing one.

(after! persp-mode
  (defadvice! dan/persp-autosave--add-breakline (&rest _)
    "Automatically add breakline for certain buffers before saving to file."
    :before #'basic-save-buffer
    (when (and
           (/= (point-max) (point-min))
           (/= (char-after (1- (point-max))) ?\n)
           (string-equal (file-name-directory
                          (or (buffer-file-name (current-buffer)) ""))
                         persp-save-dir))
      (goto-char (point-max))
      (insert ?\n))))

Nix

Use alejandra as the default formatter.

(after! nix-mode
  (set-formatter! 'alejandra "alejandra --quiet" :modes '(nix-mode))
  (map! :localleader
        :map nix-mode-map
        :desc "nix-format-buffer" "p" #'+format/buffer))

nodejs

nodejs-repl is a super useful package. However, it’s missing a comfortable way to interact with promises. We can change that with an experimental nodejs.

(after! nodejs-repl
  (setq nodejs-repl-arguments '("--experimental-repl-await")))

Org

One of the killer features of Emacs.

(use-package! org
    :defer t

If you use org and don’t want your org files in the default location below, change org-directory. It must be set before org loads!

:custom
(org-directory "~/Cloud/org/")

Set org-attach-id-dir back to default value.

(org-attach-id-dir  "data")

Any file in the agenda directory is part of the agenda view.

(org-agenda-files (list (expand-file-name "agenda/" org-directory)))

Modules for completing checklists and making links to bookmarks, elisp symbols, pdf s, info pages, man pages and others.

(org-modules  '(org-checklist
                org-mouse
                ol-elisp-symbol
                ol-bookmark
                ol-info
                ol-man
                ol-telega))

A project with no NEXT subheads is stuck.

(org-stuck-projects '("TODO=\"PROJ\"" ("NEXT") nil ""))

Default priority is lowest priority

(org-priority-default org-priority-lowest))

In org buffers, remove the line number fringe.

(after! org
  (setq-hook! org-mode
    display-line-numbers nil))

Use the TODO keywords that suit my workflow.

TODO
Task that needs to be done. Needs a SCHEDULED or DEADLINE date, else it’s just a pipe dream.
NEXT
Task that has already started.
PROJ
Task that holds a collection of related tasks, events and ideas.
WAIT
Tasks that cannot be completed until an external event happens, that is, something outside my control. Tasks that depend on other tasks, whose completion depend exclusively on me, use the BLOCKER property.
EVENT
Indicates a happening.
IDEA
A note that could turn into a task or an entire project, needs refinement.
DONE
Completed task.
CANCELLED
Task that will not be done.

Also, add them faces so they stick out more:

(after! org
  (custom-declare-face '+org-todo-wait  '((t (:inherit (bold mode-line-emphasis org-todo)))) "")
  (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "PROJ(p)" "WAIT(w)" "IDEA(i)" "EVENT(e)" "|"
                                      "DONE(d)" "CANCELLED(c)"))
        org-todo-keyword-faces '(("NEXT"      . +org-todo-active)
                                 ("WAIT"      . +org-todo-wait)
                                 ("EVENT"     . +org-todo-onhold)
                                 ("PROJ"      . +org-todo-project)
                                 ("CANCELLED" . +org-todo-cancel))))

org-capture-templates are task templates that help automate the process of automating tasks. The defaults in doom are a bit over-kill for me and make assumptions that are not fit for my workflow flow.

The key files of the capture template are:

+org-capture-projects-file
Project related todos.
+org-capture-todo-file
Unplanned or emergent todos, tend to be resolved quickly.
+org-capture-journal-file
Scheduled todos or events.
+org-capture-notes-file
Ideas file.
(after! org
  (setq +org-capture-projects-file (expand-file-name "projects.org" (car org-agenda-files))
        +org-capture-todo-file     (expand-file-name "diary.org"    (car org-agenda-files))
        +org-capture-journal-file  (expand-file-name "schedule.org" (car org-agenda-files))
        +org-capture-notes-file    (expand-file-name "ideas.org"    (car org-agenda-files))))

And the templates that use those files:

(after! org
  (setq org-capture-templates '(("t" "Quick todo" entry
                                 (file+headline +org-capture-todo-file "Quick errands")
                                 "* TODO %?\n%i\n" :prepend t)
                                ("e" "Event" entry
                                 (file+olp +org-capture-journal-file "Events")
                                 "* EVENT %?\n%i\n" :prepend nil)
                                ("i" "Random idea" entry
                                 (file+olp +org-capture-notes-file "Inbox")
                                 "* IDEA %?\n%i\n" :prepend t)
                                ("p" "Centralized templats for projects")
                                ("pt" "Project todo" entry
                                 #'+org-capture-central-project-todo-file
                                 "* TODO %?\n %i\n %a" :heading "Tasks" :prepend t)
                                ("pe""Project event" entry
                                 #'+org-capture-central-project-todo-file
                                 "* EVENT %?\n %i\n" :heading "Events" :prepend nil)
                                ("pi" "Project idea" entry
                                 #'+org-capture-central-project-todo-file
                                 "* IDEA %?\n %i\n %a" :heading "Ideas" :prepend t))))

Easier to find org-attach links.

(after! org
  (setq org-id-method 'ts
        org-attach-id-to-path-function-list '(org-attach-id-ts-folder-format
                                              org-attach-id-uuid-folder-format)))

Now we specify the org-refile-targets. Any open org file and the files that are part of the agenda.

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

To have timestamps consistently in english, no matter the system language.

(setq system-time-locale "C")

org-appear

Add integration with evil-mode, to auto hide/show entities. With the builtin +org-pretty-mode we hide the emphasis markers.

(after! org
  (add-hook! org-mode #'+org-pretty-mode #'org-appear-mode))

org-edna

org-edna offers more control over how and when tasks change state and manages dependencies between tasks through extra heading properties.

(package! org-edna)

Load and activate org-edna together with org.

(use-package! org-edna
  :hook (org-load . org-edna-mode)

Make org-edna to trigger in any state change except done.

:custom
(org-edna-from-todo-states 'not-done))

Bind org-edna-edit in org-mode buffers.

(map! (:after org
       :localleader
       :map  org-mode-map
       :desc "Org Edna Edit" :g "E" #'org-edna-edit))

org-sticky-header

Like topsy, for org mode.

(package! org-sticky-header)

Lazy load only in org-mode buffers. Show then full the path of the outline in the header.

(use-package! org-sticky-header
  :custom
  (org-sticky-header-full-path 'full)
  :hook
  (org-mode . org-sticky-header-mode))

org-super-agenda

This package lets you “supercharge” your Org daily/weekly agenda. The idea is to group items into sections, rather than having them all in one big list.

(package! org-super-agenda)

So this package filters the results from org-agenda-finalize-entries, which runs just before items are inserted into agenda views. It runs them through a set of filters that separate them into groups. Then the groups are inserted into the agenda buffer, and any remaining items are inserted at the end. Empty groups are not displayed.

The end result is your standard daily/weekly agenda, but arranged into groups defined by you. You might put items with certain tags in one group, habits in another group, items with certain todo keywords in another, and items with certain priorities in another. The possibilities are only limited by the grouping functions. u The primary use of this package is for the daily/weekly agenda, made by the org-agenda-list command, but it also works for other agenda views, like org-tags-view, org-todo-list, org-search-view, etc.

(use-package! org-super-agenda
  :after org
  :config
  (org-super-agenda-mode))

The org-agenda shows today’s tasks.

(after! org-agenda
  (setq org-agenda-span      'day
        org-agenda-start-day "<today>"))

Keybinding for the org-agenda.

(map! (:prefix "o"
       :leader
       :desc "Agenda Menu" "a" #'org-agenda))

Create a workspace like feel to each org-agenda calling. We make it fill the whole frame and restore the previous window configuration after we are done.

(after! org-agenda
  (setq org-agenda-window-setup 'only-window
        org-agenda-restore-windows-after-quit t))

Sometimes I schedule a task for a particular day and set a deadline for a time in that same day. In those cases, we want to emphasize the deadline and not the schedule date.

(after! org-agenda
  (setq org-agenda-skip-scheduled-if-deadline-is-shown t))

Don’t show deadline warning if task is scheduled prior to the deadline time.

(after! org-agenda
  (setq org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled))

Prevent done tasks to show in the agenda.

(after! org-agenda
  (setq org-agenda-skip-scheduled-if-done t
        org-agenda-skip-deadline-if-done  t
        org-agenda-skip-timestamp-if-done t))

Show scheduled items without a time first in the Day Agenda.

(after! org-agenda
  (setq org-agenda-sort-notime-is-late nil))

Don’t show blocked tasks.

(after! org-agenda
  (setq org-enforce-todo-dependencies t
        org-agenda-dim-blocked-tasks  'invisible))

For extra visibility, we tweak the agenda sorting strategy to make scheduled tasks appear first, ordered by priority.

(after! org-agenda
  (setq org-agenda-sorting-strategy '((agenda time-up priority-down)
                                      (todo scheduled-up priority-down)
                                      (tags scheduled-up priority-down)
                                      (search scheduled-up priority-down))))

Display any scheduled or timestamp info in agenda.

(after! org-agenda
  (setq org-agenda-prefix-format    '((agenda . "   %?-12t%-12s")
                                      (todo   . "   %?-12s")
                                      (tags   . "   %?-12s")
                                      (search . "   %?-12s"))))

Provide a macro to dynamically build TIMESTAMP selectors. We want the date providers to be functions so their return values are refreshed on each calling.

(after! org-super-agenda
  (defsubst dan/org-super-agenda--soon-limit ()
    "Return the last ISO Date that is within the soon limit."
    (org-read-date nil nil "+3d"))

  (defmacro dan/org-super-agenda--build-timestamp-selector (&optional date-provider)
    "Build a timestamp selector that includes the date returned by
    DATE-PROVIDER.

DATE-PROVIDER is a function with no arguments that returns a date in the style
of `calendar-gregorian-from-absolute'.  It defaults to `org-today' if empty."
    (let ((date-provider (or (and (functionp date-provider)
                                  (bound-and-true-p date-provider))
                             #'org-today)))
      `(lambda (date)
         (let ((other-date ,(funcall date-provider)))
           (if (string-match-p "--" date)
               (cl-destructuring-bind (date-start date-end) (split-string date "--" t)
                 (and (>= other-date
                          (org-time-string-to-absolute date-start))
                      (<= other-date
                          (org-time-string-to-absolute date-end))))
             (>= other-date
                 (org-time-string-to-absolute date))))))))

The dan/org-super-agenda--transformer-show-scheduled-or-deadline shows an item’s DEADLINE and/or SCHEDULED in the entry.

(after! org-super-agenda
  (defun dan/org-super-agenda--transformer-show-scheduled-or-deadline (item)
    "Transform ITEM to show SCHEDULED and DEADLINE properties."
    (let* ((this-marker    (org-find-text-property-in-string 'org-marker item))
           (scheduled-date (org-entry-get this-marker "SCHEDULED"))
           (deadline-date  (org-entry-get this-marker "DEADLINE"))
           (tags           (or (and (string-match "\s*:[[:word:]:@_]*: *$" item)
                                    (match-string 0 item))
                               "")))
      (if (or scheduled-date deadline-date)
          (concat (string-remove-suffix tags item)
                  (when scheduled-date
                    (concat "\t"
                            (propertize "SCHEDULED: " 'face 'org-special-keyword)
                            (propertize (or scheduled-date "") 'face 'org-date)))
                  (when deadline-date
                    (concat "\t"
                            (propertize "DEADLINE: " 'face 'org-special-keyword)
                            (propertize (or deadline-date "") 'face 'org-date)))
                  tags)
        item))))

Refresh the soon limit date on each org-super-agenda calling.

(after! org-super-agenda
  (defadvice! dan/org-super-agenda--refresh-soon-limit (_)
    :before #'org-super-agenda--group-items
    (let ((group (cl-find-if (lambda (group)
                               (string= (plist-get group :name) "Soon"))
                             org-super-agenda-groups)))
      (setf (plist-get group :scheduled)
            `(before ,(dan/org-super-agenda--soon-limit))))))

With the package installed, we are ready to define org-agenda-custom-commands as well as the org-super-agenda-groups. First we start with my personal custom commands, which focus on anything that is not work related.

(after! org-agenda
  (setq org-agenda-custom-commands '(("p" . "Personal Tasks"))))

How does my day look like? Day agenda focus on answering that question with as little noise as posible. No info about anything else but my current day.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("pd"
                                             "Personal Day Agenda"
                                             agenda
                                             ""
                                             ((org-agenda-tag-filter-preset    '("-work"))
                                              (org-super-agenda-groups         nil)
                                              (org-super-agenda-unmatched-name "")
                                              (org-super-agenda-header-prefix  "")))))

A more general view of my tasks, with extra contextual information, such as task that are on hold, task that are scheduled soon, etc. See org-super-agenda-groups.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("pt"
                                             "Personal TODOs"
                                             tags-todo
                                             "-work"
                                             ((org-tags-history (pushnew! org-tags-history "-work"))))))

Work custom commands.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("w" . "Work Tasks")))

Same as my personal Day Agenda but only for work related tasks.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("wd"
                                             "Work Day Agenda"
                                             agenda
                                             ""
                                             ((org-super-agenda-groups         nil)
                                              (org-agenda-tag-filter-preset    '("+work"))
                                              (org-super-agenda-unmatched-name "")
                                              (org-super-agenda-header-prefix  "")))))

Same as my personal Global TODOs, but only for work related tasks.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("wt"
                                             "Work TODOs"
                                             tags-todo
                                             "+work"
                                             ((org-tags-history (pushnew! org-tags-history "+work"))))))

A Day Agenda with any and all tasks of today.

(after! org-agenda
  (add-to-list 'org-agenda-custom-commands '("d"
                                             "Global Day Agenda"
                                             agenda
                                             ""
                                             ((org-super-agenda-groups         nil)
                                              (org-super-agenda-unmatched-name "")
                                              (org-super-agenda-header-prefix  "")))))

The default org-super-agenda-groups.

Started
Tasks that are being actively worked on. Omit the tasks that are rescheduled.
Today
Anything that is due, scheduled or is going to happen today or in the past.
Soon
Anything that is due, scheduled or is going to happen within the next three days.
Incoming Events
Future happenings.
Important
Important tasks that haven’t been scheduled. Ideally should always be empty.
On Hold
Tasks that are wating on other task or some external happening.
Are Scheduled
Scheduled future tasks.
Have Deadline
Task with a dealine, but not scheduled>
Backlog
Everything else, except projects.
Projects
All projects.

Everything

(after! org-super-agenda
  (setq org-super-agenda-groups         `((:name "Started"
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline
                                           :and (:todo "NEXT" :not (:scheduled future)))
                                          (:name "Today"
                                           :scheduled today
                                           :scheduled past
                                           :property ("TIMESTAMP"
                                                      ,(dan/org-super-agenda--build-timestamp-selector))
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline)
                                          (:name "Soon"
                                           :scheduled (before ,(dan/org-super-agenda--soon-limit))
                                           :deadline  (before ,(dan/org-super-agenda--soon-limit))
                                           :property ("TIMESTAMP"
                                                      ,(dan/org-super-agenda--build-timestamp-selector
                                                        (lambda ()
                                                          (org-time-string-to-absolute (dan/org-super-agenda--soon-limit)))))
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline)
                                          (:name "Incoming Events"
                                           :todo "EVENT")
                                          (:name "Important"
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline
                                           :and (:priority "A"
                                                 :not (:todo "WAIT"
                                                       :scheduled future)))
                                          (:name "On Hold"
                                           :todo "WAIT")
                                          (:name "Are Scheduled"
                                           :scheduled future
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline)
                                          (:name "Have Deadline"
                                           :deadline future
                                           :transformer dan/org-super-agenda--transformer-show-scheduled-or-deadline)
                                          (:name "Backlog"
                                           :not (:todo "PROJ"))
                                          (:name "Projects"
                                           :todo "PROJ"))))

There are a few clashes between evil-org-agenda-mode-map and org-super-agenda-header-map. To remove them, we simply override the latter keymap, after loading both evil-org-agenda and org-super-agenda.

(after! (evil-org-agenda org-super-agenda)
  (setq org-super-agenda-header-map (copy-keymap evil-org-agenda-mode-map))
  (map! :map org-super-agenda-header-map
        :nm "q" #'org-agenda-quit
        :nm "TAB" #'ignore))

To prevent unnecessary scrolling when calling the agenda buffer, we automate the cursor position on agenda todo views.

(after! (evil-org-agenda org-super-agenda)
 (add-hook! 'org-agenda-finalize-hook
            :append (when (and (not (buffer-narrowed-p))
                               (goto-char (point-min)))
                      (when (not (org-agenda-check-type nil 'agenda))
                        (re-search-forward "^$")
                        (forward-line)
                        (evil-scroll-line-to-center (count-lines (point-min) (point))))
                      (org-agenda-next-item 1))))

Prevent org-capture buffers from loosing their target when restarting.

(after! org
  (defadvice! dan/+org--restart-mode-h-careful-restart (fn &rest args)
    :around #'+org--restart-mode-h
    (let ((old-org-capture-current-plist (and (bound-and-true-p org-capture-mode)
                                              (bound-and-true-p org-capture-current-plist))))
      (apply fn args)
      (when old-org-capture-current-plist
        (setq-local org-capture-current-plist old-org-capture-current-plist)
        (org-capture-mode +1)))))

Prevent false error messages with org-element--cache-sync.

(after! org
  (add-hook! 'org-capture-after-finalize-hook (org-element-cache-reset t)))

osx-trash

Prevent attempting to move files to trash when in a remote directory.

(when IS-MAC
  (setq-hook! (dired-mode magit-mode 'eshell-before-prompt-hook)
    delete-by-moving-to-trash (not (file-remote-p default-directory))))

Personal information

Some functionality uses this to identify you, e.g. GPG configuration, email clients, file templates and snippets.

(setq user-full-name    "Daniel Levy"
      user-mail-address "danielmorenolevy@gmail.com")

Proced

A builtin hidden gem, which has substituted htop for me.

Mode for displaying system processes and sending signals to them.

(use-package! proced
  :defer t

By default, proced doesn’t auto update the process list, which is a bummer. Luckily, we can change that.

:custom
(proced-auto-update-flag t)
(proced-auto-update-interval 1))

Projectile

Let’s make projectile’s life easier by giving it some paths where I normally store projects.

(setq projectile-project-search-path '("~/Projects/" "~/.config/"))

There is a bug in projectile-skel-dir-locals that prevents completion of the template, as a workaround:

(after! projectile
  (defadvice! dan/projectile-skel-dir-locals--fixed (&optional str arg)
    :override #'projectile-skel-dir-locals
    (interactive "*P\nP")
    (skeleton-proxy-new
     '(nil "((nil . (" ("" '(projectile-skel-variable-cons) n)
       resume: ")))")
     str arg)))

I work on a large monorepo at work, which projectile-find-file can struggle with. Setting the indexing method to alien can speed things up. We make this variable a safe-local-variable.

(put 'projectile-indexing-method
     'safe-local-variable
     (lambda (val)
       (memq val '(alien hybrid native))))

QR encode

QR Code encoder written in pure Emacs Lisp.

(package! qrencode)

This package provides two user facing interactive functions, that will encode text into a QR Code and show it in a separate buffer.

qrencode-region Shows the current selection as a QR Code.

qrencode-url-at-point Encode URL at point as QR Code.

(use-package! qrencode
    :defer t)

Rainbow Delimiters

Matching pairs draw with the same face color, making them easily identifiable.

(add-hook! prog-mode #'rainbow-delimiters-mode-enable)

run-command

run-command you write a short recipe and obtain a command that is easy to bring up, invoke, and keep track of, without leaving Emacs.

(package! run-command)

We advice run-command--run-compile to use detached.

(use-package! run-command
  :defer t
  :config
  ;; NOTE: Forgotten builtin dependency
  (unless (featurep 'map)
    (require 'map))
  (defadvice! dan/run-command--run-detached-shell-command (command-line buffer-base-name)
    "Override `run-command--run-compile' to use `detached-compile'."
    :override #'run-command--run-compile
    (let ((compilation-buffer-name-function
           (lambda (_name-of-mode) buffer-base-name)))
      (detached-compile command-line t))))

Make sure run-command uses vertico.

(after! run-command
  (setq run-command-completion-method 'completing-read))

An easy mnemonic keybinding.

(map! :leader
      :desc "Run command recipe" "r" #'run-command)

NixOS recipes.

(after! run-command
  (defun dan/run-command--nix ()
    (if IS-LINUX
        (list
         ;; FIXME Detect the host first
         ;; (list :command-line "rm -f ~/.config/mimeapps.list && sudo nixos-rebuild switch --flake '/etc/nixos#nyx15v2'"
         ;;       :command-name "Rebuild NixOS"
         ;;       :display "Create a new NixOS generation and switch to it")
         (list :command-line "nix flake update /etc/nixos"
               :command-name "Update NixOS"
               :display "Update the NixOS flake inputs")
         (list :command-line "sudo nix-collect-garbage -d"
               :command-name "GC NixOS"
               :display "Delete unused derivations and previous generations"))
      (list
       (list :command-line "darwin-rebuild switch --flake '/Users/dlevy/.config/nixos#autoMac'"
             :command-name "Rebuild darwin"
             :display "Create a new darwin generation and switch to it")
       (list :command-line "darwin-rebuild check --flake '/Users/dlevy/.config/nixos#autoMac'"
             :command-name "Check darwin"
             :display "Check if new darwin generation is buildable")
       (list :command-line "nix flake update /Users/dlevy/.config/nixos"
             :command-name "Update darwin"
             :display "Update the darwin flake inputs")
       (list :command-line "nix-collect-garbage -d"
             :command-name "GC darwin"
             :display "Delete unused derivations and previous generations"))))
  (add-to-list 'run-command-recipes #'dan/run-command--nix t))

MacOS recipes.

(after! run-command
  (when IS-MAC
    (defun dan/run-command--macos ()
      (list
       (list :command-line "arch -arm64 brew upgrade"
             :command-name "Upgrade Brew"
             :display      "Upgrade Brew packages")))
    (add-to-list 'run-command-recipes #'dan/run-command--macos)))

Rust

Use rust-analyzer by default, since it has more features.

(use-package! rustic
  :defer t
  :custom
  (rustic-lsp-server 'rust-analyzer)

When using the rustic popup, be in Emacs state.

:config
(when (featurep 'evil)
  (add-hook! 'rustic-popup-mode-hook #'evil-emacs-state)))

Snow

A nice, comfy, useless package.

(package! snow)

We define an auto-load function as an entry point to the winter season.

(use-package! snow
  :commands snow)

Splash Screen

Default doom dashboard is pretty and welcoming, let’s just give it a small personal touch.

(setq fancy-splash-image (expand-file-name "img/qs.png" doom-private-dir))

But what if I am in the terminal? No worries:

(defun dan/my-weebery-is-always-greater ()
  (mapc (lambda (line)
          (insert (propertize (+doom-dashboard--center +doom-dashboard--width line)
                              'face 'doom-dashboard-banner) " ")
          (insert "\n"))
        '("⢸⣿⣿⣿⣿⠃⠄⢀⣴⡾⠃⠄⠄⠄⠄⠄⠈⠺⠟⠛⠛⠛⠛⠻⢿⣿⣿⣿⣿⣶⣤⡀⠄"
          "⢸⣿⣿⣿⡟⢀⣴⣿⡿⠁⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⣸⣿⣿⣿⣿⣿⣿⣿⣷"
          "⢸⣿⣿⠟⣴⣿⡿⡟⡼⢹⣷⢲⡶⣖⣾⣶⢄⠄⠄⠄⠄⠄⢀⣼⣿⢿⣿⣿⣿⣿⣿⣿⣿"
          "⢸⣿⢫⣾⣿⡟⣾⡸⢠⡿⢳⡿⠍⣼⣿⢏⣿⣷⢄⡀⠄⢠⣾⢻⣿⣸⣿⣿⣿⣿⣿⣿⣿"
          "⡿⣡⣿⣿⡟⡼⡁⠁⣰⠂⡾⠉⢨⣿⠃⣿⡿⠍⣾⣟⢤⣿⢇⣿⢇⣿⣿⢿⣿⣿⣿⣿⣿"
          "⣱⣿⣿⡟⡐⣰⣧⡷⣿⣴⣧⣤⣼⣯⢸⡿⠁⣰⠟⢀⣼⠏⣲⠏⢸⣿⡟⣿⣿⣿⣿⣿⣿"
          "⣿⣿⡟⠁⠄⠟⣁⠄⢡⣿⣿⣿⣿⣿⣿⣦⣼⢟⢀⡼⠃⡹⠃⡀⢸⡿⢸⣿⣿⣿⣿⣿⡟"
          "⣿⣿⠃⠄⢀⣾⠋⠓⢰⣿⣿⣿⣿⣿⣿⠿⣿⣿⣾⣅⢔⣕⡇⡇⡼⢁⣿⣿⣿⣿⣿⣿⢣"
          "⣿⡟⠄⠄⣾⣇⠷⣢⣿⣿⣿⣿⣿⣿⣿⣭⣀⡈⠙⢿⣿⣿⡇⡧⢁⣾⣿⣿⣿⣿⣿⢏⣾"
          "⣿⡇⠄⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⢻⠇⠄⠄⢿⣿⡇⢡⣾⣿⣿⣿⣿⣿⣏⣼⣿"
          "⣿⣷⢰⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⢰⣧⣀⡄⢀⠘⡿⣰⣿⣿⣿⣿⣿⣿⠟⣼⣿⣿"
          "⢹⣿⢸⣿⣿⠟⠻⢿⣿⣿⣿⣿⣿⣿⣿⣶⣭⣉⣤⣿⢈⣼⣿⣿⣿⣿⣿⣿⠏⣾⣹⣿⣿"
          "⢸⠇⡜⣿⡟⠄⠄⠄⠈⠙⣿⣿⣿⣿⣿⣿⣿⣿⠟⣱⣻⣿⣿⣿⣿⣿⠟⠁⢳⠃⣿⣿⣿"
          "⠄⣰⡗⠹⣿⣄⠄⠄⠄⢀⣿⣿⣿⣿⣿⣿⠟⣅⣥⣿⣿⣿⣿⠿⠋⠄⠄⣾⡌⢠⣿⡿⠃"
          "⠜⠋⢠⣷⢻⣿⣿⣶⣾⣿⣿⣿⣿⠿⣛⣥⣾⣿⠿⠟⠛⠉⠄⠄          ")))

(when (not (display-graphic-p))
  (setq +doom-dashboard-ascii-banner-fn #'dan/my-weebery-is-always-greater))

telega

telega is full featured unofficial client for Telegram platform for GNU Emacs.

(package! telega
  :recipe (:files (:defaults
                   "contrib/*.el"
                   "etc"
                   "server"
                   "Makefile")))

Minimal configuration to be able to build the server and load the cache.

(use-package! telega
  :defer t
  :custom
  (telega-directory (expand-file-name "telega" doom-cache-dir))
  (telega-video-player-command '(concat "mpv"
                                        (when telega-ffplay-media-timestamp
                                          (format " --start=%f" telega-ffplay-media-timestamp))))
  (telega-completing-read-function completing-read-function)
  (telega-use-docker t))

Quick keybinding to open and close telega.

(map! (:leader
       :desc "Telegram" :mv "o m" #'dan/=telega)
      (:after telega
       :map telega-root-mode-map
       :n "q" #'dan/=telega-kill))

evil-snipe shadows the search default bindings of telega in evil-collection, so we disable it for telega-root-mode.

(after! evil-snipe
  (add-to-list 'evil-snipe-disabled-modes 'telega-root-mode))

We set up auto-completion for chats.

(after! telega
  (set-company-backend! 'telega-chat-mode
    '(company-ispell
      company-dabbrev
      telega-company-telegram-emoji
      telega-company-username
      telega-company-botcmd
      telega-company-hashtag)))

Finally, we add some extra goodies, some in core and others in contrib. These include visual customization like shortening url's or code blocks syntax highlighting to more complicated functionality like system notifications brought to you via alert.el.

(add-hook! telega-load
           ;; core
           #'telega-mode-line-mode
           #'global-telega-squash-message-mode
           #'telega-notifications-mode
           #'telega-autoplay-mode

           ;; contrib
           #'global-telega-url-shorten-mode
           #'global-telega-mnz-mode
           #'telega-alert-mode
           #'telega-transient-mode
           #'telega-status-history-mode)

Highlight the current line in telega buffer.

(add-hook! '(telega-root-mode-hook telega-chat-mode-hook)
           #'hl-line-mode)

Sometimes telega emacs when trying to quit it. Forcing to quit telega when quitting emacs prevents this situation.

(add-hook! kill-emacs
  (when (and (boundp 'telega-root-buffer-name)
             (get-buffer telega-root-buffer-name))
    (telega-kill t)))

telega root buffer is managed by the workspace configuration.

(after! telega
  (set-popup-rule! "\\`\\*Telega.+"
    :ignore t))

Autoloads

Some functions to integrate with the workspaces module.

;;; $DOOMDIR/autoload/telega.el -*- lexical-binding: t; -*-

A function to open the telega workspace.

(defvar +telega-workspace-name "Telega")

;;;###autoload
(defun dan/=telega (&optional arg)
  "Like `telega', but opens the buffer in it's own workspace."
  (interactive "P")
  (+workspace-switch +telega-workspace-name t)
  (unless (memq (buffer-local-value 'major-mode (window-buffer (selected-window)))
                '(telega-root-mode telega-chat-mode telega-image-mode))
    (doom/switch-to-scratch-buffer)
    (telega arg)
    (+workspace/display)))

A function to close the telega workspace.

;;;###autoload
(defun dan/=telega-kill (&optional args)
  "Like `telega-kill', but deletes the workspace associated with `+telega-workspace-name' if it exists."
  (interactive "P")
  (telega-kill args)
  (+workspace-delete +telega-workspace-name))

topspace

Scroll down and recenter top lines in Emacs.

  • Easier on the eyes: Recenter or scroll down top text to a more comfortable eye level for reading, especially when in full-screen or on a large monitor.
  • Easy to use: No new keybindings are required, keep using all your previous scrolling & recentering commands, except now you can also scroll above the top lines. It also integrates seamlessly with centered-cursor-mode to keep the cursor centered all the way to the top line.
(package! topspace)

A package I didn’t know I needed.

(use-package! topspace
  :custom
  (topspace-active             #'dan/topspace--active-p)
  (topspace-autocenter-buffers #'dan/topspace--active-p)
  (indicate-empty-lines        t)
  :hook
  (doom-first-buffer . global-topspace-mode))

We create a blacklist of sorts to deactivate topspace-mode in special modes.

(after! topspace
  (defun dan/topspace--active-p ()
    (not (or (minibufferp)
             (derived-mode-p 'vterm-mode
                             'comint-mode
                             'org-agenda-mode
                             '+doom-dashboard-mode
                             'eshell-mode
                             'magit-mode
                             'telega-root-mode
                             'telega-chat-mode
                             'image-mode
                             'Custom-mode
                             'compilation-mode
                             'cfw:calendar-mode
                             'special-mode)))))

Topsy

This library shows a sticky header at the top of the window. The header shows which definition the top line of the window is within.

(package! topsy)

We activate topsy-mode only in programming contexts.

(use-package! topsy
  :defer t
  :init
  (add-hook! prog-mode
    (unless (memq major-mode '(+doom-dashboard-mode org-mode dirvish-mode))
      (topsy-mode +1)))

To make the header-line a little more noticeable while consistent with the buffer style, we change it’s font and inherit from the default style.

:config
(custom-set-faces!
  '(header-line :family "Victor Mono"
                :inherit default)))

Tramp

Already have ssh-controlmaster configured in my config for ssh. Additionally, make the chunksize a reasonable default.

(after! tramp-sh
  (setq tramp-use-ssh-controlmaster-options nil
        tramp-chunksize 2000))

uniquify

Uniquify makes buffers with the same name easier to distinguish. The ~’forward~ configuration shows the file path of the buffer.

(setq uniquify-buffer-name-style 'forward)

use-package

We want packages to be lazy by default. In other words, load only on demand.

(setq use-package-always-defer t)

Make it easy to find use-package definitions in emacs-lisp buffers.

(setq use-package-enable-imenu-support t)

vc-gutter

Let’s add minor customization to the vc-gutter module:

First, diff the file while the buffer is changing, not just when it’s saved.

(setq +vc-gutter-diff-unsaved-buffer t)

Lastly, let’s activate git-gutter for remote files as well.

(setq +vc-gutter-in-remote-files t)

vlf

Emacs minor mode that allows viewing, editing, searching and comparing large files in batches, trading memory for processor time.

(package! vlf)

vlf-setup adds a menu option to use vlf when accessing a file larger than large-file-warning-threshold. Other optimizations included expanding the number of bytes read on each batch and activating so-long-mode eagerly.

(use-package! vlf
  :defer-incrementally t
  :custom
  (vlf-batch-size-remote read-process-output-max)
  :config
  (require 'vlf-setup)
  (add-hook! 'vlf-mode-hook #'so-long-mode))

(after! so-long
  (add-to-list 'so-long-mode-preserved-variables 'vlf-mode))

For opening big files in remote servers, this package is a God send. When vlf-mode is active, other useful keybindings can be found under the prefix C-c v.

Vterm

Use default system shell for vterm.

(after! vterm
  (setq-default vterm-shell (getenv "SHELL")))

Make urls clickable in vterm modes.

(add-hook! vterm-mode #'goto-address-mode)

Maximum scrollback to the max!

(after! vterm
  (setq vterm-max-scrollback 100000))

Make vterm as snappy as it can be.

(after! vterm
  (setq vterm-timer-delay nil))

Use bash as default shell in remote servers.

(after! vterm
  (pushnew! vterm-tramp-shells
            '("ssh" "/bin/bash")
            '("scp" "/bin/bash")))

Unfortunately, MacOS M1 compilation is hardly straightforward. For a HACK/FIX, I force compilation through the latest gcc installed through homebrew.

(after! vterm
  (defadvice! dan/vterm-module-compile (fn &rest _)
    :around #'vterm-module-compile
    (if IS-MAC
        (let* ((vterm-directory
                (shell-quote-argument (file-name-directory (locate-library "vterm.el" t))))
               (vterm-module-cmake-args "-DCMAKE_C_COMPILER=`which gcc-12`")
               (make-commands
                (concat
                 "cd " vterm-directory "; \
               rm -rf build; \
               mkdir -p build; \
               cd build; \
               cmake -B build "
                 vterm-module-cmake-args
                 " ..; \
               arch -arm64 cmake --build build; \
               cd -"))
               (buffer (get-buffer-create vterm-install-buffer-name)))
          (pop-to-buffer buffer)
          (compilation-mode)
          (if (zerop (let ((inhibit-read-only t))
                       (call-process "sh" nil buffer t "-c" make-commands)))
              (message "Compilation of `emacs-libvterm' module succeeded")
            (error "Compilation of `emacs-libvterm' module failed!")))
      (funcall fn))))

Vundo

A visual representation of emacs built-in undo tree.

(package! vundo)

For node navigation:

KeyDescription
lto go forward
hto go backward
jto go to the node below when you at a branching point
kto go to the node above
Hto go back to the last branching point
Lto go forward to the end/tip of the branch
qto quit, you can also type C-g
(use-package! vundo
  :custom
  (vundo-glyph-alist     vundo-unicode-symbols)
  (vundo-compact-display t)
  :config
  (map! :map vundo-mode-map
        [remap doom/escape]        #'vundo-quit)
  :defer t)

Set an entry point.

(map! :leader
      :desc "Visual Undo Tree" "b U" #'vundo)

Which Key

Doom Emacs default configuration is too slow, let’s speed it up.

(after! which-key
  (setq which-key-idle-delay           0.1
        which-key-idle-secondary-delay 0.2))

Whitespace

Make white spaces visible in programming modes.

(after! prog-mode
  (add-hook! prog-mode #'whitespace-mode))

Minimal configuration to show most of everything except newlines. Missing newlines and end-of-files are also visually indicated.

(add-transient-hook! 'prog-mode-hook
  (setq whitespace-style '(face
                           indentation
                           tabs
                           tab-mark
                           spaces
                           space-mark
                           missing-newline-at-eof
                           trailing)))

Word wrap

I work with lots of files that have long lines, which can be hard to read sometimes. +word-wrap-mode helps to make these files more readable without changing the buffer contents.

(add-hook! 'doom-first-buffer-hook #'+global-word-wrap-mode)

World Clock

I have family all over the place, it’s hard to keep up with the time difference. Luckily, emacs can help with world-clock mode.

(after! time
  (setq world-clock-list '(("America/Denver"      "Colorado")
                           ("America/New_York"    "North Wales")
                           ("America/Sao_Paulo"   "Brasilia")
                           ("Europe/Madrid"       "Madrid")
                           ("Europe/Kiev"         "Ukraine")
                           ("Asia/Tokyo"          "Tokyo"))))

Make the wclock buffer into a popup.

(set-popup-rule! "^\\*wclock" :quit 'current :side 'top)

Make it quit-able with q.

(after! time
  (map! :map 'world-clock-mode-map
        :nm "q" #'quit-window))

Make it play nice with evil-mode.

(add-hook! world-clock-mode #'evil-normalize-keymaps)

Give it an easy entry keybinding SPC o w.

(map! :leader
      :desc "World Clock" :nm "o w" #'world-clock)

Writeroom

Keep header lines for +zen-mode, so org-sticky-header-mode works as intended.

(add-hook! org-mode
  (setq-local writeroom-header-line t))

Make zen-mode togglable in a per file basis, just like in this config.org!

(add-to-list 'safe-local-eval-forms
             '(when (and (fboundp #'+zen/toggle)
                         (not noninteractive))
                (+zen/toggle)))

xterm-color

xterm-color.el is an ANSI control sequence to text-property translator.

(package! xterm-color)

Lazy load.

(use-package! xterm-color
  :defer t)

Yasnippet

YASnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates.

org-mode

For 日本語 grammar snippets.

# -*- mode: snippet -*-
# name: 文法の事項
# key: bunpou
# --

`(make-string (+ 1 (org-outline-level)) ?*)` ${1:文法の事項}

${2:説明}

`(make-string (+ 2 (org-outline-level)) ?*)` 文型

${3:文型}

`(make-string (+ 2 (org-outline-level)) ?*)` 翻訳

${4:翻訳}

`(make-string (+ 2 (org-outline-level)) ?*)`${5:}
${6:例の翻訳}

For starting a new project!

# -*- mode: snippet -*-
# name: New project
# key: project
# --

`(make-string (+ 1 (org-outline-level)) ?*)` PROJ ${1:name} :${2:tag_name}:

${3:description}

`(make-string (+ 2 (org-outline-level)) ?*)` Ideas
`(make-string (+ 3 (org-outline-level)) ?*)` IDEA ${4:My first task!}
`(make-string (+ 2 (org-outline-level)) ?*)` Tasks
`(make-string (+ 2 (org-outline-level)) ?*)` Events

For adding a new package in package.el.

# -*- mode: snippet -*-
# name: New package
# key: package
# --
#+begin_src emacs-lisp :tangle packages.el
(package! ${1:package-name})
#+end_src

For adding new autoload functions in autoload/*.el.

# -*- mode: snippet -*-
# name: New Autoload function
# key: autoload
# --
#+begin_src emacs-lisp :tangle autoload/${1:`(downcase (car (last (org-get-outline-path))))`}.el
;;;###autoload
(defun dan/${2:function} (${3:args})
   "${4:docs}"
   $0)
#+end_src

Private modules.

doom allows the user to to write their own modules in their $DOOMDIR directory, which will be auto-loaded at startup. It offers an extra features like interaction and extension of bin/doom, a fixed file structure where each file is loaded at different points of the runtime and other niceties (more macros!).

For now, I haven’t had the need to use this feature but is good to be aware of it.

About

Doom Emacs Configuration

License:GNU General Public License v3.0


Languages

Language:Emacs Lisp 100.0%