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

From: Kierin Bell <[email-masked]>
Date: Wed, 23 Aug 2023 12:14:57 -0400

Hi Ludo’,

Ludovic Courtès <[email-masked]> writes:

> 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.

My philosophy here, I think, is that we can do both.  ('s approach
likely provides all of the integration between Guix and Emacs that many
would want.  David Wilson's patch goes a step further by providing a
configuration option to change the Emacs user directory, which I think
is a reasonable option to have.  (Testing this, though, it turns out
that changing the Emacs user directory is not so simple, so the
implementation in my patch is more involved).  In any case, as we saw,
Guix Home does need to serialize some Elisp to do this (or to do
anything similar).

So, ideally, we would provide some useful mechanisms for serializing
Elisp, but not go overboard.  It's Scheme, so users can build on things
later if reasonable foundations are there.

We may want to work on simplifying the implementation here by removing
features that add too much complexity.  For example, maybe all of the
fined-tuned controls over how Emacs servers "inherit" configuration
aren't justified given the complexity of the implementation ---
especially given that configuration is done via Scheme records, so users
can duplicate configuration themselves by just reusing records.  (But I
do think it makes sense to at least include a subset of those features.)

>> 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?
>

For `emacs-keymap', I created a record type to avoid having nested
alists like:

'((foo-map . (("C-c a" . foo) ...))
  (bar-map . ...)
  ...)

Also, the `emacs-keymap' record has a `repeat?' field, so it can serve
the purpose of something like use-package's `:repeat-map' keyword.

...I do like the idea of `<emacs-theme>', though...
Just kidding.

>> * 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.
>

I think it probably would be frustrating if it wasn't clear that users
do have access to Elisp via the escape hatch fields.  Maybe these should
be mentioned more prominently in the documentation.

They can also specify Elisp directly by using "Elisp expressions" as
values in some of the alists --- for example, those that set variables
(examples given in the configuration snippets).

>> * 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).
>

I think the Elisp reader extension could be implemented completely on
its own, outside of (guix read-print).  The (guix read-print) pretty
printer is very nice, though, and it seems relatively simple to adapt it
to print Elisp.  Though I'm open to better ideas.

Most of the changes to `pretty-print-with-comments' are actually fixes
that are not Elisp-specific, like preventing newlines where they
definitely don't belong.  Maybe that could go in a separate commit?

>> * 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?
>

I looked into modifying `guix home import', but didn't have time to
figure out exactly how to make that work.

>> (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.
>

I'm thinking about a way to create a "splicing" version of the `elisp'
macro, or something similar, so you could do something like that without
serializing the actual `progn'.

> #%(body …) is short for (elisp body …) right?
>
>
> [...]
>

Yes, they are equivalent. Most users would probably use the reader
extension, unless they want to use Scheme-specific syntax (like `#(...)'
for vectors versus `[...]').

>>      (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?
>

I have about 2K lines of it (a lot of it auto-generated by the import
function).  As for the barrier to entry, I think we should hear more
from others.

In my opinion, the benefits of configuring Emacs with Scheme records
like this go beyond composability.  I think that it is cognitively
easier to manage configuration when it is uniform, and the structure
forces us to be more deliberate about what we include and why.  But
maybe that's more of a philosophical debate.

>> 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.
>

Will look into it.  Testing the `elisp-file' function is important,
because it does all of the serialization.

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

Thanks, I appreciate the feedback!

I have a second version of the patch in the works that fixes 3 cosmetic
issues, in case people noticed: 2 comments are confusing and shouldn't
be there (code changed but comments didn't), and one line of the
documentation in `guix.texi' wasn't properly filled.  The actual code
has been unchanged for 2 months, even with regular use.

Also, the example snippet I gave in the original patch message has an
error:

--8<---------------cut here---------------start------------->8---
       ;; ...
       (emacs-server
        (name "sandbox")
        ;; Server gets its own subdirectory of `user-emacs-directory'
        ;; when inheritance is disabled.
        (inherit-directory? #f)
        ;; Server still inherits configuration from non-server Emacsen
        ;; unless inheritance is explicitly disabled.
        (inherit-init? #f)
        ;; ...
        (default-init
          (emacs-configuration
           (variables
            `(;; ...
              ;; Individualized `user-emacs-directory' gets symlinks
              ;; to all `extra-files' from the `emacs-configuration'
              ;; used by other Emacsen, so the files can still be
              ;; referenced.
              (mail-signature-file
               . ,(elisp (locate-user-emacs-file
                          "signature")))))
              ;; ...
        ))
        ;; ...
        )
--8<---------------cut here---------------end--------------->8---

`inherit-init?' should be set to true. When `inherit-init?' is false,
the `signature' file (which was previously created for the non-server
Emacsen) will not be created automatically in the Emacs server's user
directory.  When it is true, all of the files that Guix Home manages in
the main Emacs user directory are duplicated in the server's user
directory, to ensure that any references to those files in the inherited
configuration are still valid.

...Again, maybe this is one of the confusing features that could be
simplified.

-- 
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3  0D41 D14A 8CD3 2D97 0B36