/posimacs

Emacs + tools in home manager module distribution format

Primary LanguageNixMIT LicenseMIT

Posimacs

./graphics/posimacs-github-banner.svg

Demonstration management of 3rd party dependencies & dotfile integration for Emacs using Nix and Home-Manager.

Maintain all of your dependencies and dotfiles for Emacs, reproducibly, portably.

This style does not require home-manager switch just to pick up changes in your elisp files. It does not rely on Nix to manage elisp packages. Elpaca is used for package locking.

The Big Picture Scheme

Different tools are appropriate for different aspects of propagating configuring for Emacs and your home environment around Emacs.

Elpaca elisp dependency management

Elpaca is used to obtain elisp dependencies and freeze versions into reproducible lock files

Home Manager dotfile integration

Many Emacs extensions like vterm benefit from terminal configuration or the inclusion of files such as graphics and fonts into specific directories. Home manager also can perform systemd integration and start the Emacs server.

A hunk of shell configuration for vterm:

programs.bash.initExtra = ''
  vterm_printf(){
      if [ -n "$TMUX" ]; then
          # Tell tmux to pass the escape sequences through
          # (Source: http://permalink.gmane.org/gmane.comp.terminal-emulators.tmux.user/1324)
          printf "\ePtmux;\e\e]%s\007\e\\" "$1"
      elif [ "''${TERM%%-*}" = "screen" ]; then
          # GNU screen (screen, screen-256color, screen-256color-bce)
          printf "\eP\e]%s\007\e\\" "$1"
      else
          printf "\e]%s\e\\" "$1"
      fi
  }
  ''

Home manager uses a module structure similar to NixOS that makes it easy for many configurations to be declared independently and then get recursively merged together either without caring about each other or with explicit cross-settings behavior.

Posimacs modules are also an example of configuring a program, its shell aliases, systemd units, accompanying files and programs as a fully self-contained declaration for easy distribution among other nix users.

Nix flake expressions for 3rd party dependencies

Nix is really good at managing non-elisp dependencies. Instead of downloading binaries from some CI server that may or may not be compatible or even link correctly, you can build and modify packages for your platform however you need. Nixpkgs has roughly everything and changing any dependency is usually quite straightforward. Public caches are available for many popular packages and most packages from nixpkgs itself will be returned from a cache by default.

Modifying the version of guile scheme available in nixpkgs:

# Don't like something in nixpkgs?  Override it!
guile-3 = pkgs.guile.overrideAttrs( drv: {
  version = "3.0.7";
  src = builtins.fetchurl {
    url = "https://ftp.gnu.org/gnu/guile/guile-3.0.7.tar.xz";
    sha256 = "1dwiwsrpm4f96alfnz6wibq378242z4f16vsxgy1n9r00v3qczgm";
  };
});

About the Included Emacs Configurations

This is not meant to be an excellent distribution of Emacs yet. The practice of distributing hunks of configuration by burying them in git repos is anachronistic. Work is ongoing to update this anachronism to 2003-2004 level technology.

However, using these anachronistic rituals, Posimacs does attempt to maintain a decent setup for:

  • LSP and a Rust Analyzer build included, integration via Rustic
  • Minibuffer completions with Ivy + AMX + Counsel
  • Vterm integration & nix build of emacs-libvterm

Much of my actual setup is unpublished. It’s still unclear which parts will be best as elisp packages or hunks of configuration bound for distribution via some other mechanism. Bear with me.

Installation

There are three parts to implement the full scheme:

  • Setting up home manager and including this repository as a flake input (local development style against a clone is strongly recommended).
  • Loading the posimacs shims into init.el and early-init.el
  • Rehydrating package versions from the elpaca lock file. Optional.

Including Home Manager Module

Clone this repository and symlink it into your ~/.emacs.d directory.

Home manager can use this repo as a flake input. The input contains a nixosModule that you simply pass into your home manager module expression.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-21.11";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";

    # set path to your cloned posimacs repo, which is a nix flake
    posimacs = {
      url = "path:~/my/cloned/posimacs";
      inputs.nixpkgs.follows = "nixpkgs";
    }
  };

  outputs = inputs:
    let
      system = "x86_64-linux";  # or x86_64-darwin etc
      username = "emacslegend"; # change this to your user name
    in {
      homeConfigurations = {
        ${username} = inputs.home-manager.lib.homeManagerConfiguration {
          import username system;
          homeDirectory = "/home/${username}";
          configuration.imports = [
            import ./home.nix { posimacs = inputs.posimacs.homeConfigurations.${system}.default; }
          ];
        };
      };
    };
}

