(address . guix-patches@gnu.org)(name . Kierin Bell)(address . 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.