Package my emacs snippets

I’d like to create a flake of my emacs distribution (easy to start from another computer, to configure multiple users with it, to easily pin dependencies, to add in home manager…). For this reason I would like to create a package of my custom yasnippet that looks like:

$ cat snippets/latex-mode/frac.yasnippet
# -*- mode: snippet -*-
# name: frac
# key: f
# --
\frac{$1}{$2} $0

What is the procedure to follow? (I am new in emacs packaging in both nix and pure emacs) I also saw that there are many ways to build a package: emacs28Packages.trivialBuild, emacs28Packages.elpaBuild… When should I use what?

1 Like

Personally, I would just create a module and add it to a homeManagerModules output, using xdg.configFile (or home.file if you haven’t migrated to the XDG dir yet) to symlink it to the right place. This makes much more sense for user config files.

If I was making a flake for my emacs config I would probably create a full emacs package in this flake too, and make it pre-load my config with a wrapper.

Packaging a yasnippet isn’t trivial, you’d have to write some code to add it to the yasnippet load path and autoload it, something like this project and then build it with one of those commands, I believe the trivialBuild one since you won’t be downloading from elpa.

2 Likes

Ok thanks a lot! I wanted to avoid home.file as it would not work for flakes and could mess-up with an existing emacs install. I’ll try to see how far I can get with trivialBuild, thanks!

:confused: Why would that not work for flakes?

1 Like

It works just fine with flakes, I use it in mine. I’d wager there were some misunderstandings :slight_smile:

That’s a more legitimate concern though, hence I suggested the emacs wrapper that could load both a local install and your config from packages. That way you can install your config on a system properly managed with home-manager and also run it using nix run when there is no home-manager.

Overengineered, for sure, but this actually seems like a pretty sweet setup. Might steal my idea for my own config at some point.

Hum with home.file can you still use nix run to start emacs? But yes in any case it will collide with the system installed by the user so I’d love to avoid that.

Let’s see… home.file, if you use the home-manager nixos module, will symlink whatever files you ask it to to the specified user’s home directory path. This is pure and declarative, and hence completely permitted in the nixosConfiguration of flakes even without --impure.

nix run won’t start emacs. By default, it will look at your current working directory, and if it or any of its parent directories contain a flake.nix it will look for an apps.${system}.default output and if it exists run it. If this happens to point to emacs, it will start emacs.

nix run nixpkgs#emacs will start emacs, but it will look up emacs in the nixpkgs flake, which is by default downloaded from github when you run that command.

Neither of those nix run commands at all care whether you use home.file in any way. They don’t care whether you even have a nixosConfiguration in your flake outputs, since they don’t interact with it at all.

The emacs that either of those commands may launch will behave normally, so if you used home.file to install your emacs config, such an emacs instance will load whatever you asked home.file to create, because it will just be a symlink to an emacs config in e.g. ~/.config/emacs.

Does that clear it up?

That’s a bit trickier, and depends a bit on what you want to do. But you could create your own packages.${system}.emacs, and do something like this:

packages.${system}.emacs = pkgs.emacs.override {
  siteStart = ./site-start.el;
};

And then create a file similar to the one in nixpkgs in site-start.el, except configure it to also load your yasnippet and other configuration.

That way you can basically have your own emacs distribution, and run it on any Linux distro just by running nix run github:myuser/myflake#emacs, and it will come with your yasnippet preinstalled and ready to go, but also execute the user’s local configuration as normal.

Which is kind of a cool idea!

The much more reasonable thing to do is just to move your emacs config into home manager, installing the whole config with home.file, and assume that there is no existing emacs configuration :slight_smile:

If there is one, home-manager will error out and tell you so, by the way, so you will always have a chance to back it up before you overwrite something.

1 Like

Thanks a lot for the clarification!

I’m not sure to undertand why I would need this site-start.el: since home manager already provides the default.el library (loaded after site-start.el and the init.el file of the user) I guess I could load yasnippet from there directly right? Also, I was thinking that I could use a wrapper that calls emacs with

emacs -q -l default

in order to only load the library without loading the user’s configuration (useful if I want my instance to be more independent from the instance of the user).

This is in case you wanted a fully independent emacs package that will have your configuration without home-manager or NixOS, or actually any user configuration on the host. This’d give you a customized emacs even when running with emacs -q, similar to your idea but using the default site-start.el instead of -l default.

But yes, just using home-manager is more reasonable :wink:

Oh I see, it is true that the config is only available in home manager… But I guess one can easily turn it into a regular package using basically the code of home manager:

userConfig = epkgs.trivialBuild {
  pname = "default";
  src = pkgs.writeText "default.el" cfg.extraConfig;
  packageRequires = packages;
};

(to insert into ((emacsPackagesFor emacsPgtkGcc).emacsWithPackages (epkgs: let userConfig = …; in [ userConfig ]))).

And I guess it’s slightly easier to use as we don’t need to copy/paste the site-start.el.

Anyway, I finished the packaging, it was not that hard to do finally.

So first create a folder my-snippets and a file my-snippets/my-snippets.el containing this code (basically copy/pasted/renamed from yasnippet-snippets, it basically checks the path of the current file to obtain the path of the snippets and then it say to yasnippet where to find the snippets):

;;; my-snippets.el --- Collection of yasnippet snippets

;;; Code:

;; Eavily inspired by https://github.com/AndreaCrotti/yasnippet-snippets/blob/cd665c9cba4bab646f6d50ac098bee63573a4ca5/yasnippet-snippets.el

(require 'yasnippet)

;; Points to the current directory
(defconst my-snippets-dir
  (expand-file-name
   "snippets"
   (file-name-directory
    ;; Copied from  ‘f-this-file’ from f.el.
    (cond
     (load-in-progress load-file-name)
     ((and (boundp 'byte-compile-current-file) byte-compile-current-file)
      byte-compile-current-file)
     (:else (buffer-file-name))))))

;;;###autoload
(defun my-snippets-initialize ()
  "Load the `my-snippets' snippets directory."
  ;; NOTE: we add the symbol `my-snippets-dir' rather than its
  ;; value, so that yasnippet will automatically find the directory
  ;; after this package is updated (i.e., moves directory).
  (add-to-list 'yas-snippet-dirs 'my-snippets-dir t)
  (yas--load-snippet-dirs))

;;;###autoload
(eval-after-load 'yasnippet
  '(my-snippets-initialize))


(provide 'my-snippets)

Then put all your snippets in my-snippets/snippets/NAME_MODE/NAME_SNIPPET like:

$ cat my-snippets/snippets/latex-mode/f
# -*- mode: snippet -*-
# name: f
# key: f
# --
\frac{$1}{$2} $0

You also need to load your newly created library in your config (the one that you feed to extraConfig):

(use-package my-snippets)

And finally to package your snippets just use the trivialBuild that automatically compiles the files for you (I needed to add a preInstall hook to install also the snippet folder as by default only the lisp files are installed):

{
    # …
    program.emacs.extraPackages = (epkgs: with epkgs;
      let
        my-snippets = epkgs.trivialBuild {
          pname = "my-snippets";
          version = "1.0";
          src = ./my-snippets;
          packageRequires = [ yasnippet ];
          
          preInstall = ''
            LISPDIR=$out/share/emacs/site-lisp
            mkdir -p $LISPDIR
            cp -r snippets $LISPDIR
          '';
          meta.description = "My snippets are now packaged, easier to deploy an other machines and avoid conflicts with user config!";
        };
      in [
        my-snippets
      ];
}

Enjoy! And thanks a lot tlater for all the help!

5 Likes