kevinsjoberg

My Emacs adventures continue. Didn't spend a lot of time at the keyboard this weekend, but I did setup fido mode.

Fido Mode

By default, Emacs uses Icomplete as a convenient way to quickly select an element among the possible completions in a minibuffer. An alternative to Icomplete mode is Fido mode. This is very similar to Icomplete mode, but retains some functionality from a popular extension called Ido mode (in fact the name is derived from “Fake Ido”). Enabling fido-mode was easy.

;; Enable fido mode
;; An enhanced `icomplete-mode' that emulates `ido-mode'.
(fido-mode 1)

By default, completions are displayed horizontally. Unfortunately, there isn't a built-in way to change the direction yet. Thankfully, there's an extension called icomplete-vertical that we can use instead.

;; Enable icomplete-vertical (Global Emacs minor mode to display icomplete candidates vertically)
;; See https://github.com/oantolin/icomplete-vertical#installation-and-usage
(use-package icomplete-vertical
  :config  
  :hook (icomplete-minibuffer-setup . icomplete-vertical-mode))

Since Fido mode is based on Icomplete mode, no further setup is needed.

Voilà!

Today was all about setting up Emacs for OCaml development.

Tuareg

Tuareg seem to be the goto package for OCaml development. Installing it was easy enough.

;; Enable Tuareg (Emacs OCaml mode).
;; See https://github.com/ocaml/tuareg
(use-package tuareg
  :defer t)

Unfortunately, I ran into some issues that needed fixing. The $PATH I had configured in my shell didn't match the environment the Emacs knew about. Emacs couldn't locate asdf binaries, nor my opam binaries.

Thankfully, I found the excellent exec-path-from-shell package that sorted this out.

;; Enable exec-path-from-shell (Make Emacs use the $PATH set up by the user's shell)
;; See https://github.com/purcell/exec-path-from-shell
(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :config
  (exec-path-from-shell-initialize))

Merlin

Merlin is an editor service that provides modern IDE features for OCaml. It was super easy to setup.

(use-package merlin
  :hook (tuareg-mode . merlin-mode))

This will enable merlin-mode whenever the tuareg-mode-hook was called.

ocp-indent

ocp-indent is an indentation tool for OCaml. It comes with support for Emacs out of the box. Setting it up was as easy as setting up Merlin.

(use-package ocp-indent
  :hook (tuareg-mode . ocp-setup-indent)))

Voila!

TextMate is my editor of choice. Previously, I used vim for almost ten years. Now I've decided to try Emacs for 30 days.

Installation

I'm on macOS, so installing Emacs was painless using Homebrew (Sorry, nix users):

$ brew install --cask emacs
$ emacs --version
GNU Emacs 27.2
Copyright (C) 2021 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

First Steps

I decided to go through the Emacs tutorial. It was as easy as pressing C-h t. After finishing the tutorial, I decided to cleanup the UI a little bit.

Excerpt from my $HOME/.emacs.d/init.el file:

;; Configure font
(set-frame-font "DejaVu Sans Mono-14" nil t)

;; Inhibit the startup screen.
(setq inhibit-startup-screen t)

;; Disable tool-bar mode
(tool-bar-mode -1)

;; Disable menu-bar mode
(menu-bar-mode -1)

;; Disable scroll-bar mode
(scroll-bar-mode -1)

Much better! The only missing piece was replacing the default theme. A quick DuckDuckGo search led me to the Zenburn theme.

Package Management

To use the Zenburn theme I had to install the package. I knew for a fact that I would be installing more packages as I progress, so I decided to spend some time on a proper setup. The result? A use-package based setup:

Excerpt from my $HOME/.emacs.d/init.el file:

;; Configure packages
(setq package-file (expand-file-name "package.el" user-emacs-directory))
(when (file-exists-p package-file)
  (load package-file))

Excerpt from my $HOME/.emacs.d/package.el file:

