Subject: [bug#64620] [PATCH] gnu: home: Add home-emacs-service-type.

From: Ludovic Courtès <[email-masked]>
Date: Wed, 23 Aug 2023 12:01:51 +0200

Hi Kierin,

This is a truly impressive piece of work!

[email-masked] skribis:

> This patch builds on patches from ( and David Wilson for a
> `home-emacs-service-type' (https://issues.guix.gnu.org/58693,
> https://issues.guix.gnu.org/60753, https://issues.guix.gnu.org/62549).
>
> Many of the features of the prior patches have been included, but the
> major focus here is to configure Emacs in Scheme rather than symlinking
> to existing configuration files.

OK, interesting.  This seems to be one of the main questions: how far
should we go on the Scheme side?

In <https://issues.guix.gnu.org/62549>, unmatched-paren chose to not
generate elisp at all from Scheme.  The advantage is that the
implementation is simpler; as a user, the model one has to have in mind
is also simpler: you’re still configuring most things the traditional
way in elisp.  That’s also its downside: you have to do plumbing on the
elisp side, when Guix Home in some cases would know what to do.

I don’t have the answer and I’m not sure what I’d prefer, but I’m trying
to see the tradeoffs and to map out the design space.

> Here are some of the broad strokes:
>
> * The following record types have been introduced to encapsulate
>   configuration for Emacs: `emacs-configuration' (for general
>   configuration), `emacs-package' (for package-specific configuration),
>   `emacs-keymap' (for configuration of local keymaps), and
>   `emacs-server' (for configuration of Emacs servers).

Why special-case keymaps, of all the things one might one to configure
in Emacs?  I understand it’s one of the first things one may want to
tweak, but then why not add <emacs-theme> as well, etc.; IOW, where do
we draw the line?

> * Most configuration fields are either flat lists or alists that are
>   considerably abstracted from their final serialized Elisp
>   representation, but escape hatches are provided for both pulling in
>   existing configuration files and specifying s-expressions directly.

Are seasoned Emacsers not going to be frustrated because of this?  :-)

They might prefer to have full access to elisp.

> * All serialized Elisp is pretty-printed much how we would expect to see
>   it in Emacs (for example, with proper indentation according to the
>   `lisp-indent-function' symbol property, etc.).  This has been
>   accomplished by adding a new keyword argument to
>   `pretty-print-with-comments' from `(guix read-print)', among other
>   improvements.

Fun.  I’d like to see how we can avoid spreading elisp conditionals in
(guix read-print).

> * Emacs package configuration can either be serialized as `use-package'
>   forms or as equivalent, more minimalist s-expressions.  Users can
>   define their own package serializers, too.
>
> * For specifying s-expressions, an "Elisp expression" syntax has been
>   implemented that is essentially a lighter-weight version G-expressions.
>   (I try to explain why this is helpful in the documentation.)
>
> * A reader extension has been implemented that allows for "Elisp
>   expressions" to be specified directly with Elisp read syntax, and
>   Scheme values (including file-like objects or G-expressions) can in
>   turn be "unquoted" within that Elisp code.  Also, comments and
>   whitespace can be included within the Elisp code via the `#;'
>   (comment), `#>' (newline), and `;^L' (page break) forms.

Great that you’re putting (language elisp parser) to good use!

> * Each Emacs server has its own user init and early init files, which
>   can optionally inherit configuration from the init files used by
>   non-server Emacsen.  Each server can also inherit the "main"
>   `user-emacs-directory', or it can use its own subdirectory.
>
> * The `home-emacs-service-type' can be extended, with subordinate
>   configuration records being merged intelligently when possible.

Very nice.

> * A utility function has been provided for generating the aforementioned
>   Scheme records from an existing Emacs init file:
>   `elisp-file->home-emacs-configuration'.

Neat; perhaps ‘guix home import’ could use it?

> (define %gnus-init-file
>   (elisp-file "gnus.el"
>               (list
>                (elisp (setq gnus-select-method '(nnnil "")))
>                (elisp (setq gnus-secondary-select-methods
>                             '((nnml "")
>                               (nntp "news.gmane.io"))))
>                (elisp (setq mail-sources
>                             '((imap :server "mail.example.net"
>                                     :user "[email-masked]"
>                                     :port 993
>                                     :stream tls))))
>                ;; Elisp reader extension
>                #%(define-key global-map [remap compose-mail] #;comment
>                    '#$%my-function-name nil))))

Could I write:

  #%(progn
      (setq x …)
      (setq y …)
      (define-key …))

?  That would seem nicer.

#%(body …) is short for (elisp body …) right?

[...]

>      (configured-packages
>       (list
>        (emacs-package
>         (name 'windmove)
>         ;; Autoload a function used by `my--display-buffer-down'.
>         (autoloads '(windmove-display-in-direction))
>         (keys-override
>          '(("C-M-<left>" . windmove-left)
>            ("C-M-<right>" . windmove-right)
>            ("C-M-<up>" . windmove-up)
>            ("C-M-<down>" . windmove-down)
>            ("C-x <down>"
>             . my--display-buffer-down)))
>         (keys-local
>          (list
>           (emacs-keymap
>            (name 'windmove-repeat-map)
>            (repeat? #t)
>            (keys '(("<left>" . windmove-left)
>                    ("<right>" . windmove-right)
>                    ("<up>" . windmove-up)
>                    ("<down>" . windmove-down))))))

My first reaction is that I don’t see myself my 2K lines (or a subset
thereof) of .emacs and .gnus in that style.  I can foresee potential
benefits in terms of composability, but the barrier to entry looks too
high.  WDYT?

> Finally, unit tests have been added for the new `(guix read-print)'
> functionality, and for the "Elisp expression" syntax.  I couldn't make
> unit tests for anything that builds derivations serializing Elisp,
> because '%bootstrap-guile' is apparently too old to load `(guix
> read-print)' on the derivation side.  But most of this has gotten quite
> a bit of testing, as all of my personal Emacs config is now generated
> from Scheme.

I think you could write tests using ‘guix home container’ and the host’s
store, similar to what ‘tests/guix-home.sh’ is doing.  We don’t have a
testing strategy for Home services yet, but we should definitely work on
it.

That’s it for my initial feedback.  I hope others in the Home and Emacs
teams will chime in!

Thanks,
Ludo’.