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

From: Liliana Marie Prikler <[email-masked]>
Date: Tue, 29 Aug 2023 06:24:03 +0200

Am Montag, dem 28.08.2023 um 18:27 -0400 schrieb Kierin Bell:
> > These general improvements should perhaps also been given their own
> > patch(es).  Also, since read-print is used in guix style, I'd be
> > interested in seeing how the output improves from your changes.  Do
> > you have easy comparisons?
> > 
> 
> So when ready, I will open a new issue and send a patch series as per
> the manual.
No need, you can reuse this one.

> I'll also explore opening a separate issue for the "general
> improvements" to (guix read-print) that are not strictly part of the
> Elisp serialization functionality.  I'll try to find a way to clearly
> annotate that patch with examples of each change and how it affects
> output.
Same here, just use --reroll-count=2 or -v 2 on git send-email.

> Many of the changes I'm calling "general improvements" seem to affect
> Elisp output more than Scheme.  E.g., improper lists and alists
> aren't used as extensively in Scheme, none of the defined %SPECIAL-
> FORMS for Scheme accept list arguments in the right places or empty
> bodies, etc.
> 
> But you make a good point re: guix style.  I managed to contrive an
> example package definition that demonstrates most of the changes.
> 
> Here is the output of guix style without the patch:
> 
> --8<---------------cut here---------------start------------->8---
> (define-public foo
>   (package
>     (name "foo")
>     ;; ...
>     (arguments
>      (list
>       ;; *** (1) ***
>       #:make-flags #~(list "VERBOSE=1"
>                            #~more-flags "..."
>                            #~(long-gexp-that-would-protrude-beyond-
> max-width))
>       #:phases #~(modify-phases %standard-phases
>                    (add-after 'install 'foo-fix
>                      (lambda _
>                        (substitute* "some-file"
>                          (("match1")
>                           ;; *** (2) ***
>                           (string-join (list
>                                         "list would protrude if not
> preceded by newline")))
>                          (("match2")
>                           "replacement")))))))
>     ;; *** (3) ***
>     (inputs `(,bar ,baz
>               ,quux
>               ,quuux
>               ,quuuux
>               ,quuuuux))
>     (native-search-paths
>      (list (search-path-specification
>             (variable "FOO-PATH")
>             (files '("foo-dir"))
>             ;; *** (4) ***
>             (file-pattern
>                           "^string\\ with\\ backlashes\\ that\\
> would\\ protrude$")
>             (file-type 'regular))))
>     (properties '((tunable? . #t)
>                   ;; *** (5) ***
>                   (upstream-name . "foo-with-a-long-upstream-name-
> that-would-protrude")))
>     ;; ...
>     (license gpl3+)))
> --8<---------------cut here---------------end--------------->8---
> 
> 
> ...And here it is with the patch:
> 
> --8<---------------cut here---------------start------------->8---
> (define-public foo
>   (package
>     (name "foo")
>     ;; ...
>     (arguments
>      (list
>       ;; (1) No newline before special read syntaxes when they would
> not
>       ;;     protrude beyond MAX-WIDTH.
>       ;;
>       ;;     [ Only relevant where a special read syntax occurs after
>       ;;     the first argument in a function call and is not
> preceded
>       ;;     by a keyword. ]
>       #:make-flags #~(list "VERBOSE=1" #~more-flags "..."
>                            #~(long-gexp-that-would-protrude-beyond-
> max-width))
>       #:phases #~(modify-phases %standard-phases
>                    (add-after 'install 'foo-fix
>                      (lambda _
>                        (substitute* "some-file"
>                          (("match1")
>                           ;; (2) Newline and proper indentation
> before
>                           ;;     first argument of function call when
>                           ;;     it would protrude beyond MAX-WIDTH.
>                           ;;
>                           ;;     [ Only relevant when first argument
>                           ;;     of function call is a list that has
>                           ;;     elements that would protrude beyond
>                           ;;     MAX-WIDTH. ]
>                           (string-join
>                            (list
>                             "list would protrude if not preceded by
> newline")))
>                          ;; XXX: Should there be a newline after
>                          ;; `("match2")'?  In Elisp, newlines like
>                          ;; that seemed to get annoying, but perhaps
>                          ;; it would actually be better here.
>                          (("match2") "replacement")))))))
>     ;; (3) Quoted lists longer than LONG-LIST with second element on
>     ;;     its own line, like the remaining elements.
>     ;;
>     ;;     [ Fixes an obvious bug. ]
>     (inputs `(,bar
>               ,baz
>               ,quux
>               ,quuux
>               ,quuuux
>               ,quuuuux))
>     (native-search-paths
>      (list (search-path-specification
>             (variable "FOO-PATH")
>             (files '("foo-dir"))
>             ;; (4) Newline and proper indentation before string with
>             ;;     backslashes that would protrude.
>             ;;
>             ;;    [ Fixes obvious bug --- backslashes must be
>             ;;    accounted for in strings to avoid weird issues. ]
>             (file-pattern
>              "^string\\ with\\ backlashes\\ that\\ would\\
> protrude$")
>             (file-type 'regular))))
>     (properties '((tunable? . #t)
>                   ;; (5) Newline before the dot and end of improper
> lists.
>                   (upstream-name
>                    . "foo-with-a-long-upstream-name-that-would-
> protrude")))
>     ;; ...
>     (license gpl3+)))
> --8<---------------cut here---------------end--------------->8---
> 
> ...Again, these improvements become much more important in a 2k line
> Elisp init file.
I'd imagine, but there are some good things in it for (guix style) too.
Some bugs still remain, like not splitting after #:phases to keep long
lines in check, but that's beyond the scope.

> > > ---
> > > 
> > > 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.
> > > 
> > > 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).
> > > 
> > > * 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.
> > > 
> > > * 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.
> > > 
> > > * 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.
> > > 
> > > * 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.
> > > 
> > > * A utility function has been provided for generating the
> > > aforementioned
> > >   Scheme records from an existing Emacs init file:
> > >   `elisp-file->home-emacs-configuration'.
> > > 
> > > Here's an example configuration for the `home-emacs-service-type'
> > > demonstrating some of these features:
> > > 
> > > --8<---------------cut here---------------start------------->8---
> > > (use-modules (gnu home)
> > >              (gnu services)
> > >              (guix gexp)
> > >              (gnu home services)
> > >              (gnu home services emacs)
> > >              (gnu packages emacs-xyz)
> > >              (gnu packages file)
> > >              (gnu packages compression))
> > > 
> > > (define %my-function-name 'my--compose-mail)
> > > 
> > > (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))))
> > I assume that each elisp or #% only handles a single expression, am
> > I correct?  Or do we also have (elisp a b) and #%@(a b)?
> > 
> 
> Yes, `elisp' and `#%' are just like `gexp' and `#~'.  `(elisp a b)'
> would be a syntax error.  (And #%#$@(a b) is interesting; hadn't
> tried that one :) --- but it doesn't work.)
> 
> I plan on adding a convenience macro `elisp*' so that (elisp* a b)
> expands to (list (elisp a) (elisp b)).  This would make, e.g., the
> above `elisp-file' invocation much nicer.
SGTM.

Cheers