(require 'package)

;; Do not automatically make installed packages available before
;; reading the init file.
(setq package-enable-at-startup nil)

;; Add the MELPA Stable repository to the list of package archives.
(add-to-list 'package-archives
             '("melpa-stable" . "https://stable.melpa.org/packages/") t)

;; Install the `use-package' package if not already installed.
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

;; Initialize `use-package'.
(eval-when-compile
  (require 'use-package))

;; Treat every package as though it had specified using ‘:ensure SEXP’.
(setq use-package-always-ensure t)

;; Enable the zenburn theme.
;; See https://github.com/bbatsov/zenburn-emacs
(use-package zenburn-theme
  :config
  (load-theme 'zenburn t))

This will automatically load the package.el file from $HOME/.emacs.d/package.el, should it exist. The package.el file is responsible for the heavily lifting. It automatically installs use-package if it's not already installed. The use-package macro allow me to install, load and configure packages in a declarative manner.

Voila! See you tomorrow.

I used to have a bunch of git aliases. git c for git commit, git co for git checkout, git st for git status, and many others. Today, I have two. Let me tell you why.

I was pairing with a colleague of mine on their computer when I realized that they didn't share the same aliases as I do. My muscle memory kept on kicking in, leaving me staring at a terminal spitting error messages like Snoop Dogg spitting bars.

$ git co
git: 'co' is not a git command. See 'git --help'.

The most similar commands are
	commit
	clone
	log

This served as a valuable lesson. I'd hamstrung myself by making my development environment too specialized. If I only ever can work on my machine, is it really worth it?

This happened five years ago. Since then, I've accumulated exactly two git aliases. git and sync.

git

Sometimes I begin typing out a git command, only to be momentarily interrupted by something or someone. This usually leaves git lingering in my terminal prompt. Once I've dealt with the interruption, I retype the complete git command without thinking. git git status.

$ git git status
git: 'git' is not a git command. See 'git --help'.

The most similar command is
	init

This happened often enough, I decided to do something about it.

$ git config --global alias.git '!git'
$ git git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

sync

I contribute to various projects from time to time. My forks usually have two remotes, origin pointing to my fork of the original repository and upstream pointing to the original repository itself. Keeping up-to-date with upstream usually requires me to type out these commands.

$ git fetch upstream
$ git rebase upstream/master

This was fine when I just had a couple of forks and there wasn't a lot of activity. However, over time, I found myself typing this over and over again. Sometimes, I'd have to remember to stash my changes first, and then reapply them once I'm done.

This eventually led to me to the following alias.

$ git config --global alias.sync '!f() { [ "$#" -ne 1 ] && echo refname required >&2 || { git fetch "${1%%/*}" && git rebase -r --autostash "$1"; } }; f'
$ git sync upstream/master
Created autostash: 18565e3
Current branch master is up to date.
Applied autostash.

This will fetch whatever <refname> you passed to git sync, then rebase your commits onto <refname> (preserving merge commits if any). Any uncommited changes will automatically be stashed in a temporary stash entry and then reapplied automatically once the rebase is done.

Voilà!

Crystal is what I spend most of my spare time doing right now. I'm also a fan of Ctags, but unfortunately it doesn't have a parser for Crystal by default. I did a quick search on the Internet but came up short. The only Ctags Regex rules I could find was for Exuberant Ctags, which isn't compatible with my version of Universal Ctags.

Fortunately, there is great documentation on how to add your own Regex parser. So down the rabbit whole I went, and came up with the following parser:

--langdef=crystal

--map-crystal=.cr

--kinddef-crystal=a,macro,macros
--kinddef-crystal=c,class,classes
--kinddef-crystal=m,method,methods
--kinddef-crystal=M,module,modules
--kinddef-crystal=s,struct,structs

--regex-crystal=/^[ \t]*(private[ \t]+)?(abstract[ \t]+)?class[ \t]+([A-Z][A-Za-z0-9_]*::)*([A-Z][A-Za-z0-9_]*)/\4/c/{scope=push}
--regex-crystal=/^[ \t]*(private[ \t]+)?(abstract[ \t]+)?struct[ \t]+([A-Z][A-Za-z0-9_]*::)*([A-Z][A-Za-z0-9_]*)/\4/s/{scope=push}
--regex-crystal=/^[ \t]*(private[ \t]+)?macro[ \t]+([a-z_][a-zA-Z0-9_?!]+)/\2/a/{scope=push}
--regex-crystal=/^[ \t]*(private[ \t]+)?module[ \t]+([A-Z][A-Za-z0-9_]*::)*([A-Z][A-Za-z0-9_]*)/\3/M/{scope=push}
--regex-crystal=/^[ \t]*(private[ \t]+|protected[ \t]+)?(abstract[ \t]+)?def[ \t]+([a-z_][a-zA-Z0-9_?!]+)/\3/m/{scope=push}
--regex-crystal=/^[ \t]*end///{scope=pop}{placeholder}

It supports class, def, macro, module and struct definitions.

Put the above in $HOME/.config/ctags/crystal.ctags or any of the other locations CTags looks in by default.

Voilà!

The latest project I'm working on, written in Ruby on Rails, is run completely in Docker; from development to production. As a practitioner of TDD I rely heavily on the Ruby bundle to run my tests. Out of the box, it assumes your development environment to be on your local machine but with a bit of tinkering we can change those assumptions.

The Ruby bundle will try to find a Ruby binary within $PATH, however, by setting $TM_RUBY we can tell TextMate the exact location of the Ruby binary we want to use.

We create a new directory, .bin, within the root of our project. Within it we place a small Ruby binary wrapper named ruby.

#!/usr/bin/env bash
docker-compose run --rm -v "$RUBYLIB:/usr/src/textmate" web ruby -I/usr/src/textmate "${@//$PWD//usr/src/app}"

We also ensure it's executable, chmod +x ./.bin/ruby.

There are a few things going on in the wrapper above. The Ruby bundle relies on a few supporting files for parsing the test output before presentation. These files are shipped within the Ruby bundle itself and thus not exposed to our Docker container. Fortunately, $RUBYLIB points to the location of these files, allowing us to mount the folder as we spin up a new container. Just mounting the folder alone won't do much. We also have tell Ruby to include the newly mounted folder in its $LOAD_PATH.

The next problem is that any file path provided as an argument to the wrapper will be from the host's perspective, not the Docker container. E.g., on my machine the project is located at /Users/kevinsjoberg/code/my-project whereas in Docker it's located at /usr/src/app. We solve this using Parameter substitution; replacing all occurrences of our working directory with /usr/src/app.

Now all we have to do is tell the Ruby bundle about our Ruby binary wrapper. We create a .tm_properties file within the project root with the following contents.

TM_RUBY = "$CWD/.bin/ruby"

Voilà!

Enter your email to subscribe to updates.