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

  • Open
  • quality assurance status badge
Details
7 participants
  • fernseed
  • Hilton Chain
  • ???
  • Liliana Marie Prikler
  • Ludovic Courtès
  • (
  • Steve George
Owner
unassigned
Submitted by
fernseed
Severity
normal
F
F
fernseed wrote on 14 Jul 2023 17:12
(address . guix-patches@gnu.org)(name . Kierin Bell)(address . fernseed@fernseed.me)
0173e076aafb6ec389a7ebca5d56b7f4e8a02b6e.1689347338.git.fernseed@fernseed.me
From: Kierin Bell <fernseed@fernseed.me>

* gnu/home/services/emacs.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add new file.
* tests/home/services/emacs.scm: New tests file.
* Makefile.am (SCM_TESTS): Add new tests file.
* doc/guix.texi (Emacs Home Services): New node.
* guix/read-print.scm (read-with-comments, read-with-comments/sequence):
Add new ELISP? and UNELISP-EXTENSIONS? keyword arguments to support
reading Elisp.
(%newline-forms): Add `home-emacs-configuration'.
(%elisp-special-forms, %elisp-natural-whitespace-string-forms)
(%elisp-special-symbol-chars, %elisp-confusable-number-symbols)
(%elisp-basic-chars, %elisp-simple-escape-chars): New variables.
(special-form-lead, printed-string, symbol->display-string): Add new
ELISP? keyword argument.
(atom->elisp-string): New helper function.
(pretty-print-with-comments): New ELISP? and SPECIAL-FORMS keyword
arguments to support serialization to Elisp. General improvements:
enable pretty-printing of alists and improper lists; only print lists of
constants with one element per line when length exceeds LONG-LIST; do
not print newline before special read syntax forms (e.g., `'', `#~',
etc.) unless they would exceed MAX-WIDTH; include backslashes when
calculating whether a string would exceed MAX-WIDTH; do not print
extraneous newline when special form has an empty body; print newlines
after list arguments of special forms; print first argument after
function on newline with same indentation as function when it would
exceed MAX-WIDTH.
* tests/read-print.scm: Add new tests and update old tests which fail
due to improvements.
---

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:

Toggle snippet (188 lines)
(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 "user@example.net"
:port 993
:stream tls))))
;; Elisp reader extension
#%(define-key global-map [remap compose-mail] #;comment
'#$%my-function-name nil))))

(home-environment
;; ...
(services
(list
;; ...
(service
home-emacs-service-type
(home-emacs-configuration
(user-emacs-directory "~/.local/state/emacs/")
(package-serializer %emacs-use-package-serializer)
(default-init
(emacs-configuration
;; File-likes specified here symlinked in ~/.config/emacs and
;; loaded when Emacs starts.
(extra-init-files
`(("extra.el"
. ,(local-file "extra.el"))))
(variables
'((initial-scratch-message . #f)
;; Symbols values for variables quoted when serialized.
(confirm-kill-emacs . y-or-n-p)
;; Boolean values for variables serialized properly in Elisp.
(visible-bell . #t)
;; Elisp expressions serialized as-is, with no quoting.
(message-signature-file
. ,(elisp mail-signature-file))))
(modes
'((tool-bar-mode . #f)
(menu-bar-mode . #f)
(fringe-mode . 16)
(repeat-mode . #t)))
(keys
'(("C-x C-b" . ibuffer)))
(keys-override
'(("M-<up>" . scroll-down-line)
("M-<down>" . scroll-up-line)
("C-M-S-<up>"
. my--scroll-other-window-down)
("C-M-S-<down>"
. my--scroll-other-window)))
(extra-init
(list
(elisp (defun my--scroll-other-window-down ()
(interactive)
(scroll-other-window-down 1)))
(elisp (defun my--scroll-other-window ()
(interactive)
(scroll-other-window 1)))))))
(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))))))
(extra-init
(list
(elisp
(defun my--display-buffer-down (&optional arg buf)
(interactive
"P\nbSwitch to buffer in window below: ")
(windmove-display-in-direction 'down arg)
(switch-to-buffer buf))))))
(emacs-package
(name 'dired)
;; External packages used by Dired
(extra-packages (list file unzip)))))))
(simple-service
'emacs-mail-service
home-emacs-service-type
(home-emacs-extension
(default-init
(emacs-configuration
;; File-likes symlinked into `user-emacs-directory', but not
;; loaded automatically.
(extra-files
`(("gnus.el" . ,%gnus-init-file)
("signature" . ,(local-file "signature"))))
(variables
`((gnus-init-file
. ,(elisp (locate-user-emacs-file "gnus.el")))
(mail-user-agent . gnus-user-agent)
(read-mail-command . gnus)))))
(configured-packages
(list
(emacs-package
(name 'message)
(options
`((message-send-mail-function
. smtpmail-send-it)
(message-signature-file
. ,(elisp (locate-user-emacs-file
"signature"))))))))
(servers
(list
;; Servers inherit `user-emacs-directory' and init file
;; configuration from non-server Emacsen by default.
(emacs-server
(name "mail")
(default-init
(emacs-configuration
(extra-init
(list
(elisp (add-hook 'server-after-make-frame-hook
(function gnus))))))))))))
(simple-service
'emacs-sandbox-service
home-emacs-service-type
(home-emacs-extension
(servers
(list
(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)
(inherit-configured-packages? #f)
;; Server is started via a Shepherd service automatically,
;; unless disabled.
(auto-start? #f)
(default-init
(emacs-configuration
(variables
`((initial-scratch-message . #f)
;; 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")))))
(extra-init (list (elisp (ding))))))
(configured-packages
(list
;; Configure a theme specifically for the "sandbox" server.
(emacs-package
(name 'modus-themes)
(package emacs-modus-themes)
(extra-init
(list
(elisp (load-theme 'modus-operandi-tinted)))))))))))))))

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.

The patch is to the point where I'd like to get some feedback, and see
if this is something that could be included into Guix.

Makefile.am | 2 +
doc/guix.texi | 1178 +++++++++++++
gnu/home/services/emacs.scm | 3040 +++++++++++++++++++++++++++++++++
gnu/local.mk | 2 +
guix/read-print.scm | 995 +++++++++--
tests/home/services/emacs.scm | 345 ++++
tests/read-print.scm | 239 ++-
7 files changed, 5654 insertions(+), 147 deletions(-)
create mode 100644 gnu/home/services/emacs.scm
create mode 100644 tests/home/services/emacs.scm

Toggle diff (173 lines)
diff --git a/Makefile.am b/Makefile.am
index a386e6033c..7b5c67e26b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,6 +17,7 @@
# Copyright © 2020, 2021, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com>
# Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
# Copyright © 2021 Andrew Tropin <andrew@trop.in>
+# Copyright © 2023 Kierin Bell <fernseed@fernseed.me>
#
# This file is part of GNU Guix.
#
@@ -524,6 +525,7 @@ SCM_TESTS = \
tests/hackage.scm \
tests/home-import.scm \
tests/home-services.scm \
+ tests/home/services/emacs.scm \
tests/http-client.scm \
tests/import-git.scm \
tests/import-github.scm \
diff --git a/doc/guix.texi b/doc/guix.texi
index 9af1b4417b..f1958cc695 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -116,6 +116,7 @@
Copyright @copyright{} 2023 Karl Hallsby@*
Copyright @copyright{} 2023 Nathaniel Nicandro@*
Copyright @copyright{} 2023 Tanguy Le Carrour@*
+Copyright @copyright{} 2023 Kierin Bell@*
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -440,6 +441,7 @@ Top
* SSH: Secure Shell. Setting up the secure shell client.
* GPG: GNU Privacy Guard. Setting up GPG and related tools.
* Desktop: Desktop Home Services. Services for graphical environments.
+* Emacs: Emacs Home Services. Services for configuring Emacs.
* Guix: Guix Home Services. Services for Guix.
* Fonts: Fonts Home Services. Services for managing User's fonts.
* Sound: Sound Home Services. Dealing with audio.
@@ -42593,6 +42595,7 @@ Home Services
* SSH: Secure Shell. Setting up the secure shell client.
* GPG: GNU Privacy Guard. Setting up GPG and related tools.
* Desktop: Desktop Home Services. Services for graphical environments.
+* Emacs: Emacs Home Services. Services for configuring Emacs.
* Guix: Guix Home Services. Services for Guix.
* Fonts: Fonts Home Services. Services for managing User's fonts.
* Sound: Sound Home Services. Dealing with audio.
@@ -43819,6 +43822,1181 @@ Desktop Home Services
@end table
@end deftp
+@node Emacs Home Services
+@subsection Emacs Home Services
+
+The @code{(gnu home services emacs)} module provides services for
+configuring the GNU Emacs extensible text editor.
+
+@cindex Elisp expressions, for Emacs home services
+Emacs is configured by providing @dfn{initialization files} that contain
+@dfn{s-expressions} written in @dfn{Emacs Lisp} (abbreviated as
+@dfn{Elisp}) which are evaluated when Emacs is started (@pxref{Init
+File,,, emacs, The GNU Emacs Manual}).
+
+The main home service type for configuring Emacs, the
+@code{home-emacs-service-type} (see below), provides three ways to
+specify expressions for Emacs initialization files:
+
+@itemize
+@item
+File-like objects that contain Elisp can be directly referenced so that
+Emacs will evaluate their contents upon initialization (see also
+@code{elisp-file} below, which specifically creates Emacs Lisp files).
+
+@item
+Expressions can be written in Scheme using a special syntax (see the
+@code{elisp} form below), so that they will be serialized to Emacs
+initialization files---with some minor transformations---as Elisp. This
+is possible because Scheme and Emacs Lisp have very similar read
+syntaxes.
+
+@item
+Finally, some configuration fields provide an additional layer of
+abstraction that transforms Scheme values into more complex Elisp
+expressions that do meaningful things with those values.
+@end itemize
+
+For the latter two options, the @code{(gnu home services emacs)} module
+introduces a mechanism for explicitly specifying an s-expression that
+should be serialized as Elisp and evaluated by Emacs: @dfn{Elisp
+expressions}. Elisp expressions have their own data type (see
+@code{elisp?}), and they must be created by using the @code{elisp} or
+@code{#%} forms (see below), or by using other functions provided by the
+module for constructing them. Whenever the term ``Elisp expression''
+occurs in the documentation for the Emacs home service, it is an
+indication that Elisp expressions of this type should be used or that
+they have a special meaning compared with other Scheme values like
+symbols or lists.
+
+To illustrate why we would need to use Elisp expression objects when
+configuring Emacs instead of simply writing s-expressions as we normally
+would in Scheme, consider the @code{variables} field of the
+@code{emacs-configuration} record type, an association list that
+associates Emacs variable names---given as Scheme symbols---with values.
+
+When this field is serialized to the Emacs user initialization file,
+Elisp expressions that set the variables to their corresponding values
+are generated from this association list. But there is a problem: How
+do we differentiate values that should be serialized as constants
+---e.g., by using the @code{quote} syntax---from s-expressions that
+should be @emph{evaluated} by Emacs? If a given value is of a Scheme
+data type that corresponds to a self-quoting data type in Elisp---for
+example, a number or a string---then there is no ambiguity. But what if
+a symbol or a list value is given? Should it be interpreted by Emacs as
+a quoted constant, or should it be interpreted as an unquoted
+s-expression to be evaluated at initialization time?
+
+The solution chosen here is that all values of fields for which this
+ambiguity exists are serialized to Elisp as constants, unless an Elisp
+expression is explicitly used. Whenever explicit Elisp expressions
+occur in the configuration for a service, though, we can be sure that
+they will be serialized directly to Emacs initialization files as
+s-expressions that will be evaluated by Emacs.
+
+This is best illustrated by an example. The following configuration
+produces Elisp code that sets the @code{message-signature-file} Emacs
+variable to the value of the @code{mail-signature-file} variable when
+Emacs is initialized:
+
+@lisp
+(emacs-configuration
+ (variables `((message-signature-file
+ . ,(elisp mail-signature-file)))))
+@end lisp
+
+@noindent
+while the example below sets the @code{message-signature-file} variable to the @emph{symbol} @code{mail-signature-file}, which is not what we want:
+
+@lisp
+(emacs-configuration
+ (variables '((message-signature-file
+ . mail-signature-file))))
+@end lisp
+
+Additionally, Elisp expressions can be specified using the @code{#%}
+form, which allows for Elisp code to be embedded within Scheme (see
+documemtation below for more). The following is equivalent to the first
+example above:
+
+@lisp
+(emacs-configuration
+ (variables `((message-signature-file
+ . ,#%mail-signature-file)))))
+@end lisp
+
+In many ways, Elisp expressions are similar to G-expressions
+(@pxref{G-Expressions}). Elisp expressions can in fact be thought of as
+an abstraction around G-expressions. After all, before any Elisp
+expression can be serialized to a file by a service, it must first be
+transformed into a G-expression so that a derivation can be generated
+(@pxref{Derivations}).
+
+For this reason, any value that is a valid input for a G-expression can
+be referenced within an Elisp expression (see @code{unelisp} and
+@code{unelisp-splicing} below). Data types that ``compile'' and are
+specially substituted in G-expressions, such as file-like objects
+(@pxref{G-Expressions, file-like objects}), will be substituted in the
+same exact way when they are referenced within Elisp expressions. Even
+G-expressions themselves can be embedded within Elisp expressions.
+
+On the other hand, when Elisp expressions are referenced manually within
+G-expressions (e.g., with @code{ungexp}), some of the expressive power
+of Elisp expressions is l
This message was truncated. Download the full message here.
?
(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
878rb8mzpb.fsf@envs.net
fernseed@fernseed.me writes:

Toggle quote (8 lines)
> 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.

Indeed a lot, I'm very interested in this, will play with it for some
time.

Thank you!
L
L
Ludovic Courtès wrote on 23 Aug 2023 12:01
(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
87v8d613hs.fsf@gnu.org
Hi Kierin,

This is a truly impressive piece of work!

fernseed@fernseed.me skribis:

Toggle quote (8 lines)
> 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.

Toggle quote (8 lines)
> 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?

Toggle quote (5 lines)
> * 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.

Toggle quote (7 lines)
> * 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).

Toggle quote (15 lines)
> * 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!

Toggle quote (8 lines)
> * 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.

Toggle quote (4 lines)
> * 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?

Toggle quote (16 lines)
> (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 "user@example.net"
> :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?


[...]

Toggle quote (23 lines)
> (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?

Toggle quote (8 lines)
> 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’.
K
K
Kierin Bell wrote on 23 Aug 2023 18:14
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 64620@debbugs.gnu.org)
87o7ixzqf2.fsf@fernseed.me
Hi Ludo’,

Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (27 lines)
> Hi Kierin,
>
> This is a truly impressive piece of work!
>
> fernseed@fernseed.me 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.)

Toggle quote (14 lines)
>> 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.

Toggle quote (10 lines)
>> * 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).

Toggle quote (11 lines)
>> * 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?

Toggle quote (34 lines)
>> * 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.

Toggle quote (26 lines)
>> (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 "user@example.net"
>> :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'.

Toggle quote (6 lines)
> #%(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 `[...]').

Toggle quote (29 lines)
>> (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.

Toggle quote (14 lines)
>> 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.

Toggle quote (8 lines)
> 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:

Toggle snippet (27 lines)
;; ...
(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")))))
;; ...
))
;; ...
)

`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
H
H
Hilton Chain wrote on 24 Aug 2023 14:26
Re: [bug#64620] [PATCH] gnu: home: Add home-emacs-service-type.
(name . Kierin Bell)(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
87wmxkhbin.wl-hako@ultrarare.space
Hi Kierin,

On Fri, 14 Jul 2023 23:12:31 +0800,
fernseed@fernseed.me wrote:
Toggle quote (33 lines)
>
> From: Kierin Bell <fernseed@fernseed.me>
>
> * gnu/home/services/emacs.scm: New file.
> * gnu/local.mk (GNU_SYSTEM_MODULES): Add new file.
> * tests/home/services/emacs.scm: New tests file.
> * Makefile.am (SCM_TESTS): Add new tests file.
> * doc/guix.texi (Emacs Home Services): New node.
> * guix/read-print.scm (read-with-comments, read-with-comments/sequence):
> Add new ELISP? and UNELISP-EXTENSIONS? keyword arguments to support
> reading Elisp.
> (%newline-forms): Add `home-emacs-configuration'.
> (%elisp-special-forms, %elisp-natural-whitespace-string-forms)
> (%elisp-special-symbol-chars, %elisp-confusable-number-symbols)
> (%elisp-basic-chars, %elisp-simple-escape-chars): New variables.
> (special-form-lead, printed-string, symbol->display-string): Add new
> ELISP? keyword argument.
> (atom->elisp-string): New helper function.
> (pretty-print-with-comments): New ELISP? and SPECIAL-FORMS keyword
> arguments to support serialization to Elisp. General improvements:
> enable pretty-printing of alists and improper lists; only print lists of
> constants with one element per line when length exceeds LONG-LIST; do
> not print newline before special read syntax forms (e.g., `'', `#~',
> etc.) unless they would exceed MAX-WIDTH; include backslashes when
> calculating whether a string would exceed MAX-WIDTH; do not print
> extraneous newline when special form has an empty body; print newlines
> after list arguments of special forms; print first argument after
> function on newline with same indentation as function when it would
> exceed MAX-WIDTH.
> * tests/read-print.scm: Add new tests and update old tests which fail
> due to improvements.
> ---

Great work!

Tried to convert my config to home-emacs-configuration [1], my
original configuration wasn't well written, so the rewrite took a
while. Luckily it doesn't have too many lines. :)

[1]:
K
K
Kierin Bell wrote on 24 Aug 2023 15:13
(name . Hilton Chain)(address . hako@ultrarare.space)(address . 64620@debbugs.gnu.org)
875y54ziqh.fsf@fernseed.me
Hi Hilton,

Hilton Chain <hako@ultrarare.space> writes:

Toggle quote (11 lines)
> Hi Kierin,
> [...]
> Tried to convert my config to home-emacs-configuration [1], my
> original configuration wasn't well written, so the rewrite took a
> while. Luckily it doesn't have too many lines. :)
>
> [1]:
> https://codeberg.org/hako/Testament/src/branch/test-home-emacs/dorphine-home.scm#L452-L858
>
>

It's nice to see this!

What strikes me immediately is that I can easily understand your Emacs
config, because configuration via Scheme records is so consistent and
uniform. I can't usually say that about Emacs init files.

Granted, it does take a long time to get used to the format coming from
Elisp. (And the import function isn't perfect.)

After thinking about what Ludo' said, I'm exploring the possibility of
removing the `configured-packages' fields and `emacs-package' record
type from the core `home-emacs-service-type'.

I could then put this functionality (~1300 lines) in a
`home-emacs-package-configuration-service-type' that extends the Emacs
home service. That could go in a separate channel somewhere, but if it
is upstreamed then we can create a de facto standard that encourages
innovation and collaboration.

A bit of control over how the final serialized Elisp is formatted would
be lost by taking the package configuration functionality out of the
core Emacs home service, but it is not very significant (especially
considering that the point of using the feature would be to configure
Emacs in Scheme). And it seems like there would be benefits to a
modularized approach from usability and implementation standpoints.

Anyway, thanks for testing!

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
(
(address . fernseed@fernseed.me)
87il945gzu.fsf@disroot.org
*Very* nice! I think it would be nice to support a PACKAGES field with
a list of Guix packages to make available to Emacs, though that would
complicate the code a bit.

You'd probably have to do something like what I did[1], which is a
little hacky, and you'd want to somehow redirect the output from
building the Emacs profile to a log file in '$XDG_STATE_HOME/log/emacs',
but it'd let people use native-comp without the hassle of transforming
the packages themselves (with appropriate EMACS and NATIVE-COMP? fields,
of course :)) Also lets you isolate your emacs environment from your
regular environment, if you wanted to do that for... reasons.

If you'd rather not implement that for now, it's okay; I'm very much
willing to submit a followup if/when this is merged.


-- (
K
K
Kierin Bell wrote on 26 Aug 2023 16:06
(name . ()(address . paren@disroot.org)(address . 64620@debbugs.gnu.org)
874jklyk38.fsf@fernseed.me
Hi (,

"(" <paren@disroot.org> writes:

Toggle quote (21 lines)
> *Very* nice! I think it would be nice to support a PACKAGES field with
> a list of Guix packages to make available to Emacs, though that would
> complicate the code a bit.
>
> You'd probably have to do something like what I did[1], which is a
> little hacky, and you'd want to somehow redirect the output from
> building the Emacs profile to a log file in '$XDG_STATE_HOME/log/emacs',
> but it'd let people use native-comp without the hassle of transforming
> the packages themselves (with appropriate EMACS and NATIVE-COMP? fields,
> of course :)) Also lets you isolate your emacs environment from your
> regular environment, if you wanted to do that for... reasons.
>
> If you'd rather not implement that for now, it's okay; I'm very much
> willing to submit a followup if/when this is merged.
>
> [1] https://git.sr.ht/~whereiseveryone/guixrus/tree/master/item/guixrus/home/services/emacs.scm#L113
>
> -- (
>
>

Although there is an EXTRA-PACKAGES field for Guix packages (I use it,
e.g., for font packages) and a CONFIGURED-PACKAGES field specifically
for installing Emacs packages, I do like the idea of having a separate
Emacs profile, or even a separate profile for each Emacs server.

Part of me wanted to find a way to do that more "properly", with some
sort of lower-level `home-emacs-environment-service-type' that sets up
the profiles (hopefully in a more orthodox way), etc. But that turned
out to be even more hacky in the end.

Looking at the Emacs build system modules, I'm not sure exactly how
building separate profiles would allow native-comp without transforming
the package inputs. Isn't the build system basically hard-coded to use
`emacs-minimal' for building Emacs packages (i.e., unless we manually
transform the package inputs)?

At the very least, I'd like to set things up the best I can for this,
even if I end up leaving it to people with more expertise to implement.

Thanks (!

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
L
L
Liliana Marie Prikler wrote on 26 Aug 2023 22:01
Re: [PATCH] gnu: home: Add home-emacs-service-type.
c8846b0bafd00ed0df93fd3ec3aa0cccfaefa90e.camel@gmail.com
Am Freitag, dem 14.07.2023 um 11:12 -0400 schrieb Kierin Bell
<fernseed@fernseed.me>
Toggle quote (5 lines)
>
> * gnu/home/services/emacs.scm: New file.
> * gnu/local.mk (GNU_SYSTEM_MODULES): Add new file.
> * tests/home/services/emacs.scm: New tests file.
> * Makefile.am (SCM_TESTS): Add new tests file.
AFAIK we use "Register" instead of "Add".
Toggle quote (1 lines)
> * doc/guix.texi (Emacs Home Services): New node.
I'd cut this series into two (or more, see below) patches right around
here, putting everything below this line into the first patch(es) and
everything above into the second/nth.
Toggle quote (28 lines)
> * guix/read-print.scm (read-with-comments, read-with-
> comments/sequence):
> Add new ELISP? and UNELISP-EXTENSIONS? keyword arguments to support
> reading Elisp.
> (%newline-forms): Add `home-emacs-configuration'.
> (%elisp-special-forms, %elisp-natural-whitespace-string-forms)
> (%elisp-special-symbol-chars, %elisp-confusable-number-symbols)
> (%elisp-basic-chars, %elisp-simple-escape-chars): New variables.
> (special-form-lead, printed-string, symbol->display-string): Add new
> ELISP? keyword argument.
> (atom->elisp-string): New helper function.
> (pretty-print-with-comments): New ELISP? and SPECIAL-FORMS keyword
> arguments to support serialization to Elisp.  

> General improvements:
> enable pretty-printing of alists and improper lists; only print lists
> of
> constants with one element per line when length exceeds LONG-LIST; do
> not print newline before special read syntax forms (e.g., `'', `#~',
> etc.) unless they would exceed MAX-WIDTH; include backslashes when
> calculating whether a string would exceed MAX-WIDTH; do not print
> extraneous newline when special form has an empty body; print
> newlines
> after list arguments of special forms; print first argument after
> function on newline with same indentation as function when it would
> exceed MAX-WIDTH.
> * tests/read-print.scm: Add new tests and update old tests which fail
> due to improvements.
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?

Toggle quote (95 lines)
> ---
>
> 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 "user@example.net"
>                                     :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)?

Cheers
(
Re: [bug#64620] [PATCH] gnu: home: Add home-emacs-service-type.
(name . Kierin Bell)(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
873503hebs.fsf@disroot.org
Kierin Bell <fernseed@fernseed.me> writes:
Toggle quote (5 lines)
> Although there is an EXTRA-PACKAGES field for Guix packages (I use it,
> e.g., for font packages) and a CONFIGURED-PACKAGES field specifically
> for installing Emacs packages, I do like the idea of having a separate
> Emacs profile, or even a separate profile for each Emacs server.

Oh, nice. That means native-comp could be supported quite easily, just
by transforming the packages. (I meant that easy native-comp was made
possible by a PACKAGES field, not by having an Emacs profile :))

Toggle quote (6 lines)
> Looking at the Emacs build system modules, I'm not sure exactly how
> building separate profiles would allow native-comp without transforming
> the package inputs. Isn't the build system basically hard-coded to use
> `emacs-minimal' for building Emacs packages (i.e., unless we manually
> transform the package inputs)?

Yes, that's what I meant; map a transformation procedure over the Emacs
packages given :)

-- (
L
L
Liliana Marie Prikler wrote on 29 Aug 2023 06:24
Re: [PATCH] gnu: home: Add home-emacs-service-type.
(name . Kierin Bell)(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
6bc35e49d033ff9947a54599b0b83c6a3076fb83.camel@gmail.com
Am Montag, dem 28.08.2023 um 18:27 -0400 schrieb Kierin Bell:
Toggle quote (8 lines)
> > 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.

Toggle quote (5 lines)
> 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.

Toggle quote (136 lines)
> 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.

Toggle quote (119 lines)
> > > ---
> > >
> > > 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 "user@example.net"
> > >                                     :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
K
K
Kierin Bell wrote on 29 Aug 2023 00:27
(name . Liliana Marie Prikler)(address . liliana.prikler@gmail.com)(address . 64620@debbugs.gnu.org)
873502x0oa.fsf@fernseed.me
Liliana Marie Prikler <liliana.prikler@gmail.com> writes:

Toggle quote (9 lines)
> Am Freitag, dem 14.07.2023 um 11:12 -0400 schrieb Kierin Bell
> <fernseed@fernseed.me>
>>
>> * gnu/home/services/emacs.scm: New file.
>> * gnu/local.mk (GNU_SYSTEM_MODULES): Add new file.
>> * tests/home/services/emacs.scm: New tests file.
>> * Makefile.am (SCM_TESTS): Add new tests file.
> AFAIK we use "Register" instead of "Add".

Got it; will use "Register" for changes to gnu/local.mk.

Toggle quote (38 lines)
>> * doc/guix.texi (Emacs Home Services): New node.
> I'd cut this series into two (or more, see below) patches right around
> here, putting everything below this line into the first patch(es) and
> everything above into the second/nth.
>> * guix/read-print.scm (read-with-comments, read-with-
>> comments/sequence):
>> Add new ELISP? and UNELISP-EXTENSIONS? keyword arguments to support
>> reading Elisp.
>> (%newline-forms): Add `home-emacs-configuration'.
>> (%elisp-special-forms, %elisp-natural-whitespace-string-forms)
>> (%elisp-special-symbol-chars, %elisp-confusable-number-symbols)
>> (%elisp-basic-chars, %elisp-simple-escape-chars): New variables.
>> (special-form-lead, printed-string, symbol->display-string): Add new
>> ELISP? keyword argument.
>> (atom->elisp-string): New helper function.
>> (pretty-print-with-comments): New ELISP? and SPECIAL-FORMS keyword
>> arguments to support serialization to Elisp.  
>
>> General improvements:
>> enable pretty-printing of alists and improper lists; only print lists
>> of
>> constants with one element per line when length exceeds LONG-LIST; do
>> not print newline before special read syntax forms (e.g., `'', `#~',
>> etc.) unless they would exceed MAX-WIDTH; include backslashes when
>> calculating whether a string would exceed MAX-WIDTH; do not print
>> extraneous newline when special form has an empty body; print
>> newlines
>> after list arguments of special forms; print first argument after
>> function on newline with same indentation as function when it would
>> exceed MAX-WIDTH.
>> * tests/read-print.scm: Add new tests and update old tests which fail
>> due to improvements.
> 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.

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.

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:

Toggle snippet (42 lines)
(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+)))


...And here it is with the patch:

Toggle snippet (65 lines)
(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+)))

...Again, these improvements become much more important in a 2k line
Elisp init file.

Toggle quote (99 lines)
>> ---
>>
>> 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 "user@example.net"
>>                                     :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.

Thanks!

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
K
K
Kierin Bell wrote on 29 Aug 2023 00:32
Re: [bug#64620] [PATCH] gnu: home: Add home-emacs-service-type.
(name . ()(address . paren@disroot.org)(address . 64620@debbugs.gnu.org)
87y1huvluu.fsf@fernseed.me
"(" <paren@disroot.org> writes:

Toggle quote (23 lines)
> Kierin Bell <fernseed@fernseed.me> writes:
>> Although there is an EXTRA-PACKAGES field for Guix packages (I use it,
>> e.g., for font packages) and a CONFIGURED-PACKAGES field specifically
>> for installing Emacs packages, I do like the idea of having a separate
>> Emacs profile, or even a separate profile for each Emacs server.
>
> Oh, nice. That means native-comp could be supported quite easily, just
> by transforming the packages. (I meant that easy native-comp was made
> possible by a PACKAGES field, not by having an Emacs profile :))
>
>> Looking at the Emacs build system modules, I'm not sure exactly how
>> building separate profiles would allow native-comp without transforming
>> the package inputs. Isn't the build system basically hard-coded to use
>> `emacs-minimal' for building Emacs packages (i.e., unless we manually
>> transform the package inputs)?
>
> Yes, that's what I meant; map a transformation procedure over the Emacs
> packages given :)
>
> -- (
>
>

Ah, got it! The confusion was because I actually did borrow your
NATIVE-COMP? field and package transformation idea. They're already
included in the patch! (We still have to add custom profiles, though
:))

Thanks.

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
(
(name . Kierin Bell)(address . fernseed@fernseed.me)(address . 64620@debbugs.gnu.org)
87o7iqpeou.fsf@disroot.org
Kierin Bell <fernseed@fernseed.me> writes:
Toggle quote (6 lines)
> "(" <paren@disroot.org> writes:
> Ah, got it! The confusion was because I actually did borrow your
> NATIVE-COMP? field and package transformation idea. They're already
> included in the patch! (We still have to add custom profiles, though
> :))

Oops :)

-- (
L
L
Ludovic Courtès wrote on 11 Oct 2023 18:16
(name . Kierin Bell)(address . fernseed@fernseed.me)
87a5spuoc5.fsf@gnu.org
Hello!

If there’s consensus, I think we should go ahead with this patch series.
Worst that could happen is that people will think of ways to change the
service in one way or another, and that’s fine!

Two general comments:

• As I wrote earlier, I think it’d be nice to have integration tests
for this, in addition to the unit tests the patch already adds.

• We may want to split the patch into sizable, self-contained bites.
For instance, the (guix read-print) changes should probably be
separated out.

I’ll provide more specific comments about the code.

To Emacs team members: please review the Emacs bits of the series!

Thanks,
Ludo’.
L
L
Ludovic Courtès wrote on 11 Oct 2023 18:48
(address . fernseed@fernseed.me)
87lec9t890.fsf@gnu.org
So, here is a short review of the parts I’m most familiar with.

fernseed@fernseed.me skribis:


[...]

Toggle quote (3 lines)
> +(define-module (gnu home services emacs)
> + #:use-module (gnu home services)

[...]

Toggle quote (5 lines)
> + #:re-export (blank?
> +
> + vertical-space
> + vertical-space?

Why re-export these things from here? Sounds surprising because we’re
in a service module.


[...]

Toggle quote (5 lines)
> +(define* (elisp->file-builder exps #:key (special-forms '()))
> + "Return a G-expression that builds a file containing the Elisp
> +expressions (<elisp> objects or s-expressions) or G-epxressions in list EXPS.
> +See `elisp-file' for a description of SPECIAL-FORMS."

[...]

Toggle quote (7 lines)
> + (with-imported-modules (source-module-closure
> + '((guix read-print)))
> + (gexp (begin
> + (use-modules (guix read-print))
> + (call-with-output-file (ungexp output "out")
> + (lambda (port)

For clarity/conciseness, you can use #~ and #$ in this code.

Toggle quote (26 lines)
> +(define-gexp-compiler (elisp-file-compiler (elisp-file <elisp-file>)
> + system target)
> + (match-record elisp-file <elisp-file>
> + (name gexp)
> + (with-monad %store-monad
> + (gexp->derivation name gexp
> + #:system system
> + #:target target
> + #:local-build? #t
> + #:substitutable? #f))))
> +
> +(define* (elisp-file* name exps #:key (special-forms '()))
> + "Return as a monadic value a derivation that builds an Elisp file named NAME
> +containing the expressions in EXPS, a list of Elisp expression objects or
> +G-expressions.
> +
> +This is the monadic counterpart of `elisp-file', which see for a description
> +of SPECIAL-FORMS,"
> + (define builder
> + (elisp->file-builder exps
> + #:special-forms special-forms))
> +
> + (gexp->derivation name builder
> + #:local-build? #t
> + #:substitutable? #f))

I think you don’t need to fiddle with the monadic interface. I’d
suggest removing the <elisp-file> type and gexp compiler and instead
defining ‘elisp-file’ in terms of ‘computed-file’. WDYT?

Toggle quote (23 lines)
> +(define (record-value rec field)
> + "Return the value of field named FIELD in record REC."
> + ((record-accessor (record-type-descriptor rec) field) rec))
> +
> +(define-syntax extend-record
> + ;; Extend record ORIGINAL by creating a new copy using CONSTRUCTOR,
> + ;; replacing each field specified by ORIG-FIELD with the evaluation of (PROC
> + ;; ORIG-VAL EXT-VALS), where ORIG-VAL is the value of ORIG-FIELD in ORIGINAL
> + ;; and EXT-VALS is the list of values of EXT-FIELD in EXTENSIONS.
> + (lambda (s)
> + (syntax-case s ()
> + ((_ constructor original extensions (proc orig-field ext-field) ...)
> + (with-syntax (((field-specs ...)
> + (map
> + (lambda (spec)
> + (syntax-case spec ()
> + ((proc orig-field ext-field)
> + #'(orig-field
> + (apply
> + proc
> + (list
> + (record-value original 'orig-field)

I would advice against accessing record fields by name, with run-time
field name resolution.

The spirit of records, unlike alists, is that there’s a
statically-defined mapping of fields to their offsets in the struct;
without having access to record accessors, you’re not supposed to be
able to access the record (I know Guile has ‘struct-ref’,
‘record-accessor’, etc., but these are abstraction-breaking primitives
that should be avoided IMO).

How could this code be adjusted accordingly? I guess you’re looking for
a way to iterate over fields?

Toggle quote (16 lines)
> +;;; Elisp reader extension.
> +;;;
> +
> +(eval-when (expand load eval)
> +
> + (define (read-elisp-extended port)
> + (read-with-comments port
> + #:blank-line? #f
> + #:elisp? #t
> + #:unelisp-extensions? #t))
> +
> + (define (read-elisp-expression chr port)
> + `(elisp ,(read-elisp-extended port)))
> +
> + (read-hash-extend #\% read-elisp-expression))

I’d lean towards not having a reader extension because they don’t
compose and it’s easy to end up colliding with another, unrelated
extension. I think it’s okay if people write:

(elisp …)

rather than:

#%(…)

It’s also arguably easier to understand for a newcomer.

Toggle quote (2 lines)
> +++ b/guix/read-print.scm

This part is the most “problematic” for me: I’m already dissatisfied
with the current state of things (the pretty-printer in particular is
too complex and hard to work with), and this change brings more
complexity and lacks orthogonality.

What I’d like to see, ideally, is a clear separation between elisp
concerns and Scheme concerns in the reader and in the pretty printer.

Probably, a preliminary step (I could look into it) would be to rewrite
the pretty printer based on Wadler’s “prettier printer” paper and/or
Shinn’s formatting combinators¹.

WDYT?

Thanks,
Ludo’.

K
K
Kierin Bell wrote on 13 Oct 2023 00:15
(name . Ludovic Courtès)(address . ludo@gnu.org)
8734yf4he4.fsf@fernseed.me
Hello,

Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (24 lines)
> Hello!
>
> If there’s consensus, I think we should go ahead with this patch series.
> Worst that could happen is that people will think of ways to change the
> service in one way or another, and that’s fine!
>
> Two general comments:
>
> • As I wrote earlier, I think it’d be nice to have integration tests
> for this, in addition to the unit tests the patch already adds.
>
> • We may want to split the patch into sizable, self-contained bites.
> For instance, the (guix read-print) changes should probably be
> separated out.
>
> I’ll provide more specific comments about the code.
>
> To Emacs team members: please review the Emacs bits of the series!
>
> Thanks,
> Ludo’.
>
>

I have been working on getting v2 ready!

I'll address the comments specific to the reader, printer and
G-expression parts in a reply to the other message.

Specifically regarding the `home-emacs-service-type' interface, most of
it has not changed since July, but I have a few pertinent comments here.

First, I've made a few obvious improvements:

1. The package serializers no longer automatically try to add `-hook'
suffixes to hook symbols specified in the `hooks' field of the
`emacs-package' record type (à la `use-package'). This bites back
when we want to use hooks whose names end in `-functions'.

2. In order to achieve (1), the `%emacs-use-package-serializer' needs to
set the relevant options for `use-package' so that it does not add
`-hook' suffixes. Hence, I've added a new field to the
`emacs-package-serializer' record type for any Elisp that must be
evaluated in order for serialized package configuration to work
properly.

3. Writing `(list (elisp .) (elisp .))' is cumbersome, so I implemented
a new `elisp*' macro that splices multiple s-exps together. We can
achieve the same effect as above by writing `(elisp* . .)'.

4. I'm implementing two new functions,
`make-simple-package-elisp-serializer' and
`make-use-package-elisp-serializer', such that with no arguments they
return the default package serializer procedures, but:
(make-use-package-elisp-serializer EXTRA-KEYWORD-HANDLER)
...Returns a version that serializes the `extra-keywords' field of
any `emacs-package' record according to the function
EXTRA-KEYWORD-HANDLER. I'm using this, for example, in my own config
to define an `auto-faces' keyword that lets me specify faces on a
per-theme basis.

5. I'm adding an `extra-init-files' field to the `emacs-package' record
type that mirrors that of the `emacs-configuration' record type. The
rationale is that it is often convenient to have a complex
configuration for a specific package in a self-contained Elisp file,
which via this field can be loaded in the main Emacs user init file.

Second, I understand that the 1.3kloc implementation of the interface
for configuring Emacs packages in Scheme is rather opinionated. Some
of the changes described above arguably add to this even more.

To simplify things, I've been playing around with splitting this
functionality into a `home-emacs-packages-service-type', which would
extend the `home-emacs-service-type'. This could go in unofficial
channels, but ideally I'd like to see it included with this patch set.

The old configuration interface looks like this:
Toggle snippet (18 lines)
(home-environment
;; ...
(services
(list
;; ...
(service home-emacs-service-type
(home-emacs-configuration
(user-emacs-directory "~/.local/state/emacs/")
(package-serializer %emacs-use-package-serializer)
(configured-packages
(list
(emacs-package
;; ...
)
;; ... Lots more stuff here ...
)))))))

And the modularized configuration would look like this:
Toggle snippet (27 lines)
(home-environment
;; ...
(services
(list
;; ...
(service home-emacs-service-type
(home-emacs-configuration
(emacs %my-custom-emacs-version)
(user-emacs-directory "~/.local/state/emacs/")
(configured-packages
(list
(emacs-package
;; ...
)
;; ... Lots more stuff here ...
))))
(service home-emacs-packages-service-type
(emacs %my-custom-emacs-version)
(serializer %emacs-use-package-serializer)
(packages
(emacs-package
;; ...
)
;; ... Lots of stuff here ...
)))))

The benefits are maintainability and usability --- users who don't want
to use the package configuration interface don't have to deal with the
cognitive dissonance.

The downside is that Emacs package configuration becomes more cumbersome
for more advanced use cases.

One case, illustrated above, is that the
`home-emacs-packages-service-type' doesn't know the Emacs version used
by the `home-emacs-service-type' --- a non-default version of Emacs must
be specified again, separately, for the packages service (that is, if it
matters that the package serializer knows the Emacs version).

Another case is that in order to configure Emacs packages for specific
Emacs servers (created via the `servers' field of the
`home-emacs-configuration'), there would either need to be a `servers'
field in the `home-emacs-packages-configuration' record type
(complicated to implement) or users would need to do this manually (with
the help of a new function such as `emacs-server-with-packages').

I'd appreciate hearing preferences or arguments for or against either.
Also, suggestions for simplifying any part of the interface are welcome!

Thanks.
--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
K
K
Kierin Bell wrote on 13 Oct 2023 00:26
(name . Ludovic Courtès)(address . ludo@gnu.org)
871qdz4gvn.fsf@fernseed.me
Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (22 lines)
> So, here is a short review of the parts I’m most familiar with.
>
> fernseed@fernseed.me skribis:
>
>
> [...]
>
>> +(define-module (gnu home services emacs)
>> + #:use-module (gnu home services)
>
> [...]
>
>> + #:re-export (blank?
>> +
>> + vertical-space
>> + vertical-space?
>
> Why re-export these things from here? Sounds surprising because we’re
> in a service module.
>
>

IIRC, my rationale was that the `<elisp>' objects can contain <blank>
records, e.g., `elisp->sexp' can return them. But users won't normally
be manipulating them, so I should probably remove this.

Toggle quote (4 lines)
>
> For clarity/conciseness, you can use #~ and #$ in this code.
>

Thanks, fixed.

Toggle quote (6 lines)
>
> I think you don’t need to fiddle with the monadic interface. I’d
> suggest removing the <elisp-file> type and gexp compiler and instead
> defining ‘elisp-file’ in terms of ‘computed-file’. WDYT?
>

This would prevent people from being able to reference Elisp files in
G-expressions, or from writing something like:

Toggle snippet (4 lines)
(elisp-file "elisp-file-with-elisp-file"
(list (elisp (load (unelisp %another-elisp-file)))))

...Right?

As long as the Emacs home service type offers a better way to load
files, which I think it does, I'm OK with losing this capability.

Toggle quote (37 lines)
>> +(define (record-value rec field)
>> + "Return the value of field named FIELD in record REC."
>> + ((record-accessor (record-type-descriptor rec) field) rec))
>> +
>> +(define-syntax extend-record
>> + ;; Extend record ORIGINAL by creating a new copy using CONSTRUCTOR,
>> + ;; replacing each field specified by ORIG-FIELD with the evaluation of (PROC
>> + ;; ORIG-VAL EXT-VALS), where ORIG-VAL is the value of ORIG-FIELD in ORIGINAL
>> + ;; and EXT-VALS is the list of values of EXT-FIELD in EXTENSIONS.
>> + (lambda (s)
>> + (syntax-case s ()
>> + ((_ constructor original extensions (proc orig-field ext-field) ...)
>> + (with-syntax (((field-specs ...)
>> + (map
>> + (lambda (spec)
>> + (syntax-case spec ()
>> + ((proc orig-field ext-field)
>> + #'(orig-field
>> + (apply
>> + proc
>> + (list
>> + (record-value original 'orig-field)
>
> I would advice against accessing record fields by name, with run-time
> field name resolution.
>
> The spirit of records, unlike alists, is that there’s a
> statically-defined mapping of fields to their offsets in the struct;
> without having access to record accessors, you’re not supposed to be
> able to access the record (I know Guile has ‘struct-ref’,
> ‘record-accessor’, etc., but these are abstraction-breaking primitives
> that should be avoided IMO).
>
> How could this code be adjusted accordingly? I guess you’re looking for
> a way to iterate over fields?
>

From what I remember, the alternatives are all complicated.

I do find `extend-record' to be very useful, especially when services
need to extend configuration records that have nested records. It's
useful enough that I'd like to see it exported from somewhere, but `(gnu
home services emacs)' hardly seems like the place.

I propose that we put `extend-record' and a few of the most useful
`extend-record-*' procedures --- like `extend-alist-merge' and
`extend-record-field-default' (which I rewrote since v1 of the patch
because there was a bug) --- into `(guix records)'.

There, it could use `lookup-field+wrapper' to manually find the offset
like `match-record'. This isn't exactly ideal, as it would still need
to then use `struct-ref' or similar, but at least `match-record' does
this, too.

WDYT?

Toggle quote (29 lines)
>> +;;; Elisp reader extension.
>> +;;;
>> +
>> +(eval-when (expand load eval)
>> +
>> + (define (read-elisp-extended port)
>> + (read-with-comments port
>> + #:blank-line? #f
>> + #:elisp? #t
>> + #:unelisp-extensions? #t))
>> +
>> + (define (read-elisp-expression chr port)
>> + `(elisp ,(read-elisp-extended port)))
>> +
>> + (read-hash-extend #\% read-elisp-expression))
>
> I’d lean towards not having a reader extension because they don’t
> compose and it’s easy to end up colliding with another, unrelated
> extension. I think it’s okay if people write:
>
> (elisp …)
>
> rather than:
>
> #%(…)
>
> It’s also arguably easier to understand for a newcomer.
>

I'm OK with losing the reader extension. After some experience, I find
that I'd rather use `elisp'. Being able to use semicolons for comments
via reader extension is impressive but also weirdly unsettling.

Also, anyone who wants to seriously write a lot of Elisp-in-Scheme will
want to instead use the `elisp*' macro, which can contain multiple
s-exps. And in order to use the reader extension there, you'd need to
write something like:

(elisp*
;; ...
(unelisp #%SEXP-1)
(unelisp #%SEXP-2)
;; ...
)

Toggle quote (17 lines)
>> +++ b/guix/read-print.scm
>
> This part is the most “problematic” for me: I’m already dissatisfied
> with the current state of things (the pretty-printer in particular is
> too complex and hard to work with), and this change brings more
> complexity and lacks orthogonality.
>
> What I’d like to see, ideally, is a clear separation between elisp
> concerns and Scheme concerns in the reader and in the pretty printer.
>
> Probably, a preliminary step (I could look into it) would be to rewrite
> the pretty printer based on Wadler’s “prettier printer” paper and/or
> Shinn’s formatting combinators¹.
>
> WDYT?
>

I can honestly say that I'm dreading splitting the `(guix read-print)'
changes I've made into multiple commits and annotating them properly.
Writing the pretty printer from scratch sounds a lot less confusing and
painful at this point.

I'm imagining a stripped-down version of the "formatting combinators"
from SRFI-159/166 specifically adapted for pulling into service modules
to write pretty-printers, not just for Elisp but also for other
configuration languages.

It's too bad that Guile doesn't have SRFI-159/166 and the requisite
"environment monads" and delayed computation from SRFI-165 built-in.

My first design question would be: Would this be a suitable application
for `(guix monads)' [to create a formatting "environment monad"], or
does that entail more trouble than it's worth?

I'll work on the unit tests, as well, especially once more progress has
been made on the pretty-printer situation.

Toggle quote (3 lines)
> Thanks,
> Ludo’.

Thanks, I appreciate the feedback!

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
L
L
Liliana Marie Prikler wrote on 13 Oct 2023 06:30
0dc55cac713686514f90ec04243f710894451a82.camel@gmail.com
Hi, Kierin

Am Donnerstag, dem 12.10.2023 um 18:15 -0400 schrieb Kierin Bell:
Toggle quote (26 lines)
> […]
> The benefits are maintainability and usability --- users who don't
> want to use the package configuration interface don't have to deal
> with the cognitive dissonance.
>
> The downside is that Emacs package configuration becomes more
> cumbersome for more advanced use cases.
>
> One case, illustrated above, is that the
> `home-emacs-packages-service-type' doesn't know the Emacs version
> used by the `home-emacs-service-type' --- a non-default version of
> Emacs must be specified again, separately, for the packages service
> (that is, if it matters that the package serializer knows the Emacs
> version).
>
> Another case is that in order to configure Emacs packages for
> specific Emacs servers (created via the `servers' field of the
> `home-emacs-configuration'), there would either need to be a
> `servers' field in the `home-emacs-packages-configuration' record
> type (complicated to implement) or users would need to do this
> manually (with the help of a new function such as `emacs-server-with-
> packages').
>
> I'd appreciate hearing preferences or arguments for or against
> either. Also, suggestions for simplifying any part of the interface
> are welcome!
I think you should separate your concerns more clearly. Rather than
having home-emacs-service-type take packages and all that other fluff,
you could just have init-directory as a list of file-likes. Then you
can have an init.el-file procedure (or syntax) to take care of
packages.

e.g.
(home-emacs-configuration
(emacs %my-custom-emacs)
(init-directory
(list (init.el-file (emacs-package …) (elisp* …) …))))
Now, admittedly, snarfing guix packages out of init.el-file might
become an issue; I haven't thought about how to implement that
concretely.

The upside is, that you could reuse this structure for servers. An
emacs-server would just take another home-emacs-configuration and a
server name.

Cheers
K
K
Kierin Bell wrote on 13 Oct 2023 15:59
(name . Liliana Marie Prikler)(address . liliana.prikler@gmail.com)
87o7h2lj1e.fsf@fernseed.me
Hello,

Liliana Marie Prikler <liliana.prikler@gmail.com> writes:

Toggle quote (3 lines)
> Hi, Kierin
>
> Am Donnerstag, dem 12.10.2023 um 18:15 -0400 schrieb Kierin Bell:
[...]
Toggle quote (12 lines)
> I think you should separate your concerns more clearly. Rather than
> having home-emacs-service-type take packages and all that other fluff,
> you could just have init-directory as a list of file-likes. Then you
> can have an init.el-file procedure (or syntax) to take care of
> packages.
>
> e.g.
> (home-emacs-configuration
> (emacs %my-custom-emacs)
> (init-directory
> (list (init.el-file (emacs-package …) (elisp* …) …))))

Apologies! I made some confusing errors in the example configurations
in previous message. In the newer design, the `home-emacs-service-type'
would not directly deal with <emacs-package> objects at all.

Here would be a proper example of the newer semantics:

Toggle snippet (36 lines)
(home-environment
;; ...
(services
(list
;; ...
(service home-emacs-service-type
(home-emacs-configuration
(emacs %my-custom-emacs-version)
(user-emacs-directory "~/.local/state/emacs/")
(default-init
(emacs-configuration
(extra-init
(elisp* . . . .))))
;; ... And possibly:
(servers
(list
(emacs-server-with-packages
(emacs-server
(name "server"))
(list
(emacs-package
;; ...
)))))))
(service home-emacs-packages-service-type
(emacs %my-custom-emacs-version)
(serializer %emacs-use-package-serializer)
(packages
(list
(emacs-package
;; ...
)
;; ...
))))))


I think you're suggesting flatting the hierarchy from:

Toggle snippet (11 lines)
(home-emacs-configuration
(default-init
(emacs-configuration
(extra-init
(elisp* . . . .))
(extra-init-files ; files symlinked and loaded with `load'
(list
(local-file "~/guix-config/extras.el")))
(variables '((foo . #f))))))

...Into something like:

Toggle snippet (13 lines)
(home-emacs-configuration
(init-file
(list
(elisp* . . .)
(local-file "~/guix-config/extras.el") ; file contents spliced in
; or symlinked and loaded?
(emacs-variables '((foo . #f)))
(emacs-packages
(emacs-package
;; ...
)))))

This is what David Wilson's patch[1] did.

There could easily be an explicit `init.el-file' function in there
instead.

Toggle quote (8 lines)
> Now, admittedly, snarfing guix packages out of init.el-file might
> become an issue; I haven't thought about how to implement that
> concretely.
>
> The upside is, that you could reuse this structure for servers. An
> emacs-server would just take another home-emacs-configuration and a
> server name.

The upside of the approach that uses <emacs-configuration> records is
that the encapsulation avoids the weirdness of using a
<home-emacs-configuration> that contains <emacs-server> objects to
create new server objects. Also, obviously the introspection of
records.

The nesting is confusing, though, and I'd definitely like to work on
that.

A middle ground might be to keep the concept of the
<emacs-configuration> record (maybe even the
`home-emacs-packages-service-type'), but make an `init-file' field smart
enough to accept either Elisp expressions, file-like objects, or
<emacs-configuration> records.

I feel like this may be more of an Elispish way to do things than a
properly Schemic way, but it's a lot simpler to understand.

We could export functions/macros that explicitly convert alists,
<emacs-package> objects, etc. into Elisp, too, in addition. These may
be useful in other contexts. But I think it is probably less confusing
and better in principle to chose only one configuration paradigm as the
"proper", standardized way of doing things.

WDYT?

Footnotes:

--
Kierin Bell
GPG Key: FCF2 5F08 EA4F 2E3D C7C3 0D41 D14A 8CD3 2D97 0B36
S
S
Steve George wrote on 1 Apr 10:28 +0200
Bumping - Add home-emacs-service-type
(address . 64620@debbugs.gnu.org)
ZgpwLtEvdTrbPt7Q@t25sg
Hi,

Looks like a lot of work was done on this but it didn't get to a conclusion?

Any way we can get it moving again? :-)

Futurile
?