Your home.nix should be a function that accepts the various inputs and returns a module function.

{ posimacs }:
{ pkgs, ... }:

{
  imports = [
    posimacs
  ];

  # Module configuration via options (found in each module.nix file)
  #
  # Use the emacs daemon, enabling `emacsclient` aliases if aliases are turned on
  # services.emacs.enable = true;
  #
  # don't provide alias shortcuts for client commands or EDITOR setting
  # posimacs.aliases = false;
  #
}

Loading Emacs files

In order for posimacs modules to have their lisp files loaded, you can load the shims or do something similar to their contents.

;; load the posimacs early-init shim
(load (expand-file-name "posimacs/posimacs-early-init.el" user-emacs-directory))
;; load the posimacs init shim
(load (expand-file-name "posimacs/posimacs-init.el" user-emacs-directory))

;; Now that you have some basics configured, learn to use ielm or program in
;; buffer with M-x eval-region etc and customize the rest of the owl

Using Elpaca’s Package Lock

Elpaca does provide facilities to lock packages at the recipe level.

You can also freeze and rehydrate versions from a lock file. The versions used at the time of this update are in the lock file distributed with these files. Call elpaca-load-lockfile and point to elpaca-lock.el.

Because these packages are across a whole Emacs session, you will want to maintain your own lock file.

TODO it’s currently not automatic to use a lock file for installation, so versions will float, meaning you could pick up unwanted supply chain contents until you feed the lockfile to Elpaca. Further work is needed on checking how Elpaca behaves when new versions are available.

Home Manager

Tell home manager to update your nix environment

home-manager switch --flake ~/.config/nixpkgs

If the daemon is enabled (default) it will start and, if the ~/.emacs.d/ elpaca cache is cold, elpaca will begin downloading your dependencies and building packages. Connect to the daemon with emacsclient.

Otherwise you can just run emacs to watch the process manually.

Additional OSX Steps

Copy the font files from ~/.nix-profile/share/fonts/ to ~/Library/Fonts/

You will still need to run M-x all-the-icons-install-fonts for icons to begin working. Can’t pre-install them as they are not picked up in ~/Library/Fonts/

Daemon

The daemon is extremely convenient for fast loading and keeping all buffers accessible to all panes by launching clients to a central emacs server. This might not available on OSX yet. On Linux, you can disable it by, in addition to the installation instructions above, setting services.emacs.enable = false; in your home.nix.

Whenever you reload, the daemon will not restart because you might have open files that needs saving and systemd knows nothing about these. Therefore, restart your daemon manually (it prints this instruction after home-manager switch) by running systemctl --user restart emacs.

The Actual Vision

The optimum Nix integration appears to be slightly different than what is currently implemented.

Emacs generated nix profile

Emacs can drive its own nix profile if it wants to, and this can be done to provide 3rd party dependencies with or without home manager integrations.

Emacs generated home manager module

Emacs can also write its own module and have the home.nix import it so that when Emacs makes changes to that module, it can ensure that they are picked up by running the switch command on its own.

Emacs generated .el files

Distributing hunks by copy & pasting configurations from various literate org files around the internet is a form of implicit dependencies that virtually ensures cargo culting & decay. Posimacs is being used to develop a better way.

Maintenance

There’s 2-3 layers of modularization and customization we can use to achieve cooperative customization:

  1. Git branches
  2. Home manager modules
  3. Emacs packages (from a package repo or custom source)
  4. Emacs files in non-package format

Don’t Forget Customize!

You likely don’t need to change a variable setting in this repo. Configure the relevant variable in your custom.el file by using M-x customize or C-h v <variable name> and save it the way you like. If it’s a matter of opinion, we don’t need to fix it in source.

When blocked by a config, try to make it more flexible

If something is in your way, attempt to extract it to a new `.el` file or parameterize it. Maintaining an independent branch may become too painful over time, but could be viable if you are doing local development on posimacs (recommended for faster iteration). If your lisp files grow into a first-class package, of course try to publish it on Melpa or where elpaca can use it from git source. For the last-mile configuration, bare .el files are appropriate.