Table of Contents
1 Introduction
Welcome! In this booklet I will introduce you to Literate Configuration, which is the application of Literate Programming to configuration files. If you are already familiar with Literate Programming you might find most of the basic concepts familiar. Literate Programming is a beautiful concept, but it is ambitious and hard to apply in general software development. However, I have found that it can be especially applicable to configuration files, for several reasons:
- Configuration files are inherently focused, since they correspond to a single application, program or set of programs, all related. This makes it easier to draw a narrative for them;
- Most configuration files are self-contained but their structure and syntax may not be immediately evident, so they benefit from a human-readable explanation of their contents;
- Configuration files are often shared and read by others, as we all like to learn by reading the config files of other people. Applying Literate Programming to config files makes them much easier to share, since their explanation is naturally woven into the code;
- Org-mode has emerged in the last few years as a powerful and simple markup language for general writing, but with unique features that make it easy to include code within the text, and even further, to easily extract that code into stand-alone source files which can be interpreted by their corresponding programs.
Whether you already use Emacs and org-mode or not, I hope you will find value in this book by seeing how uniquely Literate Programming can help you better write, maintain, understand and share your config files.
Literate Programming
Literate programming was first proposed by Donald Knuth in 1984 (Literate Programming, The Computer Journal, 27 (2): 97–111) as a technique of producing computer code from prose. The general idea is that you write code as you would write a story or an article, describing what you want to do and how to solve it. The code gets interweaved with the code as you explain it. Ultimately, you can produce one of two outputs from the same file:
- A human-readable version of the code, nicely typeset with all the explanations and code, through a process called weaving;
- A computer-executable version of the code, which extracts only the code and puts it in the correct order for the computer to run, through a process called tangling.
The idea of literate programming has been around since then, and a number of tools have been created, starting from Knuth’s own CWEB, Ramsey’s noweb, and others. However, the lack of a standard set of tools has made its adoption slow and limited to specialized instances.
During my first encounter with literate programming I used noweb
, but beyond some school projects and homeworks, I could never get Literate Programming to “stick”, until I encountered Org mode.
Org-mode
Org mode is a markup format and corresponding toolset developed originally within the Emacs text editor. Org provides a wide range of functionality, including text formatting, agenda and todo tracking, and much more. For our purposes, we are interested in the combination of two of its functions:
- Text formatting and exporting. This is one of Org-mode’s core functionalities, and which provides the weaving aspect of Literate Programming by allowing us to produce nicely rendered versions of our document in any of Org’s supported output formats (out of the box Org supports LaTeX, PDF, HTML and others, but many more are available through third-party modules), including both the text narration and the code. The output document may even include syntax highlighting appropriate for the language in which it is written.
- Code block evaluation and exporting. This is enabled by org-babel, one of Org’s built-in modules, which allows flexible manipulation, execution and exporting of code embedded within an Org document. Babel supports tangling code blocks into one or more separate files which include only the code portions of the document.
Using Org-mode with Babel for literate programming is, of course, not a new idea. However, most cases of Literate Configuration that I have seen focus on using Literate Programming for configuring Emacs itself. This is a good use case, but limited in my opinion. In this booklet we will explore how you can use Literate Programming for all your configuration files.
2 The Tools and Process
Emacs and Org-mode
The basic tools in our setup will be Emacs and Org-mode. I assume you have at least a basic knowledge of Emacs, but if you don’t: don’t worry! It takes very little to get started. If you want a gentle introduction, check out the Guided Tour of Emacs. If you have already installed Emacs, you can start an interactive tutorial within Emacs itself by pressing C-h
followed by t
(if you are using a graphical version of Emacs, this is likely also available from within the Help menu). If you currently use vi
or vim
, check out Spacemacs, which will allow you to start using Emacs without having to relearn any new keybindings.
Org-mode is included with all recent versions of Emacs, so chances are you already have it installed. Emacs 26.3 (the one I use as oft his writing) includes org-mode 9.1.9, which is quite recent and more than enough for the uses I describe here. If you want to upgrade to the latest version, see the Org-mode Installation Instructions.
How it works
In general, the process of generating config files from Org files can be illustrated as follows:
Org mode is very flexible, so you can decide how to structure and organize both your source (Org) and destination (config) files in the way that suits you best. Here are some options:
- Org file stored in the same directory as the resulting config file. Usually one org file corresponds to one config file, but multiple related config files (e.g. for the same program or application) could be combined in the same Org file, from which all of them are generated. The advantage of this method is that the source Org files and the configuration files they produce are stored in the same location, which makes it easier to relate them for your own use, and to share them if you put them, for example, in a Github repository. The disadvantage is that the config directory gets “polluted” by the source files, and some applications might even produce errors or complain about having extraneous files in their configuration directories. However, this is very uncommon. This is the setup I use and recommend, and which is described in this booklet.
- All the org files stored in a single directory (or directory tree, or even in a single file), separate from the final destinations of the config files they contain. This has the advantage of giving you a central location for all your org files, regardless of the config files they produce. The drawback is that it makes it harder to selectively share the configuration files for a single program.
- Of course, you can combine the two approaches if it makes sense for you. For example, you might choose to have all your shell’s config files produced from a single Org file, while having separate Org files for other, individual config files.
Ultimately it is up to you to decide which scheme to use. The basic techniques are the same, and the point of literate config is to make things easier for you to understand and maintain.
Configuring Emacs and org-mode for literate programming
To get started with literate config, the only really indispensable package is org
, which can be loaded without prior installation since it is included with Emacs:possible use of literate config:
(
require
'org
)
As you write your config file, you can use the org-babel-tangle
command (bound by default to C-c C-v t
) to extract the code from the current file into the corresponding code files. Conversely, you can use org-export-dispatch
(C-c C-e
) to export your file to any of Org’s supported output formats.
There are other optional configurations which you can use to make the experience more efficient and pleasant. You can find these below.
Automatic tangle on save
After some time, it can get tedious to press C-c C-v t
to tangle your files, and you run the risk of forgetting to do it after a change, resulting in a discrepancy between your Org file and your config files. To avoid this, you can configure a hook that automatically runs org-babel-tangle
upon saving any org-mode buffer, which means the resulting files will be automatically kept up to date.
(
org-mode
.
(
lambda
()
(
add-hook
'after-save-hook
'org-babel-tangle
'run-at-end
'only-in-org-mode
)))
Language-specific org-babel support
You can load and enable org-babel
language-specific packages. Many are included in org-mode, while others can be found online. Most org-babel support packages are named ob-<language>
. For example, these are some of the ones I use:
- CFEngine, used extensively for my book Learning CFEngine.
(
use-package
ob-cfengine3
:after
org
)
- Elvish, my favorite shell.
(
use-package
ob-elvish
:after
org
)
After you load any necessary packages, you should configure the languages for which to load org-babel
support. Note that this does not affect the ability to export/tangle code, but allows you to execute snippets of code from within the Org buffer by pressing C-c C-c
on them, and have the results automatically inserted into the buffer.
(
org-babel-do-load-languages
'org-babel-load-languages
'
((
cfengine3
.
t
)
(
ruby
.
t
)
(
latex
.
t
)
(
plantuml
.
t
)
(
python
.
t
)
(
shell
.
t
)
(
elvish
.
t
)
(
calc
.
t
)
(
dot
.
t
)
(
ditaa
.
t
)))
Beautifying org-mode
Org-mode allows extensive configuration of its fonts and colors, and doing so can significantly improve the experience of editing literate code with Emacs. In the end, you can have an Emacs setup for editing org documents which looks very nice, with proportional fonts for text and monospaced fonts for code blocks, examples and other elements. For example, here is what a fragment of my Emacs config file looks like:

Instead of describing the configuration here, I’ll just point you to the Beautifying org-mode section of my Emacs config - you can find all the details there.
Structure of a literate config file
A literate config file is simply an org file which has source code blocks in it with the :tangle
option to specify the file to which they should be extracted. If more than one source block specifies the same file, they will be appended in the order they appear. For example, a very simple bash config file can be produced as follows:
bash
literate config file
* Bash config
Set the PATH environment variable to include my ~$HOME/bin~ directory.
#+begin_src bash :tangle ~/.profile
PATH=($HOME/bin $PATH)
#+end_src
If your whole org file gets tangled to a single config file, you can specify the :tangle
header globally to avoid having to specify it for every code block. To do this, you specify a header-args
global property corresponding to the language of the source blocks you want to export. For example, I have the following line at the top my init.org
Hammerspoon config source file:
#+property: header-args:lua :tangle init.lua
If the basename of your org file is the same as the exported source file (for example, init.org
produces init.lua
), you can even use a bit of Emacs Lisp magic to avoid having to specify the output filename by hand, just the extension (shown here in two lines, make sure you type everything in a single line):
#+property: header-args:lua :tangle
(concat (file-name-sans-extension (buffer-file-name)) ".lua")
Even if you specify the tangle filename globally, you can still change its value for individual code blocks. For example, you can prevent a code block from being tangled by specifying :tangle no
, or specify a different filename to write it to a file different from the global one.
3 Tips and tricks
We now look at some useful tips that will make your life easier when writing literate config files.
Converting your existing config files
You probably already have a number of long and complex config files. The beauty of literate config is that you don’t have to do a lot of work to start using it. As a first step, you can simply include your whole file into a single source block. For example, if you already have a /.profile
file, you can create /.profile.org
with the following:
* My Bash config
#+begin_src bash :tangle ~/.profile
<contents of your existing .profile file>
#+end_src
You can then start breaking it up in logical blocks, adding commentary and other structure as you see fit. For this, you may find useful the org-babel-demarcate-block
command, bound by default to C-c C-v C-d
. When invoked within a source block, this command splits the block at that point, creating two source blocks with the same language and any other header arguments as the original one.
Using noweb references to structure your code
Exporting source blocks in the sequence in which they appear in the Org file is useful, but ultimately little more than extensively documenting your file with comments. The real power of literate programming comes from being able to express the logic of your code in a way that makes sense to a human reading it. This does not necessarily match the order or structure in which the code needs to be read by the computer.
This is possible in Org by using noweb references, so called because they follow the same convention introduced by the early noweb literate programming tool. In this convention, a code block can have an arbitrary name, and be referenced (included) in other blocks by specifying its name inside double angle brackets: <<code block name>>
.
Processing of noweb references is enabled by the :noweb
header argument in source blocks. This argument can have multiple values, but I have found the following to be the most useful:
-
:noweb yes
enables Noweb processing in the block, both for tangling and for exporting (i.e. when you export the code block to HTML, LaTeX or some other format, the noweb references will be expanded as well); -
:noweb no-export
enables Noweb processing in the block when tangling, but not when exporting. This is useful for leaving the noweb indicators in the human-readable exports of your file, which can make it easier to understand.
The name of an individual code block can be specified with the :noweb-ref
header argument. If multiple blocks have the same :noweb-ref
value, they will be concatenated when referenced. You can also specify the name of a block using a #+name:
property line before the source block, but in this case there can be only one block with the given name.
Consider the following org code:
Prepend some paths to the previous value.
#+begin_src bash :tangle ~/.profile :noweb no-export
export PATH=<<Personal paths>>:<<Homebrew paths>>:$PATH
#+end_src
These are the paths in which I store my personal scripts and binaries:
#+begin_src bash :noweb-ref Personal paths
~/bin
#+end_src
These are the directories in which Homebrew binaries are installed:
#+begin_src bash :noweb-ref Homebrew paths
/usr/local/bin:/usr/local/sbin:/usr/local/opt/coreutils/libexec/gnubin
#+end_src
Here we can see that the first block references the two other by their names, and they get combined to produce the final tangled output. This is of course a very simple example, but in complex config files, this technique can make it much easier to see the overall structure of the code before delving in the details. For a more realistic example, see the Org mode section of my Emacs config. Here you can see a top-level block as follows:
(
use-package
org
:pin
manual
:load-path
(
"lisp/org-mode/lisp"
"lisp/org-mode/lisp/contrib/lisp"
)
:bind
<<org-mode-keybindings>>
:custom
<<org-mode-custom-vars>>
:custom-face
<<org-mode-faces>>
:hook
<<org-mode-hooks>>
:config
<<org-mode-config>>
)
The different noweb references get “filled in” throughout the rest of the configuration by assigning them to the corresponding :noweb-ref
values. This makes it possible to specify keybindings, variables, faces, hooks and custom config code in the sections where it makes logical sense, rather than where they need to be included in the code.
Multiple config files per org file
Most Literate Config Org-mode files will probably generate a single config file. However, as illustrated in How it works, you can easily produce multiple config files from a single org file. For this, you can use one of the following techniques:
- Manually specify the file to which each source block should be tangled, using the
:tangle
header argument to specify it; - Specify a main tangle target globally, as described in Structure of a literate config file, and specify the
:tangle
argument for the blocks that should be written to a different file; - If the different files are produced from within different sections of the Org document, you can specify the
:tangle
argument per section, by specifying it within a:PROPERTIES
drawer of the corresponding headline. For example, in the following source, each section gets tangled to a different output file:
* Bash .profile
:PROPERTIES:
:header-args:bash: :tangle ~/.profile
:END:
#+begin_src bash
this code goes to ~/.profile
#+end_src
* Bash .bashrc
:PROPERTIES:
:header-args:bash: :tangle ~/.bashrc
:END:
#+begin_src bash
this code goes to ~/.bashrc
#+end_src
4 Literate config examples
In this chapter you will find three real-world examples of literate configuration files created, maintained and used by me. These are the real thing—what you see here is included directly from the Org files that contain the configuration I use for Emacs, Hammerspoon and Elvish, respectively. You can find the latest version of these files, both in Org and in their corresponding tangled output, in Github at the locations specified in each of the sections.
Note also that each section links to a post in my blog containing the corresponding config file. This illustrates another advantage of literate configuration: that the same Org file can be used to produce the output in multiple formats. Each one of the literate config files you see below are rendered in the following places:
- This book, produced by exporting the Org files in Leanpub’s Markua format using the
ox-leanpub
package; - The corresponding blog posts in my blog, produced by exporting in Hugo Markdown format using the
ox-hugo
package; - Directly in Github, thanks to Github’s support for rendering Org files.
Emacs config
This is my Emacs configuration file.
This file is written in literate programming style using org-mode. See init.el for the generated file. You can see this in a nicer format on my blog post My Emacs Configuration, With Commentary.
References
Emacs config is an art, and I have learned a lot by reading through other people’s config files, and from many other resources. These are some of the best ones (several are also written in org mode). You will find snippets from all of these (and possibly others) throughout my config.
Performance optimization
Lately I’ve been playing with optimizing my Emacs load time. I have found a couple of useful resources, including:
- Two easy little known steps to speed up Emacs start up time
- Advanced Techniques for Reducing Emacs Startup Time
Based on these, I have added the code below.
First, a hook that reports how long and how many garbage collections the startup took. We use a hook to run it at the very end, so the message doesn’t get clobbered by other messages during startup.
(
add-hook
'emacs-startup-hook
(
lambda
()
(
message
"Emacs ready in %s with %d garbage collections."
(
format
"%.2f seconds"
(
float-time
(
time-subtract
after-init-time
before-init-time
)))
gcs-done
)))
Next, we wrap the whole init file in a block that sets file-name-handler-alist
to nil
to prevent any special-filename parsing of files loaded from the init file (e.g. remote files loaded through tramp, etc.). The let
block gets closed in the Epilogue.
(
let
((
file-name-handler-alist
nil
))
Optionally enable debug-on-error
- I do this only when I’m trying to figure out some problem in my config.
;;(setq debug-on-error t)
We set gc-cons-threshold
to its maximum value, to prevent any garbage collection from happening during load time. We also reset this value in the Epilogue.
(
setq
gc-cons-threshold
most-positive-fixnum
)
Customized variables
Emacs has its own Customization mechanism for easily customizing many parameters. To make it easier to manage, I keep the customized variables and faces in a separate file and load it from the main file. A lot of my custom settings are configured from this init file as well, but there are always some which I change by hand for added flexibility.
(
setq
custom-file
"~/.emacs.d/custom.el"
)
(
load
custom-file
)
My current custom.el
file can be found at https://github.com/zzamboni/dot-emacs/blob/master/custom.el.
Password management
Password management using auth-sources
and pass
(I normally use 1Password, but I have not found a good command-line/Emacs interface for it, so I am using pass
for now for some items I need to add to my Emacs config file).
(
require
'auth-source
)
(
require
'auth-source-pass
)
(
auth-source-pass-enable
)
Package management
I use the wonderful use-package to manage most of the packages in my installation (one exception is org-mode
, see below). As this is not bundled yet with Emacs, the first thing we do is install it by hand. All other packages are then declaratively installed and configured with use-package
. This makes it possible to fully bootstrap Emacs using only this config file, everything else is downloaded, installed and configured automatically.
First, we declare the package repositories to use.
(
customize-set-variable
'package-archives
'
((
"marmalade"
.
"https://marmalade-repo.org/packages/"
)
(
"melpa"
.
"https://melpa.org/packages/"
)))
Then we initialize the package system, refresh the list of packages and install use-package
if needed.
(
package-initialize
)
(
when
(
not
package-archive-contents
)
(
package-refresh-contents
))
(
when
(
not
(
package-installed-p
'use-package
))
(
package-install
'use-package
))
Finally, we load use-package
.
(
require
'use-package
)
We set some configuration for use-package
:
- The
use-package-always-ensure
variable indicates thatuse-package
should always try to install missing packages. For some libraries this is not appropriate, and in those cases you see the:ensure nil
declaration as part of theuse-package
statement. This applies mainly to libraries which are installed as part of some other package (happens mostly with some libraries that come with org-mode).(
customize-set-variable
'use-package-always-ensure
t
)
- The
use-package-always-defer
sets:defer true
as the default for all package declarations. This makes Emacs startup much faster by preventing packages from being loaded when Emacs starts, and only doing so when they are needed. Some packages don’t work well with this, so you’ll see some declarations when I explicitly set:defer nil
to force the package to be loaded at startup, or:defer n
to load the package, but onlyn
seconds after startup.(
customize-set-variable
'use-package-always-defer
t
)
- The
use-package-verbose
variable enables verbose loading of packages, useful for debugging. I set/unset this according to need.(
customize-set-variable
'use-package-verbose
nil
)
Testing quelpa
and to install packages directly from their github repositories (and other places). I install quelpa
using use-package
first, and then install quelpa-use-package
to allow using quelpa
from within use-package
declarations. Very recursive.
(
use-package
quelpa
:defer
nil
)
(
use-package
quelpa-use-package
:defer
nil
:after
quelpa
)
This variable tells Emacs to prefer the .el
file if it’s newer, even if there is a corresponding .elc
file. Also, use auto-compile
to autocompile files as needed.
(
customize-set-variable
'load-prefer-newer
t
)
(
use-package
auto-compile
:defer
nil
:config
(
auto-compile-on-load-mode
))
Set the load path to the directories from where I sometimes load things outside the package system. Note that the path for org-mode
(which I load from a checkout of its git repository) is set as part of its use-package
declaration, so it doesn’t appear here.
(
add-to-list
'load-path
"~/.emacs.d/lisp"
)
Giving a try to Paradox for an enhanced package management interface. We set paradox-github-token
to t
to disable GitHub integration (I don’t want to star repos).
(
use-package
paradox
:defer
nil
:custom
(
paradox-github-token
t
)
:config
(
paradox-enable
))
Settings
Proxy settings
These are two short functions I wrote to be able to set/unset proxy settings within Emacs. I haven’t bothered to improve or automate this, as I pretty much only need it to be able to install packages sometimes when I’m at work. For now I just call them manually with M-x zz/(un)set-proxy
when I need to.
(
defun
zz/set-proxy
()
(
interactive
)
(
customize-set-variable
'url-proxy-services
'
((
"http"
.
"proxy.corproot.net:8079"
)
(
"https"
.
"proxy.corproot.net:8079"
))))
(
defun
zz/unset-proxy
()
(
interactive
)
(
customize-set-variable
'url-proxy-services
nil
))
Miscellaneous settings
- Load the
cl
library to enable some additional macros (e.g.lexical-let
).(
require
'cl
)
- Start the Emacs server
(
server-start
)
- This is probably one of my oldest settings - I remember adding it around 1993 when I started learning Emacs, and it has been in my config ever since. When
time-stamp
is run before every save, the stringTime-stamp: <>
in the first 8 lines of the file will be updated with the current timestamp.(
add-hook
'before-save-hook
'time-stamp
)
- When at the beginning of the line, make
Ctrl-K
remove the whole line, instead of just emptying it.(
customize-set-variable
'kill-whole-line
t
)
- Paste text where the cursor is, not where the mouse is.
(
customize-set-variable
'mouse-yank-at-point
t
)
- Make completion case-insensitive.
(
setq
completion-ignore-case
t
)
(
customize-set-variable
'read-file-name-completion-ignore-case
t
)
(
customize-set-variable
'read-buffer-completion-ignore-case
t
)
- Show line numbers. I used
linum-mode
before, but it caused severe performance issues on large files. Emacs 26 introducesdisplay-line-numbers-mode
, which has no perceivable performance impact even on very large files. Disabled for now.(
when
(
>=
emacs-major-version
26
)
(
use-package
display-line-numbers
:defer
nil
:ensure
nil
:config
(
global-display-line-numbers-mode
)))
- Highlight trailing whitespace in red, so it’s easily visible (disabled for now as it created a lot of noise in some modes, e.g. the org-mode export screen)
(
customize-set-variable
'show-trailing-whitespace
nil
)
- Highlight matching parenthesis
(
show-paren-mode
)
- Don’t use hard tabs
(
customize-set-variable
'indent-tabs-mode
nil
)
- Emacs automatically creates backup files, by default in the same folder as the original file, which often leaves backup files behind. This tells Emacs to put all backups in ~/.emacs.d/backups.
(
customize-set-variable
'backup-directory-alist
`
((
"."
.
,
(
concat
user-emacs-directory
"backups"
))))
-
WinnerMode makes it possible to cycle and undo window configuration changes (i.e. arrangement of panels, etc.)
(
when
(
fboundp
'winner-mode
)
(
winner-mode
))
- Add “unfill” commands to parallel the “fill” ones, bind
A-q
tounfill-paragraph
and rebindM-q
to theunfill-toggle
command, which fills/unfills paragraphs alternatively.(
use-package
unfill
:bind
(
"M-q"
.
unfill-toggle
)
(
"A-q"
.
unfill-paragraph
))
- Save the place of the cursor in each file, and restore it upon opening it again.
(
use-package
saveplace
:defer
nil
:config
(
save-place-mode
))
- Provide mode-specific “bookmarks” - press
M-i
and you will be presented with a list of elements to which you can navigate - they can be headers in org-mode, function names in emacs-lisp, etc.(
use-package
imenu-anywhere
:bind
(
"M-i"
.
helm-imenu-anywhere
))
- Smooth scrolling (line by line) instead of jumping by half-screens.
(
use-package
smooth-scrolling
:config
(
smooth-scrolling-mode
1
))
- Delete trailing whitespace before saving a file.
(
add-hook
'before-save-hook
'delete-trailing-whitespace
)
- Suppress “ad-handle-definition: .. redefined” warnings during Emacs startup.
(
customize-set-variable
'ad-redefinition-action
'accept
)
System-specific configuration
Some settings maybe OS-specific, and this is where we set them. For now I only use Emacs on my Mac, so only the Mac section is filled out, but there are sections for Linux and Windows as well.
(
cond
((
eq
system-type
'darwin
)
<<Mac
settings>>
)
((
eq
system-type
'windows-nt
)
<<Windows
settings>>
)
((
eq
system-type
'gnu/linux
)
<<Linux
settings>>
))
Mac
First, we set the key modifiers correctly to my preferences: Make Command (⌘) act as Meta, Option as Alt, right-Option as Super
(
customize-set-variable
'mac-command-modifier
'meta
)
(
customize-set-variable
'mac-option-modifier
'alt
)
(
customize-set-variable
'mac-right-option-modifier
'super
)
We also make it possible to use the familiar ⌘-+
and ⌘--
to increase and decrease the font size. ⌘-=
is also bound to “increase” because it’s on the same key in an English keyboard.
(
bind-key
"M-+"
'text-scale-increase
)
(
bind-key
"M-="
'text-scale-increase
)
(
bind-key
"M--"
'text-scale-decrease
)
Somewhat surprisingly, there seems to be no “reset” function, so I define my own and bind it to ⌘-0
.
(
defun
zz/text-scale-reset
()
(
interactive
)
(
text-scale-set
0
))
(
bind-key
"M-0"
'zz/text-scale-reset
)
We also use the exec-path-from-shell
to make sure the path settings from the shell are loaded into Emacs (usually it starts up with the default system-wide path).
(
use-package
exec-path-from-shell
:defer
nil
:config
(
exec-path-from-shell-initialize
))
Linux
There are no Linux-specific settings for now.
Windows
There are no Windows-specific settings for now.
Keybindings
The which-key package makes Emacs functionality much easier to discover and explore: in short, after you start the input of a command and stop, pondering what key must follow, it will automatically open a non-intrusive buffer at the bottom of the screen offering you suggestions for completing the command. Extremely useful.
(
use-package
which-key
:defer
nil
:diminish
which-key-mode
:config
(
which-key-mode
))
I use the bind-key
package to more easily keep track and manage user keybindings. bind-key
comes with use-package
so we just load it.
The main advantage of using this over define-key
or global-set-key
is that you can use M-x
describe-personal-keybindings
to see a list of all the customized keybindings you have defined.
(
require
'bind-key
)
Miscellaneous keybindings
-
M-g
interactively asks for a line number and jump to it (goto-line)
.(
bind-key
"M-g"
'goto-line
)
-
M-`
focuses the next frame, if multiple ones are active (emulate the Mac “next app window” keybinding)(
bind-key
"M-`"
'other-frame
)
- Interactive search key bindings - visual-regexp-steroids provides sane regular expressions and visual incremental search. We make
C-s
andC-r
run the visual-regexp functions. We leaveC-M-s
andC-M-r
to run the defaultisearch-forward/backward
functions, as a fallback. I use thepcre2el
package to support PCRE-style regular expressions.(
use-package
pcre2el
)
(
use-package
visual-regexp-steroids
:custom
(
vr/engine
'pcre2el
"Use PCRE regular expressions"
)
:bind
(
"C-c r"
.
vr/replace
)
(
"C-c q"
.
vr/query-replace
)
(
"C-r"
.
vr/isearch-backward
)
(
"C-S-s"
.
vr/isearch-forward
)
(
"C-M-s"
.
isearch-forward
)
(
"C-M-r"
.
isearch-backward
))
- Key binding to use “hippie expand” for text autocompletion
(
bind-key
"M-/"
'hippie-expand
)
Emulating vi’s %
key
One of the few things I missed in Emacs from vi was the %
key, which jumps to the parenthesis, bracket or brace which matches the one below the cursor. This function implements the functionality. Inspired by http://www.emacswiki.org/emacs/NavigatingParentheses, but modified to use smartparens
instead of the default commands, and to work on brackets and braces.
(
defun
zz/goto-match-paren
(
arg
)
"Go to the matching paren/bracket, otherwise (or if ARG is not
nil) insert %. vi style of % jumping to matching brace."
(
interactive
"p"
)
(
if
(
not
(
memq
last-command
'
(
set-mark
cua-set-mark
zz/goto-match-paren
down-list
up-list
end-of-defun
beginning-of-defun
backward-sexp
forward-sexp
backward-up-list
forward-paragraph
backward-paragraph
end-of-buffer
beginning-of-buffer
backward-word
forward-word
mwheel-scroll
backward-word
forward-word
mouse-start-secondary
mouse-yank-secondary
mouse-secondary-save-then-kill
move-end-of-line
move-beginning-of-line
backward-char
forward-char
scroll-up
scroll-down
scroll-left
scroll-right
mouse-set-point
next-buffer
previous-buffer
previous-line
next-line
back-to-indentation
)))
(
self-insert-command
(
or
arg
1
))
(
cond
((
looking-at
"\\s\("
)
(
sp-forward-sexp
)
(
backward-char
1
))
((
looking-at
"\\s\)"
)
(
forward-char
1
)
(
sp-backward-sexp
))
(
t
(
self-insert-command
(
or
arg
1
))))))
We bind this function to the %
key.
(
bind-key
"%"
'zz/goto-match-paren
)
Org mode
I have started using org-mode to writing, blogging, coding, presentations and more, thanks to the hearty recommendations and information from Nick and many others. I am duly impressed. I have been a fan of the idea of literate programming for many years, and I have tried other tools before (most notably noweb, which I used during grad school for many of my homeworks and projects), but org-mode is the first tool I have encountered which seems to make it practical. Here are some of the resources I have found useful in learning it:
- Howard Abrams’ Introduction to Literate Programming, which got me jumpstarted into writing code documented with org-mode.
- Nick Anderson’s Level up your notes with Org, which contains many useful tips and configuration tricks.
- Sacha Chua’s Some tips for learning Org Mode for Emacs, her Emacs configuration and many of her other articles.
- Rainer König’s OrgMode Tutorial video series.
This is the newest and most-in-flux section of my Emacs config, since I’m still learning org-mode myself.
I use use-package
to load the org
package, and put its configuration inside the corresponding sections for keybindings (:bind
), custom variables (:custom
), custom faces (:custom-face
), hooks (:hook
) and general configuration code (:config
), respectively. The contents of each section is populated with the corresponding snippets that follow. See the sections below for the details on what goes into each configuration section, and some other configuration code that ends up outside this declaration.
(
use-package
org
:pin
manual
:load-path
(
"lisp/org-mode/lisp"
"lisp/org-mode/lisp/contrib/lisp"
)
:bind
<<org-mode-keybindings>>
:custom
<<org-mode-custom-vars>>
:custom-face
<<org-mode-faces>>
:hook
<<org-mode-hooks>>
:config
<<org-mode-config>>
)
General Org Configuration
Note that mode-specific configuration variables are defined under their corresponding packages, this section defines only global org-mode configuration variables, which are inserted in the main use-package
declaration for org-mode
.
- Default directory for org files (not all are stored here).
«org-mode-custom-vars»≡ (
org-directory
"~/Dropbox/Personal/org"
)
- Automatically log done times in todo items.
«org-mode-custom-vars»≡ (
org-log-done
t
)
- Keep the indentation well structured by setting
org-startup-indented
tot
. This is a must have. Makes it feel less like editing a big text file and more like a purpose built editor for org-mode that forces the indentation. Thanks Nick for the tip!«org-mode-custom-vars»≡ (
org-startup-indented
t
)
By default,
org-indent
produces an indicator"Ind"
in the modeline. We use diminish to hide it. I also like to increase the indentation a bit so that the levels are more visible.(
use-package
org-indent
:ensure
nil
:diminish
:custom
(
org-indent-indentation-per-level
4
))
- Log stuff into the LOGBOOK drawer by default
«org-mode-custom-vars»≡ (
org-log-into-drawer
t
)
General Org Keybindings
Note that other keybindings are configured under their corresponding packages, this section defines only global org-mode keybindings, which are inserted in the main use-package
declaration for org-mode
.
- Set up
C-c l
to store a link to the current org object, in counterpart to the defaultC-c C-l
to insert a link.«org-mode-keybindings»≡ (
"C-c l"
.
org-store-link
)
- The default keybinding for
org-mark-element
isM-h
, which in macOS hides the current application, so I bind it toA-h
.«org-mode-keybindings»≡ (
"A-h"
.
org-mark-element
)
Enable Speed Keys, which allows quick single-key commands when the cursor is placed on a heading. Usually the cursor needs to be at the beginning of a headline line, but defining it with this function makes them active on any of the asterisks at the beginning of the line (useful with the font highlighting I use, as all but the last asterisk are sometimes not visible).
(
org-use-speed-commands
(
lambda
()
(
and
(
looking-at
org-outline-regexp
)
(
looking-back
"^\**"
))))
Task tracking
Org-Agenda is the umbrella for all todo, journal, calendar, and other views. I set up C-c a
to call up agenda mode.
(
use-package
org-agenda
:ensure
nil
:after
org
:bind
(
"C-c a"
.
org-agenda
)
:custom
(
org-agenda-include-diary
t
)
(
org-agenda-prefix-format
'
((
agenda
.
" %i %-12:c%?-12t% s"
)
;; Indent todo items by level to show nesting
(
todo
.
" %i %-12:c%l"
)
(
tags
.
" %i %-12:c"
)
(
search
.
" %i %-12:c"
)))
(
org-agenda-start-on-weekday
nil
))
I also provide some customization for the holidays
package, since its entries are included in the Org Agenda through the org-agenda-include-diary
integration.
(
use-package
mexican-holidays
:defer
nil
)
(
quelpa
'
(
swiss-holidays
:fetcher
github
:repo
"egli/swiss-holidays"
))
(
require
'swiss-holidays
)
(
use-package
holidays
:defer
nil
:ensure
nil
:init
(
require
'mexican-holidays
)
:config
(
setq
calendar-holidays
(
append
'
((
holiday-fixed
1
1
"New Year's Day"
)
(
holiday-fixed
2
14
"Valentine's Day"
)
(
holiday-fixed
4
1
"April Fools' Day"
)
(
holiday-fixed
10
31
"Halloween"
)
(
holiday-easter-etc
)
(
holiday-fixed
12
25
"Christmas"
)
(
solar-equinoxes-solstices
))
swiss-holidays
swiss-holidays-catholic
swiss-holidays-zh-city-holidays
holiday-mexican-holidays
)))
org-super-agenda
provides great grouping and customization features to make agenda mode easier to use.
(
require
'org-habit
)
(
use-package
org-super-agenda
:custom
(
org-super-agenda-groups
'
((
:auto-dir-name
t
))))
I configure org-archive
to archive completed TODOs by default to the archive.org
file in the same directory as the source file, under the “date tree” corresponding to the task’s CLOSED date - this allows me to easily separate work from non-work stuff. Note that this can be overridden for specific files by specifying the desired value of org-archive-location
in the #+archive:
property at the top of the file.
(
use-package
org-archive
:ensure
nil
:custom
(
org-archive-location
"archive.org::datetree/"
))
Capturing stuff
First, I define some global keybindings to open my frequently-used org files (original tip from Learn how to take notes more efficiently in Org Mode).
I define a helper function to define keybindings that open files. Since I use the which-key
package, it also defines the description of the key that will appear in the which-key
menu. Note the use of lexical-let
so that the lambda
creates a closure, otherwise the keybindings don’t work.
(
defun
zz/add-file-keybinding
(
key
file
&optional
desc
)
(
lexical-let
((
key
key
)
(
file
file
)
(
desc
desc
))
(
global-set-key
(
kbd
key
)
(
lambda
()
(
interactive
)
(
find-file
file
)))
(
which-key-add-key-based-replacements
key
(
or
desc
file
))))
Now I define keybindings to access my commonly-used org files.
(
zz/add-file-keybinding
"C-c f w"
"~/Work/work.org.gpg"
"work.org"
)
(
zz/add-file-keybinding
"C-c f p"
"~/org/projects.org"
"projects.org"
)
(
zz/add-file-keybinding
"C-c f i"
"~/org/ideas.org"
"ideas.org"
)
(
zz/add-file-keybinding
"C-c f d"
"~/org/diary.org"
"diary.org"
)
org-capture
provides a generic and extensible interface to capturing things into org-mode in different formats. I set up C-c c
as the default keybinding for triggering org-capture
. Usually setting up a new capture template requires some custom code, which gets defined in the corresponding package config sections and included in the :config
section below.
(
use-package
org-capture
:ensure
nil
:after
org
:defer
1
:bind
(
"C-c c"
.
org-capture
)
:config
<<org-capture-config>>
)
Building presentations
org-reveal is an awesome package for building presentations with org-mode. The MELPA version of the package gives me a conflict with my hand-installed version of org-mode, so I also install it by hand and load it directly from its checked-out repository.
(
use-package
ox-reveal
:load-path
(
"lisp/org-reveal"
)
:defer
3
:after
org
:custom
(
org-reveal-note-key-char
nil
)
(
org-reveal-root
"file:///Users/taazadi1/.emacs.d/lisp/reveal.js"
))
(
use-package
htmlize
:defer
3
:after
ox-reveal
)
Various exporters
One of the big strengths of org-mode is the ability to export a document in many different formats. Here I load some of the exporters I have found useful.
- HTML
(
use-package
ox-html
:ensure
nil
:defer
3
:after
org
:custom
(
org-html-checkbox-type
'unicode
))
- Markdown
(
use-package
ox-md
:ensure
nil
:defer
3
:after
org
)
-
GitHub Flavored Markdown
(
use-package
ox-gfm
:defer
3
:after
org
)
-
Jira markup. I also load
org-jira
, which provides a full interface to Jira through org-mode.(
use-package
ox-jira
:defer
3
:after
org
)
(
use-package
org-jira
:defer
3
:after
org
:custom
(
jiralib-url
"https://jira.work.com"
))
- Confluence markup.
(
use-package
ox-confluence
:defer
3
:ensure
nil
:after
org
)
- AsciiDoc
(
use-package
ox-asciidoc
:defer
3
:after
org
)
- TexInfo. I have found that the best way to produce a PDF from an org file is to export it to a
.texi
file, and then usetexi2pdf
to produce the PDF.(
use-package
ox-texinfo
:load-path
"lisp/org-mode/lisp"
:defer
3
:ensure
nil
:after
org
)
- Some customizations for the LaTeX exporter.
ox-latex
gets loaded automatically, but we useuse-package
anyway so that the config code is only executed after the package is loaded. I add a pseudo-class which uses the document classbook
but without parts (only chapters at the top level).(
use-package
ox-latex
:load-path
"lisp/org-mode/lisp"
:ensure
nil
:demand
:after
org
:custom
(
org-latex-compiler
"xelatex"
)
(
org-latex-pdf-process
'
(
"%latex -shell-escape -interaction nonstopmode -output-directory %o %f"
"%latex -interaction nonstopmode -output-directory %o %f"
"%latex -interaction nonstopmode -output-directory %o %f"
))
:config
(
setq
org-latex-listings
'minted
)
(
add-to-list
'org-latex-packages-alist
'
(
"newfloat"
"minted"
))
(
add-to-list
'org-latex-minted-langs
'
(
lua
"lua"
))
(
add-to-list
'org-latex-minted-langs
'
(
shell
"shell"
))
(
add-to-list
'org-latex-classes
'
(
"book-no-parts"
"\\documentclass[11pt,letterpaper]{book}"
(
"\\chapter{%s}"
.
"\\chapter*{%s}"
)
(
"\\section{%s}"
.
"\\section*{%s}"
)
(
"\\subsection{%s}"
.
"\\subsection*{%s}"
)
(
"\\subsubsection{%s}"
.
"\\subsubsection*{%s}"
)
(
"\\paragraph{%s}"
.
"\\paragraph*{%s}"
)))
(
add-to-list
'org-latex-classes
'
(
"awesome-cv"
"\\documentclass{awesome-cv}"
(
"\\cvsection{%s}"
.
"\\cvsection{%s}"
)
(
"\\cvsubsection{%s}"
.
"\\cvsubsection{%s}"
)
(
"\\subsection{%s}"
.
"\\subsection*{%s}"
)
(
"\\subsubsection{%s}"
.
"\\subsubsection*{%s}"
)
(
"\\cvparagraph{%s}"
.
"\\cvparagraph{%s}"
)))
;; Necessary for LuaLaTeX to work - see
;; https://tex.stackexchange.com/a/374391/10680
(
setenv
"LANG"
"en_US.UTF-8"
))
-
ox-clip to export HTML-formatted snippets.
(
use-package
ox-clip
:bind
(
"A-C-M-k"
.
ox-clip-formatted-copy
))
Blogging with Hugo
ox-hugo is an awesome way to blog from org-mode. It makes it possible for posts in org-mode format to be kept separate, and it generates the Markdown files for Hugo. Hugo supports org files, but using ox-hugo has multiple advantages:
- Parsing is done by org-mode natively, not by an external library. Although goorgeous (used by Hugo) is very good, it still lacks in many areas, which leads to text being interpreted differently as by org-mode.
- Hugo is left to parse a native Markdown file, which means that many of its features such as shortcodes, TOC generation, etc., can still be used on the generated file.
- I am intrigued by ox-hugo’s “one post per org subtree” proposed structure. So far I’ve always had one file per post, but with org-mode’s structuring features, it might make sense to give it a try.
(
use-package
ox-hugo
:defer
3
:after
org
;; Testing hooks to automatically set the filename on an ox-hugo
;; blog entry when it gets marked as DONE
;; :hook
;; (org-mode . (lambda ()
;; (add-hook 'org-after-todo-state-change-hook
;; (lambda ()
;; (org-set-property
;; "testprop"
;; (concat "org-state: " org-state
;; " prev-state: " (org-get-todo-state))))
;; 'run-at-end 'only-in-org-mode)))
)
Configure a capture template for creating new ox-hugo blog posts, from ox-hugo’s Org Capture Setup.
(
defun
org-hugo-new-subtree-post-capture-template
()
"Returns
`org-capture'
template string for new Hugo post.
See
`org-capture-templates'
for more information."
(
let*
((
title
(
read-from-minibuffer
"Post Title: "
))
(
fname
(
org-hugo-slug
title
)))
(
mapconcat
#'identity
`
(
,
(
concat
"* TODO "
title
)
":PROPERTIES:"
,
(
concat
":EXPORT_HUGO_BUNDLE: "
fname
)
":EXPORT_FILE_NAME: index"
":END:"
"%?\n"
)
; Place the cursor here finally
"\n"
)))
(
add-to-list
'org-capture-templates
'
(
"z"
;`org-capture' binding + z
"zzamboni.org post"
entry
;; It is assumed that below file is present in `org-directory'
;; and that it has an "Ideas" heading. It can even be a
;; symlink pointing to the actual location of all-posts.org!
(
file+olp
"zzamboni.org"
"Ideas"
)
(
function
org-hugo-new-subtree-post-capture-template
)))
Encryption
First, load the built-in EasyPG support. By calling (epa-file-enable)
, Emacs automatically encrypts/decrypts files with a .gpg
extension. By default it asks about the key to use, but I configure it to always use my own GPG key.
(
use-package
epa-file
:ensure
nil
;; included with Emacs
:config
(
setq
epa-file-encrypt-to
'
(
"diego@zzamboni.org"
))
(
epa-file-enable
)
:custom
(
epa-file-select-keys
'silent
))
Then, load org-crypt to enable selective encryption/decryption using GPG within org-mode.
(
use-package
org-crypt
:ensure
nil
;; included with org-mode
:after
org
:config
(
org-crypt-use-before-save-magic
)
(
setq
org-tags-exclude-from-inheritance
(
quote
(
"crypt"
)))
:custom
(
org-crypt-key
"diego@zzamboni.org"
))
Keeping a Journal
I use 750words for my personal Journal, and I used to write my entries locally using Scrivener. Now I am using org-journal
for this, works quite well together with wc-mode
to keep a count of how many words I have written.
In order to keep my journal entries encrypted there are two separate but confusingly named mechanisms:
-
org-journal-encrypt-journal
, if set tot
has the effect of transparently encrypting/decrypting the journal files as they are written to disk. This is what I use. -
org-journal-enable-encryption
, if set tot
, enables integration withorg-crypt
(see above), so it automatically adds a:crypt:
tag to new journal entries. This has the effect of automatically encrypting those entries upon save, replacing them with a blob of gpg-encrypted text which has to be further decrypted withorg-decrypt-entry
in order to read or edit them again. I have disabled it for now to make it more transparent to work with my journal entries while I am editing them.
(
use-package
org-journal
:after
org
:custom
(
org-journal-dir
(
concat
(
file-name-as-directory
org-directory
)
"journal"
))
(
org-journal-file-format
"%Y/%m/%Y%m%d"
)
(
org-journal-date-format
"%A, %Y-%m-%d"
)
(
org-journal-encrypt-journal
t
)
(
org-journal-enable-encryption
nil
)
(
org-journal-enable-agenda-integration
t
)
:bind
(
"C-c j"
.
org-journal-new-entry
))
Literate programming
Org-mode is the first literate programming tool that seems practical and useful, since it’s easy to edit, execute and document code from within the same tool (Emacs) using all of its existing capabilities (i.e. each code block can be edited in its native Emacs mode, taking full advantage of indentation, completion, etc.)
First, we load the necessary programming language support. The base features and literate programming for Emacs LISP is built-in, but the ob-*
packages provide the ability to execute code in different languages directly from within the Org buffer, beyond those included with org-mode. I load the modules for some of the languages I use frequently:
- CFEngine, used extensively for my book Learning CFEngine.
(
use-package
ob-cfengine3
:after
org
)
- Elvish, my favorite shell.
(
use-package
ob-elvish
:after
org
)
- The PlantUML graph language.
We determine the location of the PlantUML jar file automatically from the installed Homebrew formula.
«plantuml-jar-command»≡ brew list plantuml
|
grep jar
Which in my current setup results in the following:
/usr/local/Cellar/plantuml/1.2019.12/libexec/plantuml.jar
The command defined above is used to define the value of the homebrew-plantuml-jar-path
variable. If you don’t use Homebrew of have installed PlantUML some other way, you need to modify this command, or hard-code the path.
(
require
'subr-x
)
(
setq
homebrew-plantuml-jar-path
(
expand-file-name
(
string-trim
(
shell-command-to-string
"<<plantuml-jar-command>>"
))))
Finally, we use this value to configure both plantuml-mode
(for syntax highlighting) and ob-plantuml
(for evaluating PlantUML code and inserting the results in exported Org documents).
(
use-package
plantuml-mode
:custom
(
plantuml-jar-path
homebrew-plantuml-jar-path
))
(
use-package
ob-plantuml
:ensure
nil
:after
org
:custom
(
org-plantuml-jar-path
homebrew-plantuml-jar-path
))
- Define
shell-script-mode
as an alias forconsole-mode
, so thatconsole
src blocks can be edited and are fontified correctly.(
defalias
'console-mode
'shell-script-mode
)
- Finally, from all the available languages, we configure the ones for which to load
org-babel
support.«org-mode-config»≡ (
org-babel-do-load-languages
'org-babel-load-languages
'
((
cfengine3
.
t
)
(
ruby
.
t
)
(
latex
.
t
)
(
plantuml
.
t
)
(
python
.
t
)
(
shell
.
t
)
(
elvish
.
t
)
(
calc
.
t
)
(
dot
.
t
)
(
ditaa
.
t
)))
Now, we configure some other org-babel
settings:
- This little snippet has revolutionized my literate programming workflow. It automatically runs
org-babel-tangle
upon saving any org-mode buffer, which means the resulting files will be automatically kept up to date.«org-mode-hooks»≡ (
org-mode
.
(
lambda
()
(
add-hook
'after-save-hook
'org-babel-tangle
'run-at-end
'only-in-org-mode
)))
- This is potentially dangerous: it suppresses the query before executing code from within org-mode. I use it because I am very careful and only press
C-c C-c
on blocks I absolutely understand.«org-mode-custom-vars»≡ (
org-confirm-babel-evaluate
nil
)
- This makes it so that code within
src
blocks is fontified according to their corresponding Emacs mode, making the file much more readable.«org-mode-custom-vars»≡ (
org-src-fontify-natively
t
)
- In principle this makes it so that indentation in
src
blocks works as in their native mode, but in my experience it does not always work reliably. For full proper indentation, always edit the code in a native buffer by pressingC-c '
.«org-mode-custom-vars»≡ (
org-src-tab-acts-natively
t
)
- Automatically show inline images, useful when executing code that produces them, such as PlantUML or Graphviz.
«org-mode-hooks»≡ (
org-babel-after-execute
.
org-redisplay-inline-images
)
- I add hooks to measure and report how long the tangling took. I first define a function to compute and report the elapsed time:
«org-mode-config»≡ (
defun
zz/report-tangle-time
(
start-time
)
(
message
"org-babel-tangle took %s"
(
format
"%.2f seconds"
(
float-time
(
time-since
start-time
)))))
And this function is used in the corresponding hooks before and after
org-babel-tangle
:«org-mode-hooks»≡ (
org-babel-pre-tangle
.
(
lambda
()
(
setq
zz/pre-tangle-time
(
current-time
))))
(
org-babel-post-tangle
.
(
lambda
()
(
zz/report-tangle-time
zz/pre-tangle-time
)))
Beautifying org-mode
These settings make org-mode much more readable by using different fonts for headings, hiding some of the markup, etc. This was taken originally from Howard Abrams’ Org as a Word Processor, and subsequently tweaked and broken up in the different parts of the use-package
declaration by me.
First, we set org-hid-emphasis-markers
so that the markup indicators are not shown.
(
org-hide-emphasis-markers
t
)
We add an entry to the org-mode font-lock table so that list markers are shown with a middle dot instead of the original character.
(
font-lock-add-keywords
'org-mode
'
((
"^ *\\([-]\\) "
(
0
(
prog1
()
(
compose-region
(
match-beginning
1
)
(
match-end
1
)
"•"
))))))
We use the org-bullets
package to display the titles with nice unicode bullets instead of the text ones.
(
use-package
org-bullets
:after
org
:hook
(
org-mode
.
(
lambda
()
(
org-bullets-mode
1
))))
We choose a nice font for the document title and the section headings. The first one found in the system from the list below is used, and the same font is used for the different levels, in varying sizes.
(
let*
((
variable-tuple
(
cond
((
x-list-fonts
"Source Sans Pro"
)
'
(
:font
"Source Sans Pro"
))
((
x-list-fonts
"Lucida Grande"
)
'
(
:font
"Lucida Grande"
))
((
x-list-fonts
"Verdana"
)
'
(
:font
"Verdana"
))
((
x-family-fonts
"Sans Serif"
)
'
(
:family
"Sans Serif"
))
(
nil
(
warn
"Cannot find a Sans Serif Font."
))))
(
base-font-color
(
face-foreground
'default
nil
'default
))
(
headline
`
(
:inherit
default
:weight
bold
:foreground
,
base-font-color
)))
(
custom-theme-set-faces
'user
`
(
org-level-8
((
t
(
,@
headline
,@
variable-tuple
))))
`
(
org-level-7
((
t
(
,@
headline
,@
variable-tuple
))))
`
(
org-level-6
((
t
(
,@
headline
,@
variable-tuple
))))
`
(
org-level-5
((
t
(
,@
headline
,@
variable-tuple
))))
`
(
org-level-4
((
t
(
,@
headline
,@
variable-tuple
:height
1.1
))))
`
(
org-level-3
((
t
(
,@
headline
,@
variable-tuple
:height
1.25
))))
`
(
org-level-2
((
t
(
,@
headline
,@
variable-tuple
:height
1.5
))))
`
(
org-level-1
((
t
(
,@
headline
,@
variable-tuple
:height
1.75
))))
`
(
org-headline-done
((
t
(
,@
headline
,@
variable-tuple
:strike-through
t
))))
`
(
org-document-title
((
t
(
,@
headline
,@
variable-tuple
:height
2.0
:underline
nil
))))))
I use proportional fonts in org-mode for the text, while keeping fixed-width fonts for blocks, so that source code, tables, etc. are shown correctly. These settings include:
- Setting up the
variable-pitch
face to the proportional font I like to use. I’m currently alternating between my two favorites, Source Sans Pro and Avenir Next.«org-mode-faces»≡ (
variable-pitch
((
t
(
:family
"Source Sans Pro"
:height
160
:weight
light
))))
;;(variable-pitch ((t (:family "Avenir Next" :height 160 :weight light))))
- Setting up the
fixed-pitch
face to be the same as my usualdefault
face. My current one is Inconsolata.«org-mode-faces»≡ (
fixed-pitch
((
t
(
:family
"Inconsolata"
))))
- Configure
org-indent
to inherit fromfixed-pitch
to fix the vertical spacing in code blocks. Thanks to Ben for the tip!«org-mode-faces»≡ (
org-indent
((
t
(
:inherit
(
org-hide
fixed-pitch
)))))
- Configure
org-fontify-done-headline
to apply a special face to DONE items in org-mode, and configure theorg-done
face to be used. Note thatorg-done
only applies to the “DONE” keyword itself, the face for the rest of a “done” headline is defined above as theorg-headline-done
face.«org-mode-custom-vars»≡ (
org-fontify-done-headline
t
)
«org-mode-faces»≡ (
org-done
((
t
(
:foreground
"PaleGreen"
:strike-through
t
))))
- Configuring the corresponding
org-mode
faces for blocks, verbatim code, and maybe a couple of other things. As these change more frequently, I do them directly from thecustomize-face
interface, you can see their current settings in the Customized variables section. - Setting up
visual-line-mode
and making all my paragraphs one single line, so that the lines wrap around nicely in the window according to their proportional-font size, instead of at a fixed character count, which does not work so nicely when characters have varying widths. I set up a hook that automatically enablesvisual-line-mode
andvariable-pitch-mode
when entering org-mode.«org-mode-hooks»≡ (
org-mode
.
visual-line-mode
)
(
org-mode
.
variable-pitch-mode
)
- In
variable-pitch
mode, the default right-alignment for headline tags doesn’t work, and results in the tags being misaligned (as it uses character positions to do the alignment). This setting positions the tags right after the last character of the headline, so at least they are more consistent.«org-mode-custom-vars»≡ (
org-tags-column
0
)
- I also set
org-todo-keyword-faces
to highlight different types of org-mode TODO items with different colors.«org-mode-custom-vars»≡ (
org-todo-keyword-faces
'
((
"AREA"
.
"DarkOrchid1"
)
(
"[AREA]"
.
"DarkOrchid1"
)
(
"INBOX"
.
"cyan"
)
(
"[INBOX]"
.
"cyan"
)
(
"PROPOSAL"
.
"orange"
)
(
"[PROPOSAL]"
.
"orange"
)
(
"DRAFT"
.
"yellow"
)
(
"[DRAFT]"
.
"yellow"
)
(
"INPROGRESS"
.
"yellow"
)
(
"[INPROGRESS]"
.
"yellow"
)
(
"MEETING"
.
"purple"
)
(
"[MEETING]"
.
"purple"
)
(
"CANCELED"
.
"blue"
)
(
"[CANCELED]"
.
"blue"
)))
These two modes produce modeline indicators, which I disable using
diminish
.«org-mode-config»≡ (
eval-after-load
'face-remap
'
(
diminish
'buffer-face-mode
))
(
eval-after-load
'simple
'
(
diminish
'visual-line-mode
))
- Prettify checkbox lists - courtesy of https://blog.jft.rocks/emacs/unicode-for-orgmode-checkboxes.html. First, we add special characters for checkboxes:
«org-mode-hooks»≡ (
org-mode
.
(
lambda
()
"Beautify Org Checkbox Symbol"
(
push
'
(
"[ ]"
.
"☐"
)
prettify-symbols-alist
)
(
push
'
(
"[X]"
.
"☑"
)
prettify-symbols-alist
)
(
push
'
(
"[-]"
.
"⊡"
)
prettify-symbols-alist
)
(
prettify-symbols-mode
)))
Second, we define a special face for checked items.
«org-mode-config»≡ (
defface
org-checkbox-done-text
'
((
t
(
:foreground
"#71696A"
:strike-through
t
)))
"Face for the text part of a checked org-mode checkbox."
)
(
font-lock-add-keywords
'org-mode
`
((
"^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\\
][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
1
'org-checkbox-done-text
prepend
))
'append
)
Auto-generated table of contents
The toc-org
package allows us to insert a table of contents in headings marked with :TOC:
. This is useful for org files that are to be viewed directly on GitHub, which renders org files correctly, but does not generate a table of contents at the top. For an example, see this file on GitHub.
Note that this breaks HTML export by default, as the links generated by toc-org
cannot be parsed properly by the html exporter. The workaround is to use :TOC:noexport:
as the marker, which removed the generated TOC from the export, but still allows ox-html
to insert its own TOC at the top.
(
use-package
toc-org
:after
org
:hook
(
org-mode
.
toc-org-enable
))
Grabbing links from different Mac applications
org-mac-link
(included in contrib) implements the ability to grab links from different Mac apps and insert them in the file. Bind C-c g
to call org-mac-grab-link
to choose an application and insert a link.
(
use-package
org-mac-link
:ensure
nil
:load-path
"lisp/org-mode/contrib/lisp"
:after
org
:bind
(
:map
org-mode-map
(
"C-c g"
.
org-mac-grab-link
)))
Reformatting an Org buffer
I picked up this little gem in the org mailing list. A function that reformats the current buffer by regenerating the text from its internal parsed representation. Quite amazing.
(
defun
zz/org-reformat-buffer
()
(
interactive
)
(
when
(
y-or-n-p
"Really format current buffer? "
)
(
let
((
document
(
org-element-interpret-data
(
org-element-parse-buffer
))))
(
erase-buffer
)
(
insert
document
)
(
goto-char
(
point-min
)))))
Remove a link. For some reason this is not part of org-mode. From https://emacs.stackexchange.com/a/10714/11843, I bind it to C-c
C-M-u
.
(
defun
afs/org-remove-link
()
"Replace an org link by its description or if empty its address"
(
interactive
)
(
if
(
org-in-regexp
org-bracket-link-regexp
1
)
(
let
((
remove
(
list
(
match-beginning
0
)
(
match-end
0
)))
(
description
(
if
(
match-end
3
)
(
org-match-string-no-properties
3
)
(
org-match-string-no-properties
1
))))
(
apply
'delete-region
remove
)
(
insert
description
))))
(
bind-key
"C-c C-M-u"
'afs/org-remove-link
)
Code for org-mode macros
Here I define functions which get used in some of my org-mode macros
The first is a support function which gets used in some of the following, to return a string (or an optional custom string) only if it is a non-zero, non-whitespace string, and nil
otherwise.
(
defun
zz/org-if-str
(
str
&optional
desc
)
(
when
(
org-string-nw-p
str
)
(
or
(
org-string-nw-p
desc
)
str
)))
This function receives three arguments, and returns the org-mode code for a link to the Hammerspoon API documentation for the link
module, optionally to a specific function
. If desc
is passed, it is used as the display text, otherwise section.function
is used.
(
defun
zz/org-macro-hsapi-code
(
module
&optional
func
desc
)
(
org-link-make-string
(
concat
"https://www.hammerspoon.org/docs/"
(
concat
module
(
zz/org-if-str
func
(
concat
"#"
func
))))
(
or
(
org-string-nw-p
desc
)
(
format
"=%s="
(
concat
module
(
zz/org-if-str
func
(
concat
"."
func
)))))))
Split STR at spaces and wrap each element with the ~
char, separated by +
. Zero-width spaces are inserted around the plus signs so that they get formatted correctly. Envisioned use is for formatting keybinding descriptions. There are two versions of this function: “outer” wraps each element in ~
, the “inner” wraps the whole sequence in them.
(
defun
zz/org-macro-keys-code-outer
(
str
)
(
mapconcat
(
lambda
(
s
)
(
concat
"~"
s
"~"
))
(
split-string
str
)
(
concat
(
string
?\u
200B
)
"+"
(
string
?\u
200B
))))
(
defun
zz/org-macro-keys-code-inner
(
str
)
(
concat
"~"
(
mapconcat
(
lambda
(
s
)
(
concat
s
))
(
split-string
str
)
(
concat
(
string
?\u
200B
)
"-"
(
string
?\u
200B
)))
"~"
))
(
defun
zz/org-macro-keys-code
(
str
)
(
zz/org-macro-keys-code-inner
str
))
Links to a specific section/function of the Lua manual.
(
defun
zz/org-macro-luadoc-code
(
func
&optional
section
desc
)
(
org-link-make-string
(
concat
"https://www.lua.org/manual/5.3/manual.html#"
(
zz/org-if-str
func
section
))
(
zz/org-if-str
func
desc
)))
(
defun
zz/org-macro-luafun-code
(
func
&optional
desc
)
(
org-link-make-string
(
concat
"https://www.lua.org/manual/5.3/manual.html#"
(
concat
"pdf-"
func
))
(
zz/org-if-str
(
concat
"="
func
"()="
)
desc
)))
Publishing project configuration
Define a publishing function based on org-latex-publish-to-pdf
but which opens the resulting file at the end.
(
defun
org-latex-publish-to-latex-and-open
(
plist
file
pub-dir
)
(
org-open-file
(
org-latex-publish-to-pdf
plist
file
pub-dir
)))
Sample project configuration - disabled for now because this configuration has been incorporated into the structure.tex
file and in the general ox-latex
configuration, but kept here as a sample.
(
org-publish-project-alist
'
((
"mac-automation"
:base-directory
"~/Personal/writing/mac-automation/"
:publishing-directory
"~/Personal/writing/mac-automation/build/"
:base-extension
"org"
:publishing-function
org-latex-publish-to-latex-and-open
:latex-compiler
"xelatex"
:latex-classes
'
(
"book-no-parts"
"\\documentclass[11pt]{book}"
(
"\\chapter{%s}"
.
"\\chapter*{%s}"
)
(
"\\section{%s}"
.
"\\section*{%s}"
)
(
"\\subsection{%s}"
.
"\\subsection*{%s}"
)
(
"\\subsubsection{%s}"
.
"\\subsubsection*{%s}"
)
(
"\\paragraph{%s}"
.
"\\paragraph*{%s}"
))
:latex-class
"book-no-parts"
:latex-title-command
"\\makeatletter\\begingroup
\\thispagestyle{empty}
\\begin{tikzpicture}[remember picture,overlay]
\\node[inner sep=0pt] (background) at (current page.center) {\\includegraphic\
s[width=\\paperwidth]{background}};
\\draw (current page.center) node [fill=ocre!30!white,fill opacity=0.6,text o\
pacity=1,inner sep=1cm]{\\Huge\\centering\\bfseries\\sffamily\\parbox[c][][t]{\\
\paperwidth}{\\centering \\@title \\\\[15pt]
{\\Large \\@subtitle }\\\\[20pt]
{\\huge \\@author }}};
\\end{tikzpicture}
\\vfill
\\endgroup\\makeatother
\\chapterimage{chapter_head_1.pdf}"
:latex-toc-command
"\\pagestyle{empty}
\\tableofcontents
\\cleardoublepage
\\pagestyle{fancy}"
)))
Publishing to LeanPub
I use LeanPub for self-publishing my books Learning Hammerspoon and Learning CFEngine. Fortunately, it is possible to export from org-mode to LeanPub-flavored Markdown.
Some references:
- Description of ox-leanpub.el (GitHub repo) by Juan Reyero
- Publishing a book using org-mode by Lakshmi Narasimhan
- Writing a book with emacs org-mode and Leanpub by Angelo Basile (the link goes to an archive copy of the post, as it is not live on his website anymore)
- Publishing a Book with Leanpub and Org Mode by Jon Snader (from where I found the links to the above)
First, load ox-leanpub-markdown
. This is based on Juan’s ox-leanpub
, but with many changes of my own, including a rename. You can get it from my fork at https://github.com/zzamboni/ox-leanpub/tree/book-and-markua.
(
use-package
ox-leanpub-markdown
:defer
1
:ensure
nil
:after
org
:load-path
"lisp/ox-leanpub"
)
(
use-package
ox-leanpub-markua
:defer
1
:ensure
nil
:after
org
:load-path
"lisp/ox-leanpub"
)
Next, load my ox-leanpub-book
module (also available at https://github.com/zzamboni/ox-leanpub/tree/book-and-markua). It defines a new export backend called leanpub-book
, which adds three additional items in the LeanPub export section:
- “Multifile: Whole book”, which exports the whole book as one-file-per-chapter;
- “Multifile: Subset”, which exports only the chapters that should be included in
Subset.txt
(if any), according to the rules listed below. I use this together with#+LEANPUB_WRITE_SUBSET: current
in my files to quickly export only the current chapter, to be able to quickly preview it using LeanPub’s subset-preview feature; - “Multifile: Current chapter” to explicitly export only the current chapter to its own file. This also updates
Subset.txt
, so it can be used to preview the current chapter without having to set#+LEANPUB_WRITE_SUBSET: current
.
The book files are populated as follows:
-
Book.txt
with all chapters, except those tagged withnoexport
. -
Sample.txt
with all chapters tagged withsample
. -
Subset.txt
with chapters depending on the value of the#+LEANPUB_WRITE_SUBSET
file property (if set):- Default or
none
: not created. -
tagged
: use all chapters taggedsubset
. -
all
: use the same chapters asBook.txt
. -
sample
: use same chapters asSample.txt
. -
current
: export the current chapter (where the cursor is at the moment of the export) as the contents ofSubset.txt
.
- Default or
If a heading has the frontmatter
, mainmatter
or backmatter
tags, the corresponding markup is inserted in the output, before the headline. This way, you only need to tag the first chapter of the front, main, and backmatter, respectively.
Note that the org-leanpub-book-setup-menu-markdown
function gets called in the :config
section. This is because I am working on ox-markua
to export Leanpub’s new Markua format, and I plan for ox-leanpub-book
to also support it.
(
use-package
ox-leanpub-book
:defer
1
:ensure
nil
:after
ox-leanpub-markdown
:load-path
"lisp/ox-leanpub"
:config
(
progn
(
org-leanpub-book-setup-menu-markdown
)
(
org-leanpub-book-setup-menu-markua
)))
Miscellaneous org functions and configuration
Utility org-get-keyword
function (from the org-mode mailing list) to get the value of file-level properties.
(
defun
org-get-keyword
(
key
)
(
org-element-map
(
org-element-parse-buffer
'element
)
'keyword
(
lambda
(
k
)
(
when
(
string=
key
(
org-element-property
:key
k
))
(
org-element-property
:value
k
)))
nil
t
))
org-sidebar provides a configurable sidebar to org buffers, showing the agenda, headlines, etc.
(
use-package
org-sidebar
)
Appearance, buffer/file management and theming
Here we take care of all the visual, UX and desktop-management settings.
You’ll notice that many of the packages in this section have :defer nil
. This is because some of these package are never called explicitly because they operate in the background, but I want them loaded when Emacs starts so they can perform their necessary customization.
Emacs 26 (which I am trying now) introduces pixel-level scrolling.
(
when
(
>=
emacs-major-version
26
)
(
pixel-scroll-mode
))
The diminish
package makes it possible to remove clutter from the modeline. Here we just load it, it gets enabled for individual packages in their corresponding declarations.
(
use-package
diminish
:defer
1
)
I have been playing with different themes, and I have settled for now in gruvbox
. Some of my other favorites are also here so I don’t forget about them.
;;(use-package solarized-theme)
;;(use-package darktooth-theme)
;;(use-package kaolin-themes)
(
use-package
gruvbox-theme
)
(
load-theme
'gruvbox
)
Install smart-mode-line for modeline goodness, including configurable abbreviation of directories, and other things.
(
use-package
smart-mode-line
:defer
2
:config
(
sml/setup
)
:custom
(
sml/theme
'dark
)
(
sml/replacer-regexp-list
'
((
"^~/\\.emacs\\.d/elpa/"
":ELPA:"
)
(
"^~/\\.emacs\\.d/"
":ED:"
)
(
"^/sudo:.*:"
":SU:"
)
(
"^~/Documents/"
":Doc:"
)
(
"^:\\([^:]*\\):Documento?s/"
":\\1/Doc:"
)
(
"^~/Dropbox/"
":DB:"
)
(
"^:DB:org"
":Org:"
)
(
"^:DB:Personal/"
":P:"
)
(
"^:DB:Personal/writing/"
":Write:"
)
(
"^:P:devel/"
":Dev:"
)
(
"^:Write:learning-cfengine-3/learning-cfengine-3/"
":cf-learn:"
)
(
"^:Dev:go/src/github.com/elves/elvish/"
":elvish:"
)
(
"^:Dev:zzamboni.org/zzamboni.org/"
":zz.org:"
))))
Enable desktop-save mode, which saves the current buffer configuration on exit and reloads it on restart.
Desktop mode also includes the desktop-clear
function, which can be used to kill all open buffers. I bind it to Control-Meta-super-k
.
(
use-package
desktop
:defer
nil
:custom
(
desktop-restore-eager
1
"Restore the first buffer right away"
)
(
desktop-lazy-idle-delay
1
"Restore the other buffers 1 second later"
)
(
desktop-lazy-verbose
nil
"Be silent about lazily opening buffers"
)
:bind
(
"C-M-s-k"
.
desktop-clear
)
:config
(
desktop-save-mode
))
The uniquify
package makes it much easier to identify different open files with the same name by prepending/appending their directory or some other information to them. I configure it to add the directory name after the filename. uniquify
is included with Emacs, so I specify :ensure nil
so that use-package
doesn’t try to install it, and just loads and configures it.
(
use-package
uniquify
:defer
1
:ensure
nil
:custom
(
uniquify-after-kill-buffer-p
t
)
(
uniquify-buffer-name-style
'post-forward
)
(
uniquify-strip-common-suffix
t
))
I like to highlight the current line. For this I use the built-in hl-line
.
(
use-package
hl-line
:defer
nil
:config
<<hl-line
custom
line-range
function>>
(
global-hl-line-mode
))
I also provide a custom value for hl-line-range-function
(thanks to Eric on the org-mode mailing list for the tip) which highlights only the current visual line in visual-line-mode
, which I use for Org-mode files (see Beautifying org-mode).
(
defun
zz/get-visual-line-range
()
(
let
(
b
e
)
(
save-excursion
(
beginning-of-visual-line
)
(
setq
b
(
point
))
(
end-of-visual-line
)
(
setq
e
(
+
1
(
point
)))
)
(
cons
b
e
)))
(
setq
hl-line-range-function
#'
zz/get-visual-line-range
)
I have also experimented with highlighting the current column. At the moment the code below is all disabled because I find it too distracting, but I’m leaving it here for reference. I found two options to achieve this:
- The
col-highlight
package, which highlights the column only after a defined interval has passed - The
crosshairs
package, which always highlights both the column and the line. It also has a “highlight crosshairs when idle” mode, but I prefer to have the current line always highlighted.
(
use-package
col-highlight
:disabled
:defer
nil
:config
(
col-highlight-toggle-when-idle
)
(
col-highlight-set-interval
2
))
(
use-package
crosshairs
:disabled
:defer
nil
:config
(
crosshairs-mode
))
I also use recentf
to keep a list of recently open buffers. These are visible in helm’s open-file mode.
(
use-package
recentf
:defer
1
:custom
(
recentf-max-menu-items
100
)
(
recentf-max-saved-items
100
)
:init
(
recentf-mode
))
The ibuffer package allows all sort of useful operations on the list of open buffers. I haven’t customized it yet, but I have a keybinding to open it. (Disabled for now as I am using helm’s helm-buffer-list
).
(
use-package
ibuffer
:disabled
:bind
(
"C-x C-b"
.
ibuffer
))
The smex package is incredibly useful, adding IDO integration and some other very nice features to M-x
, which make it easier to discover and use Emacs commands. Highly recommended. (Disabled for now as I’m using helm’s helm-M-x
).
(
use-package
smex
:disabled
:bind
((
"M-x"
.
smex
))
:config
(
smex-initialize
))
midnight-mode purges buffers which haven’t been displayed in 3 days. We configure the period so that the cleanup happens every 2 hours (7200 seconds).
(
use-package
midnight
:defer
3
:config
(
setq
midnight-period
7200
)
(
midnight-mode
1
))
For distraction-free writing, I’m testing out writeroom-mode
.
(
use-package
writeroom-mode
)
NeoTree shows a navigation tree on a sidebar, and allows a number of operations on the files and directories. I’m not much of a fan of this type of interface in Emacs, but I have set it up to check it out.
(
use-package
neotree
:custom
(
neo-theme
(
if
(
display-graphic-p
)
'icons
'arrow
))
(
neo-smart-open
t
)
(
projectile-switch-project-action
'neotree-projectile-action
)
:config
(
defun
neotree-project-dir
()
"Open NeoTree using the git root."
(
interactive
)
(
let
((
project-dir
(
projectile-project-root
))
(
file-name
(
buffer-file-name
)))
(
neotree-toggle
)
(
if
project-dir
(
if
(
neo-global--window-exists-p
)
(
progn
(
neotree-dir
project-dir
)
(
neotree-find
file-name
)))
(
message
"Could not find git project root."
))))
:bind
([
f8
]
.
neotree-project-dir
))
wc-mode
allows counting characters and words, both on demand and continuously. It also allows setting up a word/character goal.
(
use-package
wc-mode
:defer
3
:hook
(
org-journal-mode
.
wc-mode
))
The all-the-icons
package provides a number of useful icons.
(
use-package
all-the-icons
:defer
3
)
Completion: IDO or Helm?
The battle rages on - helm or IDO? Both are nice completion frameworks for Emacs, and both integrate nicely with most main Emacs functions, including file opening, command and buffer selection, etc. I was using IDO for some time but are now giving helm a try. Both my configs are shown below, but only Helm is enabled at the moment.
Should I also look at ivy?
IDO
I use IDO mode to get better matching capabilities everywhere in Emacs (disabled while I give helm a try, see below).
(
use-package
ido
:disabled
:config
(
ido-mode
t
)
(
ido-everywhere
1
)
(
setq
ido-use-virtual-buffers
t
)
(
setq
ido-enable-flex-matching
t
)
(
setq
ido-use-filename-at-point
nil
)
(
setq
ido-auto-merge-work-directories-length
-1
))
(
use-package
ido-completing-read+
:disabled
:config
(
ido-ubiquitous-mode
1
))
Helm
This config came originally from Uncle Dave’s Emacs config, thought I have tweaked it a bit.
(
use-package
helm
:defer
1
:diminish
helm-mode
:bind
((
"C-x C-f"
.
helm-find-files
)
(
"C-x C-b"
.
helm-buffers-list
)
(
"C-x b"
.
helm-multi-files
)
(
"M-x"
.
helm-M-x
)
:map
helm-find-files-map
(
"C-<backspace>"
.
helm-find-files-up-one-level
)
(
"C-f"
.
helm-execute-persistent-action
)
([
tab
]
.
helm-ff-RET
))
:config
(
defun
daedreth/helm-hide-minibuffer
()
(
when
(
with-helm-buffer
helm-echo-input-in-header-line
)
(
let
((
ov
(
make-overlay
(
point-min
)
(
point-max
)
nil
nil
t
)))
(
overlay-put
ov
'window
(
selected-window
))
(
overlay-put
ov
'face
(
let
((
bg-color
(
face-background
'default
nil
)))
`
(
:background
,
bg-color
:foreground
,
bg-color
)))
(
setq-local
cursor-type
nil
))))
(
add-hook
'helm-minibuffer-set-up-hook
'daedreth/helm-hide-minibuffer
)
(
setq
helm-autoresize-max-height
0
helm-autoresize-min-height
40
helm-M-x-fuzzy-match
t
helm-buffers-fuzzy-matching
t
helm-recentf-fuzzy-match
t
helm-semantic-fuzzy-match
t
helm-imenu-fuzzy-match
t
helm-split-window-in-side-p
nil
helm-move-to-line-cycle-in-source
nil
helm-ff-search-library-in-sexp
t
helm-scroll-amount
8
helm-echo-input-in-header-line
nil
)
:init
(
helm-mode
1
))
(
require
'helm-config
)
(
helm-autoresize-mode
1
)
(
use-package
helm-flx
:custom
(
helm-flx-for-helm-find-files
t
)
(
helm-flx-for-helm-locate
t
)
:config
(
helm-flx-mode
+1
))
(
use-package
swiper-helm
:bind
(
"C-s"
.
swiper
))
Coding
Coding is one of my primary uses for Emacs, although lately it has shifted toward more general writing. This used to be the largest section in my config until Org mode overtook it :)
General settings and modules
When enabled, subword
allows navigating “sub words” individually in CamelCaseIdentifiers. For now I only enable it in clojure-mode
.
(
use-package
subword
:hook
(
clojure-mode
.
subword-mode
))
With aggressive-indent
, indentation is always kept up to date in the whole buffer. Sometimes it gets in the way, but in general it’s nice and saves a lot of work, so I enable it for all programming modes except for Python mode, where I explicitly disable as it often gets the indentation wrong and messes up existing code.
Disabled for now while I test how much I miss it (I often find it gets in the way, but I’m not sure how often it helps and I don’t even notice it)
(
use-package
aggressive-indent
:disabled
:diminish
aggressive-indent-mode
:hook
(
prog-mode
.
aggressive-indent-mode
)
(
python-mode
.
(
lambda
()
(
aggressive-indent-mode
-1
))))
With company-mode
, we get automatic completion - when there are completions available, a popup menu will appear when you stop typing for a moment, and you can either continue typing or accept the completion using the Enter key. I enable it globally.
(
use-package
company
:diminish
company-mode
:hook
(
after-init
.
global-company-mode
))
projectile-mode
allows us to perform project-relative operations such as searches, navigation, etc.
(
use-package
projectile
:defer
2
:diminish
projectile-mode
:config
(
projectile-global-mode
))
I find iedit
absolutely indispensable when coding. In short: when you hit Ctrl-:
, all occurrences of the symbol under the cursor (or the current selection) are highlighted, and any changes you make on one of them will be automatically applied to all others. It’s great for renaming variables in code, but it needs to be used with care, as it has no idea of semantics, it’s a plain string replacement, so it can inadvertently modify unintended parts of the code.
(
use-package
iedit
:config
(
set-face-background
'iedit-occurrence
"Magenta"
)
:bind
(
"C-;"
.
iedit-mode
))
Turn on the online documentation mode for all programming modes (not all of them support it) and for the Clojure REPL cider
mode.
(
use-package
eldoc
:diminish
:hook
(
prog-mode
.
turn-on-eldoc-mode
)
(
cider-repl-mode
.
turn-on-eldoc-mode
))
On-the-fly spell checking. I enable it for all text modes.
(
use-package
flyspell
:defer
1
:diminish
)
Clojure and LISP coding
I dabble in Clojure and Emacs LISP, and Emacs has some fantastic support for them. There’s a number of packages and configuration related to this, so I have a whole section for it.
The centerpiece is of course clojure-mode
. In addition to files ending in .clj
, I bind it automatically to .boot
files (both by extension and by shebang line) and to the Riemann config files.
(
use-package
clojure-mode
:mode
"\\.clj.*$"
:mode
"riemann.config"
:mode
"\\.boot"
:config
(
add-to-list
'magic-mode-alist
'
(
".* boot"
.
clojure-mode
)))
Enable some additional fontification for Clojure code.
(
use-package
clojure-mode-extra-font-locking
)
The cider
package provides a fantastic REPL built into Emacs. We configure a few aspects, including pretty printing, fontification, history size and others.
(
use-package
cider
:custom
;; nice pretty printing
(
cider-repl-use-pretty-printing
nil
)
;; nicer font lock in REPL
(
cider-repl-use-clojure-font-lock
t
)
;; result prefix for the REPL
(
cider-repl-result-prefix
"; => "
)
;; never ending REPL history
(
cider-repl-wrap-history
t
)
;; looong history
(
cider-repl-history-size
5000
)
;; persistent history
(
cider-repl-history-file
"~/.emacs.d/cider-history"
)
;; error buffer not popping up
(
cider-show-error-buffer
nil
)
;; go right to the REPL buffer when it's finished connecting
(
cider-repl-pop-to-buffer-on-connect
t
))
We use clj-refactor
for supporting advanced code refactoring in Clojure.
(
use-package
clj-refactor
:config
(
defun
my-clojure-mode-hook
()
(
clj-refactor-mode
1
)
(
yas-minor-mode
1
)
; for adding require/use/import statements
;; This choice of keybinding leaves cider-macroexpand-1 unbound
(
cljr-add-keybindings-with-prefix
"C-c C-m"
))
:hook
(
clojure-mode
.
my-clojure-mode-hook
))
Use emr
for supporting refactoring in Emacs LISP and some other languages.
(
use-package
emr
:config
(
bind-key
"A-RET"
'emr-show-refactor-menu
prog-mode-map
))
When coding in LISP-like languages, rainbow-delimiters
is a must-have - it marks each concentric pair of parenthesis with different colors, which makes it much easier to understand expressions and spot mistakes.
(
use-package
rainbow-delimiters
:hook
((
prog-mode
cider-repl-mode
)
.
rainbow-delimiters-mode
))
Another useful addition for LISP coding - smartparens
enforces parenthesis to match, and adds a number of useful operations for manipulating parenthesized expressions. I map M-(
to enclose the next expression as in paredit
using a custom function. Prefix argument can be used to indicate how many expressions to enclose instead of just 1. E.g. C-u 3 M-(
will enclose the next 3 sexps.
(
defun
zz/sp-enclose-next-sexp
(
num
)
(
interactive
"p"
)
(
insert-parentheses
(
or
num
1
)))
(
use-package
smartparens
:diminish
smartparens-mode
:config
(
require
'smartparens-config
)
:custom
(
sp-base-key-bindings
'paredit
)
:hook
((
clojure-mode
emacs-lisp-mode
lisp-mode
cider-repl-mode
racket-mode
racket-repl-mode
)
.
smartparens-strict-mode
)
(
smartparens-mode
.
sp-use-paredit-bindings
)
(
smartparens-mode
.
(
lambda
()
(
local-set-key
(
kbd
"M-("
)
'zz/sp-enclose-next-sexp
))))
Minor mode for highlighting the current sexp in LISP modes.
(
use-package
hl-sexp
:hook
((
clojure-mode
lisp-mode
emacs-lisp-mode
)
.
hl-sexp-mode
))
Trying out lispy for LISP code editing (disabled for now).
(
use-package
lispy
:disabled
:config
(
defun
enable-lispy-mode
()
(
lispy-mode
1
))
:hook
((
clojure-mode
emacs-lisp-mode
common-lisp-mode
scheme-mode
lisp-mode
)
.
enable-lispy-mode
))
Other programming languages
Many other programming languages are well served by a single mode, without so much setup as Clojure/LISP.
-
CFEngine policy files.
(
use-package
cfengine
:commands
cfengine3-mode
:mode
(
"\\.cf\\'"
.
cfengine3-mode
))
-
Perl.
(
use-package
cperl-mode
:mode
"\\.p[lm]\\'"
:interpreter
"perl"
:config
(
setq
cperl-hairy
t
))
-
Fish shell.
(
use-package
fish-mode
:mode
"\\.fish\\'"
:interpreter
"fish"
)
-
Lua, which I use for Hammerspoon configuration.
(
use-package
lua-mode
)
- YAML, generally useful
(
use-package
yaml-mode
)
- AppleScript
(
use-package
applescript-mode
)
- Go
(
use-package
go-mode
)
- Build and check MELPA package definitions
(
use-package
package-build
)
(
use-package
package-lint
)
-
Elvish shell
(
use-package
elvish-mode
)
-
Racket
(
use-package
racket-mode
)
-
Nix package files
(
use-package
nix-mode
)
-
Dockerfile files
(
use-package
dockerfile-mode
)
-
The Dhall configuration language
(
use-package
dhall-mode
:ensure
t
:mode
"\\.dhall\\'"
)
Other tools
- Use
helm-pass
as an interface topass
.(
use-package
helm-pass
)
- git interface with some simple configuration I picked up somewhere. When you press
C-c C-g
,magit-status
runs full-screen, but when you pressq
, it restores your previous window setup. Very handy.(
use-package
magit
:diminish
auto-revert-mode
:bind
((
"C-c C-g"
.
magit-status
)
:map
magit-status-mode-map
(
"q"
.
magit-quit-session
))
:config
(
defadvice
magit-status
(
around
magit-fullscreen
activate
)
"Make magit-status run alone in a frame."
(
window-configuration-to-register
:magit-fullscreen
)
ad-do-it
(
delete-other-windows
))
(
defun
magit-quit-session
()
"Restore the previous window configuration and kill the magit buffer."
(
interactive
)
(
kill-buffer
)
(
jump-to-register
:magit-fullscreen
)))
- Interface to use the silver-searcher
(
use-package
ag
)
- Publishing with Hugo. I don’t use this anymore since I started blogging with ox-hugo. I keep it loaded, but without its keybinding, because it makes it easy sometimes to see the history of my Markdown posts.
(
use-package
easy-hugo
:custom
(
easy-hugo-basedir
"~/Personal/devel/zzamboni.org/zzamboni.org/"
)
(
easy-hugo-url
"http://zzamboni.org/"
)
(
easy-hugo-previewtime
"300"
)
;;(define-key global-map (kbd "C-c C-e") 'easy-hugo)
)
- Function to randomize the order of lines in a region, from https://www.emacswiki.org/emacs/RandomizeBuffer.
(
defun
my-randomize-region
(
beg
end
)
"Randomize lines in region from BEG to END."
(
interactive
"*r"
)
(
let
((
lines
(
split-string
(
delete-and-extract-region
beg
end
)
"\n"
)))
(
when
(
string-equal
""
(
car
(
last
lines
1
)))
(
setq
lines
(
butlast
lines
1
)))
(
apply
'insert
(
mapcar
'cdr
(
sort
(
mapcar
(
lambda
(
x
)
(
cons
(
random
)
(
concat
x
"\n"
)))
lines
)
(
lambda
(
a
b
)
(
<
(
car
a
)
(
car
b
))))))))
-
auto-insert mode for automatically inserting user-defined templates for certain file types. It’s included with Emacs, so I just configure its directory to one inside my Dropbox, and set the hook to run it automatically when opening a file.
(
use-package
autoinsert
:ensure
nil
:custom
(
auto-insert-directory
(
concat
user-emacs-directory
"auto-insert/"
))
:hook
(
find-file
.
auto-insert
))
- Create and manage GitHub gists. Setting
gist-view-gist
tot
makes it open new gists in the web browser automatically after creating them.(
use-package
gist
:custom
(
gist-view-gist
t
"Automatically open new gists in browser"
))
-
Emacs Startup Profiler, to get detailed stats of what’s taking time during initialization.
(
use-package
esup
)
- Macro to measure how long a command takes, from https://stackoverflow.com/questions/23622296/emacs-timing-execution-of-function-calls-in-emacs-lisp
(
defmacro
measure-time
(
&rest
body
)
"Measure the time it takes to evaluate BODY."
`
(
let
((
time
(
current-time
)))
,@
body
(
message
"%.06f"
(
float-time
(
time-since
time
)))))
- Trying out Deft
(
use-package
deft
:custom
(
deft-use-filename-as-title
nil
)
(
deft-use-filter-string-for-filename
t
)
(
deft-file-naming-rules
'
((
noslash
.
"-"
)
(
nospace
.
"-"
)
(
case-fn
.
downcase
)))
(
deft-org-mode-title-prefix
t
)
(
deft-extensions
'
(
"org"
"txt"
"text"
"md"
"markdown"
))
(
deft-default-extension
"org"
))
- Ability to restart Emacs from within Emacs:
(
use-package
restart-emacs
)
-
Multiple cursors
(
use-package
multiple-cursors
:bind
(
"C-c m c"
.
mc/edit-lines
)
(
"C-c m <"
.
mc/mark-next-like-this
)
(
"C-c m >"
.
mc/mark-previous-like-this
)
(
"C-c m C-<"
.
mc/mark-all-like-this
))
General text editing
In addition to coding, I configure some modes that can be used for text editing.
-
AsciiDoc, which I use for my book and some other text. I also set up
visual-line-mode
andvariable-pitch-mode
here.adoc-mode
is not so granular asorg-mode
with respect to face assignments, so the variable/fixed distinction does not always work, but it’s still pretty good for long-text editing.(
use-package
adoc-mode
:mode
"\\.asciidoc\\'"
:hook
(
adoc-mode
.
visual-line-mode
)
(
adoc-mode
.
variable-pitch-mode
))
-
Markdown, generally useful. I also set up variable pitch and visual line mode.
(
use-package
markdown-mode
:hook
(
markdown-mode
.
visual-line-mode
)
(
markdown-mode
.
variable-pitch-mode
))
- When typopunct is enabled (needs to be enabled by hand), automatically inserts “pretty” quotes of the appropriate type.
(
use-package
typopunct
:config
(
typopunct-change-language
'english
t
))
-
undo-tree
visualises undo history as a tree for easy navigation (found about this from Jamie’s config)(
use-package
undo-tree
:ensure
t
:diminish
undo-tree-mode
:config
(
global-undo-tree-mode
1
))
Cheatsheet and experiments
Playground and how to do different things, not necessarily used in my Emacs config but useful sometimes.
This is how we get a global header property in org-mode
(
alist-get
:tangle
(
org-babel-parse-header-arguments
(
org-entry-get-with-inheritance
"header-args"
)))
Testing formatting org snippets to look like noweb-rendered output (disabled for now).
(
eval-after-load
'ob
(
customize-set-variable
'org-entities-user
'
((
"llangle"
"\\llangle"
t
"⟨⟨"
"<<"
"<<"
"«"
)
(
"rrangle"
"\\rrangle"
t
"⟩⟩"
">>"
">>"
"»"
)))
(
setq
org-babel-exp-code-template
(
concat
"\n@@latex:\\noindent@@\\llangle/%name/\\rrangle\\equiv\n"
org-babel-exp-code-template
)))
An experiment to reduce file tangle time, from https://www.wisdomandwonder.com/article/10630/how-fast-can-you-tangle-in-org-mode. In my tests it doesn’t have a noticeable impact.
(
setq
help/default-gc-cons-threshold
gc-cons-threshold
)
(
defun
help/set-gc-cons-threshold
(
&optional
multiplier
notify
)
"Set
`gc-cons-threshold'
either to its default value or a
`multiplier'
thereof."
(
let*
((
new-multiplier
(
or
multiplier
1
))
(
new-threshold
(
*
help/default-gc-cons-threshold
new-multiplier
)))
(
setq
gc-cons-threshold
new-threshold
)
(
when
notify
(
message
"Setting
`gc-cons-threshold'
to %s"
new-threshold
))))
(
defun
help/double-gc-cons-threshold
()
"Double
`gc-cons-threshold'
."
(
help/set
\
-gc-cons-threshold
10
))
(
add-hook
'org-babel-pre-tangle-hook
#'
help/double-gc-cons-threshold
)
(
add-hook
'org-babel-post-tangle-hook
#'
help/set-gc-cons-threshold
)
A work-in-progress Hammerspoon shell for Emacs, posted on the Hammerspoon mailing list.
;;===> hammerspoon-shell
;; Quick and dirty shell with interactive history search and persistence
;; Just drop into your ~/.emacs file.
;;
;; A hammerspoon buffer is any lua buffer visiting a pathname like
;; **/*hammerspoon**/*.lua
;; Usage: M-x hammerspoon-shell, or Hyper-s in a hammerspoon buffer.
;; In any hammerspoon buffer, Hyper-c runs dofile(file) on the visited file.
;;
;; Tip: to reload a Spoon "MySpoon" without hs.reload:
;; package.loaded.MySpoon=false hs.spoons.use("MySpoon",{config={debug=true})
(
add-hook
'lua-mode-hook
(
lambda
()
(
when
(
string-match
"hammerspoon"
buffer-file-name
)
(
local-set-key
(
kbd
"H-s"
)
#'
hammerspoon-shell
)
(
local-set-key
(
kbd
"H-c"
)
(
lambda
()
(
interactive
)
(
save-buffer
)
(
let
((
name
buffer-file-name
))
(
unless
(
and
(
boundp
'hammerspoon-buffer
)
(
buffer-live-p
hammerspoon-buffer
))
(
hammerspoon-shell
))
(
with-current-buffer
hammerspoon-buffer
(
goto-char
(
point-max
))
(
insert
(
concat
"dofile(\""
name
"\")"
))
(
comint-send-input
))))))))
(
defvar
hammerspoon-buffer
nil
)
(
defun
hammerspoon-shell
()
(
interactive
)
(
if
(
and
hammerspoon-buffer
(
comint-check-proc
hammerspoon-buffer
))
(
pop-to-buffer
hammerspoon-buffer
)
(
setq
hammerspoon-buffer
(
make-comint
"hammerspoon"
"/usr/local/bin/hs"
nil
"-C"
))
(
let*
((
process
(
get-buffer-process
hammerspoon-buffer
))
(
history-file
"~/.hammerspoon/.hs-history"
))
(
pop-to-buffer
hammerspoon-buffer
)
(
turn-on-comint-history
history-file
)
(
local-set-key
(
kbd
"<down>"
)
(
lambda
()
(
interactive
)
(
comint-move-or-history
nil
)))
(
local-set-key
(
kbd
"<up>"
)
(
lambda
()
(
interactive
)
(
comint-move-or-history
'up
))))))
;; Comint configs and extensions
(
setq
comint-input-ring-size
1024
comint-history-isearch
'dwim
)
(
defun
comint-move-or-history
(
up
&optional
arg
)
"History if at process mark, move otherwise"
(
interactive
)
(
let*
((
proc
(
get-buffer-process
(
current-buffer
)))
(
proc-pos
(
if
proc
(
marker-position
(
process-mark
proc
))))
(
arg
(
or
arg
1
))
(
arg
(
if
up
arg
(
-
arg
))))
(
if
(
and
proc
(
if
up
(
=
(
line-number-at-pos
)
(
line-number-at-pos
proc-pos
))
(
=
(
line-number-at-pos
)
(
line-number-at-pos
(
point-max
)))))
(
comint-previous-input
arg
)
(
forward-line
(
-
arg
)))))
(
defun
comint-write-history-on-exit
(
process
event
)
(
comint-write-input-ring
)
(
let
((
buf
(
process-buffer
process
)))
(
when
(
buffer-live-p
buf
)
(
with-current-buffer
buf
(
insert
(
format
"\nProcess %s %s"
process
event
))))))
(
defun
turn-on-comint-history
(
&optional
file
)
(
let
((
process
(
get-buffer-process
(
current-buffer
))))
(
when
process
(
setq
comint-input-ring-file-name
(
or
file
(
format
"~/.emacs.d/inferior-%s-history"
(
process-name
process
))))
(
comint-read-input-ring
)
;; Ensure input ring gets written
(
add-hook
'kill-buffer-hook
'comint-write-input-ring
nil
t
)
(
set-process-sentinel
process
#'
comint-write-history-on-exit
))))
;; Ensure all input rings get written on exit
(
defun
comint-write-input-ring-all-buffers
()
(
mapc
(
lambda
(
buffer
)
(
with-current-buffer
buffer
(
comint-write-input-ring
)))
(
buffer-list
)))
(
add-hook
'kill-emacs-hook
'comint-write-input-ring-all-buffers
)
Epilogue
Here we close the let
expression from the preface.
)
We also reset the value of gc-cons-threshold
, not to its original value, we still leave it larger than default so that GCs don’t happen so often.
(
setq
gc-cons-threshold
(
*
2
1000
1000
))
Hammerspoon config
This is my Hammerspoon configuration file.
This file is written in literate programming style using org-mode. See init.lua for the generated file. You can see this in a nicer format on my blog post My Hammerspoon Configuration, With Commentary.
If you want to learn more about Hammerspoon, check out my book Learning Hammerspoon!
General variables and configuration
Global log level. Per-spoon log level can be configured in each Install:andUse
block below.
hs
.
logger
.
defaultLogLevel
=
"info"
I use hyper
and shift_hyper
as the modifiers for most of my key bindings, so I define them as variables here for easier referencing.
hyper
=
{
"cmd"
,
"alt"
,
"ctrl"
}
shift_hyper
=
{
"cmd"
,
"alt"
,
"ctrl"
,
"shift"
}
Set up an abbreviation for hs.drawing.color.x11
since I use it repeatedly later on.
col
=
hs
.
drawing
.
color
.
x11
Work’s logo, which I use in some of my Seal shortcuts later on.
work_logo
=
hs
.
image
.
imageFromPath
(
hs
.
configdir
..
"/files/work_logo_2x.png"
)
Spoon Management
Set up SpoonInstall - this is the only spoon that needs to be manually installed (it is already there if you check out this repository), all the others are installed and configured automatically.
hs
.
loadSpoon
(
"SpoonInstall"
)
Configuration of my personal spoon repository, which contains Spoons that have not been merged in the main repo. See the descriptions at https://zzamboni.github.io/zzSpoons/.
spoon
.
SpoonInstall
.
repos
.
zzspoons
=
{
url
=
"https://github.com/zzamboni/zzSpoons"
,
desc
=
"zzamboni's spoon repository"
,
}
I prefer sync notifications, makes them easier to read.
spoon
.
SpoonInstall
.
use_syncinstall
=
true
This is just a shortcut to make the declarations below look more readable, i.e. Install:andUse
instead of spoon.SpoonInstall:andUse
.
Install
=
spoon
.
SpoonInstall
BetterTouchTool
I’m currently working on a new BetterTouchTool.spoon which provides integration with the BetterTouchTool AppleScript API. This is in heavy development! See the configuration for the Hammer spoon in System and UI for an example of how to use it.
Install
:
andUse
(
"BetterTouchTool"
,
{
loglevel
=
'debug'
})
BTT
=
spoon
.
BetterTouchTool
URL Dispatching to site-specific browsers
The URLDispatcher spoon makes it possible to open URLs with different browsers. I have created different site-specific browsers using Epichrome, which allows me to keep site-specific bookmarks, search settings, etc.
JiraApp
=
"org.epichrome.app.Jira"
WikiApp
=
"org.epichrome.app.Wiki"
CollabApp
=
"org.epichrome.app.Collab"
OpsGenieApp
=
"org.epichrome.app.OpsGenie"
Install
:
andUse
(
"URLDispatcher"
,
{
config
=
{
url_patterns
=
{
{
"https?://issue.work.com"
,
JiraApp
},
{
"https?://jira.work.com"
,
JiraApp
},
{
"https?://wiki.work.com"
,
WikiApp
},
{
"https?://collaboration.work.com"
,
CollabApp
},
{
"https?://app.opsgenie.com"
,
OpsGenieApp
},
{
"https?://app.eu.opsgenie.com"
,
OpsGenieApp
},
},
default_handler
=
"com.google.Chrome"
-- default_handler = "com.electron.brave"
-- default_handler = "com.brave.Browser.dev"
},
start
=
true
}
)
Window and screen manipulation
The WindowHalfsAndThirds spoon sets up multiple key bindings for manipulating the size and position of windows.
Install
:
andUse
(
"WindowHalfsAndThirds"
,
{
config
=
{
use_frame_correctness
=
true
},
hotkeys
=
'default'
}
)
The WindowScreenLeftAndRight spoon sets up key bindings for moving windows between multiple screens.
Install
:
andUse
(
"WindowScreenLeftAndRight"
,
{
hotkeys
=
'default'
}
)
The WindowGrid spoon sets up a key binding (Hyper-g
here) to overlay a grid that allows resizing windows by specifying their opposite corners.
Install
:
andUse
(
"WindowGrid"
,
{
config
=
{
gridGeometries
=
{
{
"6x4"
}
}
},
hotkeys
=
{
show_grid
=
{
hyper
,
"g"
}},
start
=
true
}
)
The ToggleScreenRotation spoon sets up a key binding to rotate the external screen (the spoon can set up keys for multiple screens if needed, but by default it rotates the first external screen).
Install
:
andUse
(
"ToggleScreenRotation"
,
{
hotkeys
=
{
first
=
{
hyper
,
"f15"
}
}
}
)
Organization and Productivity
The UniversalArchive spoon sets up a single key binding (Ctrl-Cmd-a
) to archive the current item in Evernote, Mail and Outlook.
Install
:
andUse
(
"UniversalArchive"
,
{
config
=
{
evernote_archive_notebook
=
".Archive"
,
archive_notifications
=
false
},
hotkeys
=
{
archive
=
{
{
"ctrl"
,
"cmd"
},
"a"
}
}
}
)
The SendToOmniFocus spoon sets up a single key binding (Hyper-t
) to send the current item to OmniFocus from multiple applications. We use the fn
attribute of Install:andUse
to call a function which registers some of the Epichrome site-specific-browsers I use, so that the Spoon knows how to collect items from them.
function
chrome_item
(
n
)
return
{
apptype
=
"chromeapp"
,
itemname
=
n
}
end
function
OF_register_additional_apps
(
s
)
s
:
registerApplication
(
"Collab"
,
chrome_item
(
"tab"
))
s
:
registerApplication
(
"Wiki"
,
chrome_item
(
"wiki page"
))
s
:
registerApplication
(
"Jira"
,
chrome_item
(
"issue"
))
s
:
registerApplication
(
"Brave Browser Dev"
,
chrome_item
(
"page"
))
end
Install
:
andUse
(
"SendToOmniFocus"
,
{
config
=
{
quickentrydialog
=
false
,
notifications
=
false
},
hotkeys
=
{
send_to_omnifocus
=
{
hyper
,
"t"
}
},
fn
=
OF_register_additional_apps
,
}
)
The EvernoteOpenAndTag spoon sets up some missing key bindings for note manipulation in Evernote. I no longer use Evernote for GTD, so I have disabled the shortcuts for tagging notes.
Install
:
andUse
(
"EvernoteOpenAndTag"
,
{
hotkeys
=
{
open_note
=
{
hyper
,
"o"
},
-- ["open_and_tag-+work"] = { hyper, "w" },
-- ["open_and_tag-+personal"] = { hyper, "p" },
-- ["tag-@zzdone"] = { hyper, "z" }
}
}
)
The TextClipboardHistory spoon implements a clipboard history, only for text items. It is invoked with Cmd-Shift-v
.
This is disabled for the moment as I experiment with BetterTouchTool’s built-in clipboard history, which I have bound to the same key combination for consistency in my workflow.
Install
:
andUse
(
"TextClipboardHistory"
,
{
disable
=
true
,
config
=
{
show_in_menubar
=
false
,
},
hotkeys
=
{
toggle_clipboard
=
{
{
"cmd"
,
"shift"
},
"v"
}
},
start
=
true
,
}
)
System and UI
The BTT_restart_Hammerspoon
function sets up a BetterTouchTool widget which also executes the config_reload
action from the spoon. This gets assigned to the fn
config parameter in the configuration of the Hammer spoon below, which has the effect of calling the function with the Spoon object as its parameter.
This is still very manual - the uuid
parameter contains the ID of the BTT widget to configure, and for now you have to get it by hand from BTT and paste it here.
function
BTT_restart_hammerspoon
(
s
)
BTT
:
bindSpoonActions
(
s
,
{
config_reload
=
{
kind
=
'touchbarButton'
,
uuid
=
"FF8DA717-737F-4C42-BF91-E8826E586FA1"
,
name
=
"Restart"
,
icon
=
hs
.
image
.
imageFromName
(
hs
.
image
.
systemImageNames
.
ApplicationIcon
),
color
=
hs
.
drawing
.
color
.
x11
.
orange
,
}})
end
The Hammer spoon (get it? hehe) is a simple wrapper around some common Hamerspoon configuration variables. Note that this gets loaded from my personal repo, since it’s not in the official repository.
Install
:
andUse
(
"Hammer"
,
{
repo
=
'zzspoons'
,
config
=
{
auto_reload_config
=
false
},
hotkeys
=
{
config_reload
=
{
hyper
,
"r"
},
toggle_console
=
{
hyper
,
"y"
}
},
fn
=
BTT_restart_Hammerspoon
,
start
=
true
}
)
The Caffeine spoon allows preventing the display and the machine from sleeping. I use it frequently when playing music from my machine, to avoid having to unlock the screen whenever I want to change the music. In this case we also create a function BTT_caffeine_widget
to configure the widget to both execute the corresponding function, and to set its icon according to the current state.
function
BTT_caffeine_widget
(
s
)
BTT
:
bindSpoonActions
(
s
,
{
toggle
=
{
kind
=
'touchbarWidget'
,
uuid
=
'72A96332-E908-4872-A6B4-8A6ED2E3586F'
,
name
=
'Caffeine'
,
widget_code
=
[[
do
title = " "
icon = hs.image.imageFromPath(spoon.Caffeine.spoonPath.."/caffeine-off.pdf")
if (hs.caffeinate.get('displayIdle')) then
icon = hs.image.imageFromPath(spoon.Caffeine.spoonPath.."/caffeine-on.pdf")
end
print(hs.json.encode({ text = title,
icon_data = BTT:hsimageToBTTIconData(icon) }))
end
]]
,
code
=
"spoon.Caffeine.clicked()"
,
widget_interval
=
1
,
color
=
hs
.
drawing
.
color
.
x11
.
black
,
icon_only
=
true
,
icon_size
=
hs
.
geometry
.
size
(
15
,
15
),
BTTTriggerConfig
=
{
BTTTouchBarFreeSpaceAfterButton
=
0
,
BTTTouchBarItemPadding
=
-
6
,
},
}
})
end
Install
:
andUse
(
"Caffeine"
,
{
start
=
true
,
hotkeys
=
{
toggle
=
{
hyper
,
"1"
}
},
fn
=
BTT_caffeine_widget
,
})
The MenubarFlag spoon colorizes the menubar according to the selected keyboard language or layout (functionality inspired by ShowyEdge). I use English, Spanish and German, so those are the colors I have defined.
Install
:
andUse
(
"MenubarFlag"
,
{
config
=
{
colors
=
{
[
"U.S."
]
=
{
},
Spanish
=
{
col
.
green
,
col
.
white
,
col
.
red
},
German
=
{
col
.
black
,
col
.
red
,
col
.
yellow
},
}
},
start
=
true
}
)
The MouseCircle spoon shows a circle around the mouse pointer when triggered. I have it disabled for now because I have the macOS shake-to-grow feature enabled.
Install
:
andUse
(
"MouseCircle"
,
{
disable
=
true
,
config
=
{
color
=
hs
.
drawing
.
color
.
x11
.
rebeccapurple
},
hotkeys
=
{
show
=
{
hyper
,
"m"
}
}
}
)
One of my original bits of Hammerspoon code, now made into a spoon (although I keep it disabled, since I don’t really use it). The ColorPicker spoon shows a menu of the available color palettes, and when you select one, it draws swatches in all the colors in that palette, covering the whole screen. You can click on any of them to copy its name to the clipboard, or cmd-click to copy its RGB code.
Install
:
andUse
(
"ColorPicker"
,
{
disable
=
true
,
hotkeys
=
{
show
=
{
hyper
,
"z"
}
},
config
=
{
show_in_menubar
=
false
,
},
start
=
true
,
}
)
I use Homebrew, and when I run brew update
, I often wonder about what some of the formulas shown are (names are not always obvious). The BrewInfo spoon allows me to point at a Formula or Cask name and press Hyper-b
or Hyper-c
(for Casks) to have the output of the info
command in a popup window, or the same key with Shift-Hyper
to open the URL of the Formula/Cask.
Install
:
andUse
(
"BrewInfo"
,
{
config
=
{
brew_info_style
=
{
textFont
=
"Inconsolata"
,
textSize
=
14
,
radius
=
10
}
},
hotkeys
=
{
-- brew info
show_brew_info
=
{
hyper
,
"b"
},
open_brew_url
=
{
shift_hyper
,
"b"
},
-- brew cask info
show_brew_cask_info
=
{
hyper
,
"c"
},
open_brew_cask_url
=
{
shift_hyper
,
"c"
},
}
}
)
The KSheet spoon traverses the current application’s menus and builds a cheatsheet of the keyboard shortcuts, showing it in a nice popup window.
Install
:
andUse
(
"KSheet"
,
{
hotkeys
=
{
toggle
=
{
hyper
,
"/"
}
}})
The TimeMachineProgress spoon shows an indicator about the progress of the ongoing Time Machine backup. The indicator disappears when there is no backup going on.
Install
:
andUse
(
"TimeMachineProgress"
,
{
start
=
true
}
)
Other applications
The ToggleSkypeMute spoon sets up the missing keyboard bindings for toggling the mute button on Skype and Skype for Business. I’m not fully happy with this spoon - it should auto-detect the application instead of having separate keys for each application, and it could be extended to more generic use.
Install
:
andUse
(
"ToggleSkypeMute"
,
{
hotkeys
=
{
toggle_skype
=
{
shift_hyper
,
"v"
},
toggle_skype_for_business
=
{
shift_hyper
,
"f"
}
}
}
)
The HeadphoneAutoPause spoon implements auto-pause/resume for iTunes, Spotify and others when the headphones are unplugged.
Install
:
andUse
(
"HeadphoneAutoPause"
,
{
start
=
true
}
)
Seal
The Seal spoon is a powerhouse - it implements a Spotlight-like launcher, but which allows for infinite configurability of what can be done or searched from the launcher window. I use Seal as my default launcher, triggered with Cmd-space
, although I still keep Spotlight around under Hyper-space
, mainly for its search capabilities.
We start by loading the spoon, and specifying which plugins we want.
Install
:
andUse
(
"Seal"
,
{
hotkeys
=
{
show
=
{
{
"cmd"
},
"space"
}
},
fn
=
function
(
s
)
s
:
loadPlugins
({
"apps"
,
"calc"
,
"safari_bookmarks"
,
"screencapture"
,
"useractions"
})
s
.
plugins
.
safari_bookmarks
.
always_open_with_safari
=
false
s
.
plugins
.
useractions
.
actions
=
{
<<
useraction
-
definitions
>>
}
s
:
refreshAllCommands
()
end
,
start
=
true
,
}
)
The useractions
Seal plugin allows me to define my own shortcuts. For example, a bookmark to the Hammerspoon documentation page:
[
"Hammerspoon docs webpage"
]
=
{
url
=
"https://hammerspoon.org/docs/"
,
icon
=
hs
.
image
.
imageFromName
(
hs
.
image
.
systemImageNames
.
ApplicationIcon
),
},
Or to manually trigger my work/non-work transition scripts (see below):
[
"Leave corpnet"
]
=
{
fn
=
function
()
spoon
.
WiFiTransitions
:
processTransition
(
'foo'
,
'corpnet01'
)
end
,
icon
=
work_logo
,
},
[
"Arrive in corpnet"
]
=
{
fn
=
function
()
spoon
.
WiFiTransitions
:
processTransition
(
'corpnet01'
,
'foo'
)
end
,
icon
=
work_logo
,
},
Or to translate things using dict.leo.org:
[
"Translate using Leo"
]
=
{
url
=
"http://dict.leo.org/englisch-deutsch/${query}"
,
icon
=
'favicon'
,
keyword
=
"leo"
,
}
Network transitions
The WiFiTransitions spoon allows triggering arbitrary actions when the SSID changes. I am interested in the change from my work network (corpnet01) to other networks, mainly because at work I need a proxy for all connections to the Internet. I have two applications which don’t handle these transitions gracefully on their own: Spotify and Adium. So I have written a couple of functions for helping them along.
The reconfigSpotifyProxy
function quits Spotify, updates the proxy settings in its config file, and restarts it.
function
reconfigSpotifyProxy
(
proxy
)
local
spotify
=
hs
.
appfinder
.
appFromName
(
"Spotify"
)
local
lastapp
=
nil
if
spotify
then
lastapp
=
hs
.
application
.
frontmostApplication
()
spotify
:
kill
()
hs
.
timer
.
usleep
(
40000
)
end
-- I use CFEngine to reconfigure the Spotify preferences
cmd
=
string.format
(
"/usr/local/bin/cf-agent -K -f %s/files/spotify-proxymode.cf%s"
,
hs
.
configdir
,
(
proxy
and
" -DPROXY"
or
" -DNOPROXY"
))
output
,
status
,
t
,
rc
=
hs
.
execute
(
cmd
)
if
spotify
and
lastapp
then
hs
.
timer
.
doAfter
(
3
,
function
()
if
not
hs
.
application
.
launchOrFocus
(
"Spotify"
)
then
hs
.
notify
.
show
(
"Error launching Spotify"
,
""
,
""
)
end
if
lastapp
then
hs
.
timer
.
doAfter
(
0.5
,
hs
.
fnutils
.
partial
(
lastapp
.
activate
,
lastapp
))
end
end
)
end
end
The reconfigAdiumProxy
function uses AppleScript to tell Adium about the change without having to restart it - only if Adium is already running.
function
reconfigAdiumProxy
(
proxy
)
app
=
hs
.
application
.
find
(
"Adium"
)
if
app
and
app
:
isRunning
()
then
local
script
=
string.format
(
[[
tell application "Adium"
repeat with a in accounts
if (enabled of a) is true then
set proxy enabled of a to %s
end if
end repeat
go offline
go online
end tell
]]
,
hs
.
inspect
(
proxy
))
hs
.
osascript
.
applescript
(
script
)
end
end
Functions to stop applications that are disallowed in the work network.
function
stopApp
(
name
)
app
=
hs
.
application
.
get
(
name
)
if
app
and
app
:
isRunning
()
then
app
:
kill
()
end
end
function
forceKillProcess
(
name
)
hs
.
execute
(
"pkill "
..
name
)
end
function
startApp
(
name
)
hs
.
application
.
open
(
name
)
end
The configuration for the WiFiTransitions spoon invoked these functions with the appropriate parameters.
Install
:
andUse
(
"WiFiTransitions"
,
{
config
=
{
actions
=
{
-- { -- Test action just to see the SSID transitions
-- fn = function(_, _, prev_ssid, new_ssid)
-- hs.notify.show("SSID change",
-- string.format("From '%s' to '%s'",
-- prev_ssid, new_ssid), "")
-- end
-- },
{
-- Enable proxy config when joining corp network
to
=
"corpnet01"
,
fn
=
{
hs
.
fnutils
.
partial
(
reconfigSpotifyProxy
,
true
),
hs
.
fnutils
.
partial
(
reconfigAdiumProxy
,
true
),
hs
.
fnutils
.
partial
(
forceKillProcess
,
"Dropbox"
),
hs
.
fnutils
.
partial
(
stopApp
,
"Evernote"
),
}
},
{
-- Disable proxy config when leaving corp network
from
=
"corpnet01"
,
fn
=
{
hs
.
fnutils
.
partial
(
reconfigSpotifyProxy
,
false
),
hs
.
fnutils
.
partial
(
reconfigAdiumProxy
,
false
),
hs
.
fnutils
.
partial
(
startApp
,
"Dropbox"
),
}
},
}
},
start
=
true
,
}
)
Pop-up translation
I live in Switzerland, and my German is far from perfect, so the PopupTranslateSelection spoon helps me a lot. It allows me to select some text and, with a keystroke, translate it to any of three languages using Google Translate. Super useful! Usually, Google’s auto-detect feature works fine, so the translate_to_<lang>
keys are sufficient. I have some translate_<from>_<to>
keys set up for certain language pairs for when this doesn’t quite work (I don’t think I’ve ever needed them).
local
wm
=
hs
.
webview
.
windowMasks
Install
:
andUse
(
"PopupTranslateSelection"
,
{
config
=
{
popup_style
=
wm
.
utility
|
wm
.
HUD
|
wm
.
titled
|
wm
.
closable
|
wm
.
resizable
,
},
hotkeys
=
{
translate_to_en
=
{
hyper
,
"e"
},
translate_to_de
=
{
hyper
,
"d"
},
translate_to_es
=
{
hyper
,
"s"
},
translate_de_en
=
{
shift_hyper
,
"e"
},
translate_en_de
=
{
shift_hyper
,
"d"
},
}
}
)
I am now testing DeepLTranslate, based on PopupTranslateSelection but which uses the DeepL translator.
Install
:
andUse
(
"DeepLTranslate"
,
{
disable
=
true
,
config
=
{
popup_style
=
wm
.
utility
|
wm
.
HUD
|
wm
.
titled
|
wm
.
closable
|
wm
.
resizable
,
},
hotkeys
=
{
translate
=
{
hyper
,
"e"
},
}
}
)
Leanpub integration
The Leanpub spoon provides monitoring of book build jobs.
Install
:
andUse
(
"Leanpub"
,
{
config
=
{
watch_books
=
{
-- api_key gets set in init-local.lua like this:
-- spoon.Leanpub.api_key = "my-api-key"
{
slug
=
"learning-hammerspoon"
},
{
slug
=
"learning-cfengine"
},
{
slug
=
"lit-config"
},
{
slug
=
"zztestbook"
},
}
},
start
=
true
,
})
Loading private configuration
In init-local.lua
I keep experimental or private stuff (like API tokens) that I don’t want to publish in my main config. This file is not committed to any publicly-accessible git repositories.
local
localfile
=
hs
.
configdir
..
"/init-local.lua"
if
hs
.
fs
.
attributes
(
localfile
)
then
dofile
(
localfile
)
end
End-of-config animation
The FadeLogo spoon simply shows an animation of the Hammerspoon logo to signal the end of the config load.
Install
:
andUse
(
"FadeLogo"
,
{
config
=
{
default_run
=
1.0
,
},
start
=
true
}
)
If you don’t want to use FadeLogo, you can have a regular notification.
-- hs.notify.show("Welcome to Hammerspoon", "Have fun!", "")
Elvish config
This is my main config file for Elvish.
This file is written in literate programming style using org-mode. See rc.elv for the generated file. You can see this in a nicer format on my blog post My Elvish Configuration With Commentary.
Paths
First we set up the executable paths. We set the GOPATH
environment variable while we are at it, since we need to use it as part of the path.
E
:
GOPATH
=
~/
Dropbox
/Personal/devel/g
o
E
:
RACKETPATH
=
~/
Library
/Racket/
7.2
paths
=
[
~/
bin
$E
:
GOPATH
/
bin
$E
:
RACKETPATH
/
bin
~/
Library
/Python/3.7/
bin
/usr/local/opt/coreutils/libexec/g
nubin
/usr/local/opt/texinfo/
bin
/usr/local/opt/python/libexec/
bin
/usr/local/opt/ruby/
bin
~/
Dropbox
/Personal/devel/hammerspoon/spoon/
bin
~/.
gem
/ruby/2.4.0/
bin
/opt/X11/
bin
/Library/TeX/
texbin
/usr/local/
bin
/usr/local/s
bin
/usr/s
bin
/
sbin
/usr/
bin
/
bin
]
Package installation
The bundled epm module allows us to install and manage Elvish packages.
use epm
For now I use these packages:
- github.com/zzamboni/elvish-modules contains all my modules except completions and themes. Maybe these should be separated eventually, but for now this works fine.
- github.com/zzamboni/elvish-themes contains my prompt themes (only chain for now).
- github.com/zzamboni/elvish-completions contains my completer definitions.
-
github.com/xiaq/edit.elv, which includes the
smart-matcher
module used below. - github.com/muesli/elvish-libs for the git utilities module.
- github.com/iwoloschin/elvish-packages for the update.elv package.
epm
:
install
&
silent
-
if
-
installed
=
$true
\
github
.
com
/zzamboni/
elvish
-
modules
\
github
.
com
/zzamboni/
elvish
-
completions
\
github
.
com
/zzamboni/
elvish
-
themes
\
github
.
com
/xiaq/
edit
.
elv
\
github
.
com
/muesli/
elvish
-
libs
\
github
.
com
/iwoloschin/
elvish
-
packages
The modules within each package get loaded individually below.
Automatic proxy settings
When I am in the office, I need to use a proxy to access the Internet. For macOS applications, the proxy is set automatically using a company-provided PAC file. For the environment variables http_proxy
and https_proxy
, commonly used by command-line programs, the proxy module allows me to define a test which determines when the proxy should be used, so that the change is done automatically. We load this early on so that other modules which need to access the network get the correct settings already.
First, we load the module and set the proxy host.
use github.com/zzamboni/elvish-modules/proxy
proxy:host = "http://proxy.corproot.net:8079"
Next, we set the test function to enable proxy auto-setting. In my case, the /etc/resolv.conf
file contains the corproot.net
domain (set through DHCP) when I’m in the corporate network, so I can check for that.
proxy
:
test
=
{
and
?(
test
-
f
/etc/
resolv
.
conf
)
\
?(
egrep
-
q
'^(search|domain).*(corproot.net|swissptt.ch)'
/etc/
resolv
.
conf
)
}
We run an initial check so that other commands in rc.org get the correctd settings already, even before the first prompt.
proxy
:
autoset
Base modules
Load the bundled re module to have access to regular expression functions.
use re
The bundled readline-binding module associates some Emacs-like keybindings for manipulation of the command line.
use readline-binding
I add a couple of keybindings which are missing from the default readline-binding
module:
-
Alt-backspace
to delete small-wordedit
:
insert
:
binding
[
Alt
-
Backspace
]
=
$edit
:
kill
-
small
-
word
-
left
~
-
Alt-d
to delete the small-word under the cursoredit
:
insert
:
binding
[
Alt
-
d
]
=
$edit
:
kill
-
small
-
word
-
right
~
Aliases
Elvish does not have built-in alias functionality, but this is implemented easily using the alias module, which stores the alias definitions as functions under ~/.elvish/aliases/ and loads them automatically.
use github.com/zzamboni/elvish-modules/alias
For reference, I define here a few of my commonly-used aliases:
alias
:
new
dfc
e
:
dfc
-
W
-
p
-/
dev
/
disk1s4
,
devfs
,
map
alias
:
new
ls
e
:
exa
--
color
-
scale
--
git
--
group
-
directories
-
first
alias
:
new
cat
bat
alias
:
new
more
bat
--
paging
always
alias
:
new
v
vagrant
Completions
The smart-matcher module tries prefix match, smart-case prefix match, substring match, smart-case substring match, subsequence match and smart-case subsequence match automatically.
use github.com/xiaq/edit.elv/smart-matcher
smart-matcher:apply
Other possible values for edit:completion:matcher
are [p]{ edit:match-prefix &smart-case $p }
for smart-case completion (if your pattern is entirely lower case it ignores case, otherwise it’s case sensitive). &smart-case
can be replaced with &ignore-case
to make it always case-insensitive.
I also configure Tab
to trigger completion mode, but also to automatically enter “filter mode”, so I can keep typing the filename I want, without having to use the arrow keys. Disabled as this is the default behavior starting with commit b24e4a7, but you may need it if you are running an older version for any reason and want this behavior.
# edit:insert:binding[Tab] = {
# edit:completion:smart-start
# edit:completion:trigger-filter
# }
I load some command-specific completions from the elvish-completions package:
use github.com/zzamboni/elvish-completions/vcsh
use github.com/zzamboni/elvish-completions/cd
use github.com/zzamboni/elvish-completions/ssh
use github.com/zzamboni/elvish-completions/builtins
I configure the git completer to use hub
instead of git
(if you use plain git, you don’t need to call git:init
)
use github.com/zzamboni/elvish-completions/git
git:git-command = hub
git:init
This is not usually necessary, but I load the comp
library specifically since I do a lot of tests and development of completions.
use github.com/zzamboni/elvish-completions/comp
Prompt theme
I use the chain prompt theme, ported from the fish theme at https://github.com/oh-my-fish/theme-chain.
use github.com/zzamboni/elvish-themes/chain
chain:bold-prompt = $true
I set the color of the directory segment, the prompt chains and the prompt arrow in my prompt to a session-identifying color.
chain
:
segment
-
style
=
[
&
dir
=
session
&
chain
=
session
&
arrow
=
session
&
git
-
combined
=
session
]
Customize some of the glyphs for the font I use in my terminal.
chain
:
glyph
[
git
-
ahead
]
=
"⬆ "
chain
:
glyph
[
git
-
staged
]
=
"✔ "
Elvish has a comprehensive mechanism for displaying prompts with useful information while avoiding getting blocked by prompt functions which take too long to finish. For the most part the defaults work well. One change I like to make is to change the stale prompt transformer function to make the prompt dim when stale:
edit
:
prompt
-
stale
-
transform
=
{
each
[
x
]{
styled
$x
[
text
]
"gray"
}
}
Another possibility is to make the prompt stay the same when stale - useful to avoid distractions (disabled for now):
# edit:prompt-stale-transform = $all~
I also like the continuous update of the prompt as I type (by default it only updates on Enter and on $pwd
changes, but I like also git status changes to be updated automatically), so I increase its eagerness.
edit
:
-
prompt
-
eagerness
=
10
Long-running-command notifications
The long-running-notifications module allows for producing a notification when a command takes longer than a certain time to finish (by default the period is 10 seconds). The module automatically detects when terminal-notifier is available on macOS and uses it to produce Mac-style notifications, otherwise it prints a notification on the terminal.
use github.com/zzamboni/elvish-modules/long-running-notifications
Directory and command navigation and history
Elvish comes with built-in location and command history modes, and these are the main mechanism for accessing prior directories and commands. The weight-keeping in location mode makes the most-used directories automatically raise to the top of the list over time.
I have decades of muscle memory using !!
and !$
to insert the last command and its last argument, respectively. The bang-bang module allows me to keep using them.
use github.com/zzamboni/elvish-modules/bang-bang
The dir module implements a directory history and some related functions. I alias the cd
command to dir:cd
so that any directory changes are kept in the history. I also alias cdb
to dir:cdb
function, which allows changing to the base directory of the argument.
use github.com/zzamboni/elvish-modules/dir
alias:new cd &use=[github.com/zzamboni/elvish-modules/dir] dir:cd
alias:new cdb &use=[github.com/zzamboni/elvish-modules/dir] dir:cdb
dir
also implements a narrow-based directory history chooser, which I bind to Alt-i
(I have found I don’t use this as much as I thought I would - the built-in location mode works nicely).
edit
:
insert
:
binding
[
Alt
-
i
]
=
$dir
:
history
-
chooser
~
I bind Alt-b/f
to dir:left-small-word-or-prev-dir
and dir:right-small-word-or-next-dir
respectively, which “do the right thing” depending on the current content of the command prompt: if it’s empty, they move back/forward in the directory history, otherwise they move through the words of the current command. In my Terminal.app setup, Alt-left/right
also produce Alt-b/f
, so these bindings work for those keys as well.
edit
:
insert
:
binding
[
Alt
-
b
]
=
$dir
:
left
-
small
-
word
-
or
-
prev
-
dir
~
edit
:
insert
:
binding
[
Alt
-
f
]
=
$dir
:
right
-
small
-
word
-
or
-
next
-
dir
~
The following makes the location and history modes be case-insensitive by default:
edit
:
location
:
matcher
=
[
@
a
]{
edit
:
location
:
match
-
dir
-
pattern
&
ignore
-
case
$
@
a
}
edit
:
insert
:
binding
[
Ctrl
-
R
]
=
{
edit
:
histlist
:
start
edit
:
histlist
:
toggle
-
case
-
sensitivity
}
Dynamic terminal title
The terminal-title module handles setting the terminal title dynamically according to the current directory or the current command being executed.
use github.com/zzamboni/elvish-modules/terminal-title
Loading private settings
The private
module sets up some private settings such as authentication tokens. This is not on github :) The $private-loaded
variable gets set to $ok
if the module was loaded correctly.
private-loaded = ?(use private)
O’Reilly Atlas
I sometimes use the O’Reilly Atlas publishing platform. The atlas module contains some useful functions for triggering and accessing document builds.
use github.com/zzamboni/elvish-modules/atlas
OpsGenie
I used OpsGenie at work for a while, so I put together the opsgenie library to make API operations easier. I don’t actively use or maintain this anymore.
use github.com/zzamboni/elvish-modules/opsgenie
LeanPub
I use LeanPub for publishing my books, so I have written a few utility functions. I don’t use this regularly, I have much better integration using Hammerspoon and CircleCI, I wrote about it in my blog: Automating Leanpub book publishing with Hammerspoon and CircleCI.
use github.com/zzamboni/elvish-modules/leanpub
Environment variables
Default options to less
.
E
:
LESS
=
"-i -R"
Use vim as the editor from the command line (although I am an Emacs fan, I still sometimes use vim for quick editing).
E
:
EDITOR
=
"vim"
Locale setting.
E
:
LC_ALL
=
"en_US.UTF-8"
Utility functions
The util module includes various utility functions.
use github.com/zzamboni/elvish-modules/util
I use muesli’s git utilities module.
use github.com/muesli/elvish-libs/git
The update.elv package prints a message if there are new commits in Elvish after the running version.
use github.com/iwoloschin/elvish-packages/update
update:curl-timeout = 3
update:check-commit &verbose
Work-specific stuff
I have a private library which contains some work-specific functions.
use work
Exporting aliases
We populate $-exports-
with the alias definitions so that they become available in the interactive namespace.
-exports- = (alias:export)