Message ID | 87v8lby7jq.fsf@daviwil.com |
---|---|
State | New |
Headers | show |
Series | [bug#60753] gnu: home: Add home-emacs-service-type. | expand |
Hello! David Wilson <david@daviwil.com> skribis: > Yes, I saw that you meant to run Emacs as a daemon and I think it should > be added to this service (or another daemon-specific service) in a > future patch. I figured it would be easier to get a patch accepted > without the daemon functionality just yet since there seemed to be more > feedback about that aspect in your patch thread. Agree, I think we should add the daemon functionality in a subsequent patch. Overall it looks nice to me! Some comments and suggestions: > +@node Emacs Home Services > +@subsection Emacs Home Services > + > +@defvr {Scheme Variable} home-emacs-service-type It would be nice if you could start the section with a few sentences explaining the rationale and what’s being described here. > +This is the service type for configuring the Emacs text editor. It > +enables you to assemble @file{init.el} and @file{early-init.el} files > +from snippets in your home configuration and other Emacs Lisp files you > +have in your personal configuration folder. Maybe like so: … to assemble the @file{init.el} (@pxref{Init File,,, emacs, GNU Emacs Manual}) and @file{early-init.el} (@pxref{Early Init File,,, emacs, GNU Emacs Manual}) files … > +@example > +$ cp ~/.emacs.d $XDG_CONFIG_HOME/emacs > +$ cp ~/.emacs $XDG_CONFIG_HOME/emacs/init.el > +@end example I’d drop the prompt. > +@item @code{init-file} (default: @code{'()}) > +Configuration text or files to include in @file{init.el}. > + > +@item @code{early-init-file} (default: @code{'()}) > +Configuration text or files to include in @file{early-init.el}. What about accepting sexps (or gexps) instead of strings? As in: (init-file '((require 'whatever) (setq something t))) Also I find it confusing that it’s either text or files. In the code it has type ‘text-config’, which means list of file-like objects IIUC, no? > +@item @code{load-paths} (default: @code{'()}) > +Additional load paths to add to Emacs' @code{load-path} variable. Lines > +will be inserted at the beginning of @file{early-init.el}. Nitpick: I think this should be ‘load-path’ (singular), because it’s one search path (i.e., a list of directories). And: s/Additional load paths/Additional directories/ > +@end table > +@end deftp Would be nice to have a couple of commented examples here, like you had in the first message in this thread. :-) > +@deftp {Data Type} home-emacs-extension > +The extension record for @code{home-emacs-service-type}. Would be nice to have a sentence above, like “This service type can be extended with @code{home-emacs-extension} records, described below:”. > +@table @asis > +@item @code{packages} (default: @code{'()}) > +Additional packages required by the Emacs configuration. > + > +@item @code{init-file} (default: @code{'()}) > +Configuration text or files to include in @file{init.el}. > + > +@item @code{early-init-file} (default: @code{'()}) > +Configuration text or files to include in @file{early-init.el}. > + > +@item @code{load-paths} (default: @code{'()}) Ditto. > +@end deftp Would be great to have an example of that too. :-) Thank you! Ludo’.
On 2023-01-14 19:00, Ludovic Courtès wrote: > Hello! > > David Wilson <david@daviwil.com> skribis: > >> Yes, I saw that you meant to run Emacs as a daemon and I think it should >> be added to this service (or another daemon-specific service) in a >> future patch. I figured it would be easier to get a patch accepted >> without the daemon functionality just yet since there seemed to be more >> feedback about that aspect in your patch thread. > > Agree, I think we should add the daemon functionality in a subsequent > patch. > > Overall it looks nice to me! Some comments and suggestions: > >> +@node Emacs Home Services >> +@subsection Emacs Home Services >> + >> +@defvr {Scheme Variable} home-emacs-service-type > > It would be nice if you could start the section with a few sentences > explaining the rationale and what’s being described here. > >> +This is the service type for configuring the Emacs text editor. It >> +enables you to assemble @file{init.el} and @file{early-init.el} files >> +from snippets in your home configuration and other Emacs Lisp files you >> +have in your personal configuration folder. > > Maybe like so: > > … to assemble the @file{init.el} (@pxref{Init File,,, emacs, GNU Emacs > Manual}) and @file{early-init.el} (@pxref{Early Init File,,, emacs, > GNU Emacs Manual}) files … > >> +@example >> +$ cp ~/.emacs.d $XDG_CONFIG_HOME/emacs >> +$ cp ~/.emacs $XDG_CONFIG_HOME/emacs/init.el >> +@end example > > I’d drop the prompt. > >> +@item @code{init-file} (default: @code{'()}) >> +Configuration text or files to include in @file{init.el}. >> + >> +@item @code{early-init-file} (default: @code{'()}) >> +Configuration text or files to include in @file{early-init.el}. > > What about accepting sexps (or gexps) instead of strings? As in: > > (init-file '((require 'whatever) (setq something t))) A quick minor note on this approach: it won't be possible to use #'elisp-function inside such configuration because it will be interpreted by guile reader, but actually rde lives without this functionality completely ok. Do we want something like this possible? (init-file `((require 'whatever) (setq something t) (load ,(local-file "old-init.el"))) > > Also I find it confusing that it’s either text or files. In the code it > has type ‘text-config’, which means list of file-like objects IIUC, no? > >> +@item @code{load-paths} (default: @code{'()}) >> +Additional load paths to add to Emacs' @code{load-path} variable. Lines >> +will be inserted at the beginning of @file{early-init.el}. > > Nitpick: I think this should be ‘load-path’ (singular), because it’s one > search path (i.e., a list of directories). > > And: s/Additional load paths/Additional directories/ > >> +@end table >> +@end deftp > > Would be nice to have a couple of commented examples here, like you had > in the first message in this thread. :-) > >> +@deftp {Data Type} home-emacs-extension >> +The extension record for @code{home-emacs-service-type}. > > Would be nice to have a sentence above, like “This service type can be > extended with @code{home-emacs-extension} records, described below:”. > >> +@table @asis >> +@item @code{packages} (default: @code{'()}) >> +Additional packages required by the Emacs configuration. >> + >> +@item @code{init-file} (default: @code{'()}) >> +Configuration text or files to include in @file{init.el}. >> + >> +@item @code{early-init-file} (default: @code{'()}) >> +Configuration text or files to include in @file{early-init.el}. >> + >> +@item @code{load-paths} (default: @code{'()}) > > Ditto. > >> +@end deftp > > Would be great to have an example of that too. :-) > > Thank you! > > Ludo’. > > >
Thanks for the feedback, Ludo! I'll make the requested changes and send them later today. I may also try adding some tests to ensure that the output is what we expect. There seems to be a `tests/services` folder, should I add a `tests/home-services` folder to match? Ludovic Courtès <ludo@gnu.org> writes: > It would be nice if you could start the section with a few sentences > explaining the rationale and what’s being described here. Will do, I'll look at other services to get an idea of the best way to do this. > What about accepting sexps (or gexps) instead of strings? As in: > > (init-file '((require 'whatever) (setq something t))) > > Also I find it confusing that it’s either text or files. In the code it > has type ‘text-config’, which means list of file-like objects IIUC, no? Yes, this was a misunderstanding on my part! Regarding using s-expressions for this, it would certainly look cleaner. Andrew raises a good concern about it in his response, I'll take a look at what RDE does for this and see if I can adopt a similar approach. > Nitpick: I think this should be ‘load-path’ (singular), because it’s one > search path (i.e., a list of directories). > > And: s/Additional load paths/Additional directories/ Will change both of these, thanks! > Would be nice to have a couple of commented examples here, like you had > in the first message in this thread. :-) Yep, I'll add examples for all the use cases here. > Would be nice to have a sentence above, like “This service type can be > extended with @code{home-emacs-extension} records, described below:”. Will describe and give an example of how to extend the service! David
Hi, Andrew Tropin <andrew@trop.in> skribis: >> What about accepting sexps (or gexps) instead of strings? As in: >> >> (init-file '((require 'whatever) (setq something t))) > > A quick minor note on this approach: it won't be possible to use > #'elisp-function inside such configuration because it will be > interpreted by guile reader, but actually rde lives without this > functionality completely ok. Specifically: (write '#'x) |= (syntax x) But we can use (guix read-print) and ensure that it prints #'. > Do we want something like this possible? > > (init-file `((require 'whatever) > (setq something t) > (load ,(local-file "old-init.el"))) It’d be nice. In that case, we’ll want it to be a gexp though: #~((require 'whatever) (load #$(local-file …))) Thanks, Ludo’.
On 2023-01-17 10:02, Ludovic Courtès wrote: > Hi, > > Andrew Tropin <andrew@trop.in> skribis: > >>> What about accepting sexps (or gexps) instead of strings? As in: >>> >>> (init-file '((require 'whatever) (setq something t))) >> >> A quick minor note on this approach: it won't be possible to use >> #'elisp-function inside such configuration because it will be >> interpreted by guile reader, but actually rde lives without this >> functionality completely ok. > > Specifically: > > (write '#'x) > |= (syntax x) > > But we can use (guix read-print) and ensure that it prints #'. > Do you have any links to docs/sample implementations on the topic of extending guile reader, so we have an example to start with? Does guix workflow language do something like that? I think it will be cool to hook up a custom reader, ideally comment preserving, for emacs lisp inside scheme files. >> Do we want something like this possible? >> >> (init-file `((require 'whatever) >> (setq something t) >> (load ,(local-file "old-init.el"))) > > It’d be nice. In that case, we’ll want it to be a gexp though: > > #~((require 'whatever) (load #$(local-file …))) > gexps are nice, but do we really need/want them here? Do you have any thoughts on what are the benifits over quasiquotes in this case? Maybe some examples?
Hi, Andrew Tropin <andrew@trop.in> skribis: > On 2023-01-17 10:02, Ludovic Courtès wrote: > >> Hi, >> >> Andrew Tropin <andrew@trop.in> skribis: >> >>>> What about accepting sexps (or gexps) instead of strings? As in: >>>> >>>> (init-file '((require 'whatever) (setq something t))) >>> >>> A quick minor note on this approach: it won't be possible to use >>> #'elisp-function inside such configuration because it will be >>> interpreted by guile reader, but actually rde lives without this >>> functionality completely ok. >> >> Specifically: >> >> (write '#'x) >> |= (syntax x) >> >> But we can use (guix read-print) and ensure that it prints #'. >> > > Do you have any links to docs/sample implementations on the topic of > extending guile reader, so we have an example to start with? It’s not the reader but rather the writer that we’d want to tweak. In (guix read-print), ‘pretty-print-with-comments’ already special cases quasiquote etc. so that it prints ‘`’ (backtick) and not ‘quasiquote'. We’d add clauses for ‘syntax’ and ‘quasisyntax’. > I think it will be cool to hook up a custom reader, ideally comment > preserving, for emacs lisp inside scheme files. (guix read-print) is what you want. :-) >>> Do we want something like this possible? >>> >>> (init-file `((require 'whatever) >>> (setq something t) >>> (load ,(local-file "old-init.el"))) >> >> It’d be nice. In that case, we’ll want it to be a gexp though: >> >> #~((require 'whatever) (load #$(local-file …))) >> > > gexps are nice, but do we really need/want them here? Do you have any > thoughts on what are the benifits over quasiquotes in this case? Maybe > some examples? The benefit in the example above is that the gexp would actually work whereas the sexp wouldn’t :-), unless there’s code somewhere to manually traverse the sexp adn replace the <local-file> record with its store item (which is what gexps are about). I hope that makes sense! Ludo’.
On 2023-01-23 11:18, Ludovic Courtès wrote: > Hi, > > Andrew Tropin <andrew@trop.in> skribis: > >> On 2023-01-17 10:02, Ludovic Courtès wrote: >> >>> Hi, >>> >>> Andrew Tropin <andrew@trop.in> skribis: >>> >>>>> What about accepting sexps (or gexps) instead of strings? As in: >>>>> >>>>> (init-file '((require 'whatever) (setq something t))) >>>> >>>> A quick minor note on this approach: it won't be possible to use >>>> #'elisp-function inside such configuration because it will be >>>> interpreted by guile reader, but actually rde lives without this >>>> functionality completely ok. >>> >>> Specifically: >>> >>> (write '#'x) >>> |= (syntax x) >>> >>> But we can use (guix read-print) and ensure that it prints #'. >>> >> >> Do you have any links to docs/sample implementations on the topic of >> extending guile reader, so we have an example to start with? > > It’s not the reader but rather the writer that we’d want to tweak. Right, it already can read #'x as (syntax x) and we can print it properly later, but AFAIK comments are ignored by the default reader. So I would expect to do something (very roughly) like this: --8<---------------cut here---------------start------------->8--- (parameterize (((@@ (guix gexp) read-procedure) read-with-comments)) #~(list 'hello ; Comment I would like to preserve during serialization 'guix)) --8<---------------cut here---------------end--------------->8--- Of course it doesn't work, but I hope demonstrates the idea. > > In (guix read-print), ‘pretty-print-with-comments’ already special > cases quasiquote etc. so that it prints ‘`’ (backtick) and not > ‘quasiquote'. We’d add clauses for ‘syntax’ and ‘quasisyntax’. > It seems ice-9 pretty-print also preserves backticks, but I see that pretty-print-with-comments also preserves gexps, which is cool. Adding syntax will make it even cooler. >> I think it will be cool to hook up a custom reader, ideally comment >> preserving, for emacs lisp inside scheme files. > > (guix read-print) is what you want. :-) > Can you give a hint on how to use it for preserving comments, please? >>>> Do we want something like this possible? >>>> >>>> (init-file `((require 'whatever) >>>> (setq something t) >>>> (load ,(local-file "old-init.el"))) >>> >>> It’d be nice. In that case, we’ll want it to be a gexp though: >>> >>> #~((require 'whatever) (load #$(local-file …))) >>> >> >> gexps are nice, but do we really need/want them here? Do you have any >> thoughts on what are the benifits over quasiquotes in this case? Maybe >> some examples? > > The benefit in the example above is that the gexp would actually work > whereas the sexp wouldn’t :-), unless there’s code somewhere to manually > traverse the sexp adn replace the <local-file> record with its store > item (which is what gexps are about). > > I hope that makes sense! With this simple serializer we already achieved quite good results: https://git.sr.ht/~abcdw/rde/tree/388d3ad95e8607543df3dcdf26d058b610e77389/src/rde/serializers/lisp.scm#L35 For this input --8<---------------cut here---------------start------------->8--- `((load ,(local-file "./feature-lists.scm")) ,#~(format #f "hello") ; top level gexps are evaluated (list ,#~(format #f "hello")) ; nested gexps are not ,#~";; hacky comment" ;; comment, which is not preserved #'hi-fn ; incorrectly serialized, but fixable by alternative ; pretty-print ) --8<---------------cut here---------------end--------------->8--- it provides quite satisfying results: --8<---------------cut here---------------start------------->8--- (load "/gnu/store/xb6ma0mcgg1zzq645s63arvy3qskmbiz-feature-lists.scm") hello (list (format #f "hello")) ;; hacky comment (syntax hi-fn) --8<---------------cut here---------------end--------------->8--- It's a little incosistent (top level gexp are evaluated, but nested are not), comments are not preserved and #' serialized incorrectly, but other than that it works very good. WDYT about overall approach used here? or we can do it radically better?
Ludovic Courtès <ludo@gnu.org> writes: > Specifically: > > (write '#'x) > |= (syntax x) > > But we can use (guix read-print) and ensure that it prints #'. The way I get around this in my config is adding #+begin_source emacs-lisp ;; #' exports a scheme (syntax ...) form. Treat this as a ;; (function ...) form. (defalias 'syntax 'function) #+end_source This is a very hacky solution, but if you wanted to not modify the reader, you could add this to the beginning of init.el with the service. -- Reily Siegel
Hi Andrew, Andrew Tropin <andrew@trop.in> skribis: >>> I think it will be cool to hook up a custom reader, ideally comment >>> preserving, for emacs lisp inside scheme files. >> >> (guix read-print) is what you want. :-) >> > > Can you give a hint on how to use it for preserving comments, please? It can be used like this: --8<---------------cut here---------------start------------->8--- scheme@(guile-user)> ,use(guix read-print) scheme@(guile-user)> (pretty-print-with-comments (current-output-port) `(list foo ,(comment ";ooh!\n" #t) bar)) (list foo ;ooh! bar)$5 = 10 scheme@(guile-user)> (call-with-input-string "(list foo ;oh!\nbar)" read-with-comments) $6 = (list foo #<<comment> str: ";oh!\n" margin?: #t> bar) --8<---------------cut here---------------end--------------->8--- There’s a <comment> record type. Now let’s see perhaps what we need to get ‘home-emacs-service-type’ merged, and what we can keep as future work. Thoughts? Ludo’.
Andrew Tropin <andrew@trop.in> writes: > On 2023-01-23 11:18, Ludovic Courtès wrote: > >> Hi, >> >> Andrew Tropin <andrew@trop.in> skribis: >> >>> On 2023-01-17 10:02, Ludovic Courtès wrote: >>> >>>> Hi, >>>> >>>> Andrew Tropin <andrew@trop.in> skribis: >>>> >>>>>> What about accepting sexps (or gexps) instead of strings? As in: >>>>>> >>>>>> (init-file '((require 'whatever) (setq something t))) >>>>> >>>>> A quick minor note on this approach: it won't be possible to use >>>>> #'elisp-function inside such configuration because it will be >>>>> interpreted by guile reader, but actually rde lives without this >>>>> functionality completely ok. >>>> >>>> Specifically: >>>> >>>> (write '#'x) >>>> |= (syntax x) >>>> >>>> But we can use (guix read-print) and ensure that it prints #'. >>>> >>> >>> Do you have any links to docs/sample implementations on the topic of >>> extending guile reader, so we have an example to start with? >> >> It’s not the reader but rather the writer that we’d want to tweak. > > Right, it already can read #'x as (syntax x) and we can print it > properly later, but AFAIK comments are ignored by the default reader. > So I would expect to do something (very roughly) like this: > > --8<---------------cut here---------------start------------->8--- > (parameterize (((@@ (guix gexp) read-procedure) read-with-comments)) > #~(list 'hello ; Comment I would like to preserve during serialization > 'guix)) > --8<---------------cut here---------------end--------------->8--- > > Of course it doesn't work, but I hope demonstrates the idea. > >> >> In (guix read-print), ‘pretty-print-with-comments’ already special >> cases quasiquote etc. so that it prints ‘`’ (backtick) and not >> ‘quasiquote'. We’d add clauses for ‘syntax’ and ‘quasisyntax’. >> > > It seems ice-9 pretty-print also preserves backticks, but I see that > pretty-print-with-comments also preserves gexps, which is cool. Adding > syntax will make it even cooler. > >>> I think it will be cool to hook up a custom reader, ideally comment >>> preserving, for emacs lisp inside scheme files. >> >> (guix read-print) is what you want. :-) >> > > Can you give a hint on how to use it for preserving comments, please? > >>>>> Do we want something like this possible? >>>>> >>>>> (init-file `((require 'whatever) >>>>> (setq something t) >>>>> (load ,(local-file "old-init.el"))) >>>> >>>> It’d be nice. In that case, we’ll want it to be a gexp though: >>>> >>>> #~((require 'whatever) (load #$(local-file …))) >>>> >>> >>> gexps are nice, but do we really need/want them here? Do you have any >>> thoughts on what are the benifits over quasiquotes in this case? Maybe >>> some examples? >> >> The benefit in the example above is that the gexp would actually work >> whereas the sexp wouldn’t :-), unless there’s code somewhere to manually >> traverse the sexp adn replace the <local-file> record with its store >> item (which is what gexps are about). >> >> I hope that makes sense! > > With this simple serializer we already achieved quite good results: > https://git.sr.ht/~abcdw/rde/tree/388d3ad95e8607543df3dcdf26d058b610e77389/src/rde/serializers/lisp.scm#L35 > > For this input > --8<---------------cut here---------------start------------->8--- > `((load ,(local-file "./feature-lists.scm")) > ,#~(format #f "hello") ; top level gexps are evaluated > (list ,#~(format #f "hello")) ; nested gexps are not > ,#~";; hacky comment" > ;; comment, which is not preserved > #'hi-fn ; incorrectly serialized, but fixable by alternative > ; pretty-print > ) > --8<---------------cut here---------------end--------------->8--- > > it provides quite satisfying results: > --8<---------------cut here---------------start------------->8--- > (load "/gnu/store/xb6ma0mcgg1zzq645s63arvy3qskmbiz-feature-lists.scm") > hello > (list (format #f "hello")) > ;; hacky comment > (syntax hi-fn) > --8<---------------cut here---------------end--------------->8--- > > It's a little incosistent (top level gexp are evaluated, but nested are > not), comments are not preserved and #' serialized incorrectly, but > other than that it works very good. > > WDYT about overall approach used here? or we can do it radically > better? Not saying it's better in any particular way, but I have had this locally for all my elisp-read-by-guile-written-back-to-elisp needs: --8<---------------cut here---------------start------------->8--- (define-module (jlicht build elisp-write) #:use-module (ice-9 match) #:use-module (srfi srfi-1) #:export (elisp-write)) (define (elisp-write in-list? exp port) "Stack-blowing implementation that writes guile's internal elisp representation to something that can be parsed by Emacs." ;; Definitions from (language elisp parser)'s quotation-symbols: (define symbol-strings '((#{`}# . "`") (#{,}# . ",") (#{,@}# . ",@"))) (define (elisp-symbol? sym) (assq sym symbol-strings)) (define (write-elisp-symbol sym port) (format port "~A" (assq-ref symbol-strings sym))) (match exp (((? elisp-symbol? sym) rest) (write-elisp-symbol sym port) (elisp-write in-list? rest port)) ;; Vector expression (#(vs ...) (format port "[") (elisp-write #t vs port) (format port "]")) ;; Guile elisp implementation detail ('(%set-lexical-binding-mode #f) 'skip) ;; List walker ((e ...) (when (not in-list?) (format port "(")) (unless (null? e) (elisp-write #f (car e) port) (for-each (lambda (v) (format port " ") (elisp-write #f v port)) (cdr e))) (when (not in-list?) (format port ")"))) ;; dotted pair ((and (? pair?) (? dotted-list? l)) (format port "(") (elisp-write #t (drop-right l 0) port) (format port " . ") (elisp-write #t (take-right l 0) port) (format port ")")) ;; Print simple primitives (_ (write exp port)))) --8<---------------cut here---------------end--------------->8--- On the reader side I just use guile's elisp reader: --8<---------------cut here---------------start------------->8--- (define-module (jlicht test elisp) #:use-module (language elisp parser) #:use-module (jlicht build elisp-write) #:use-module (srfi srfi-26) #:use-module (srfi srfi-64)) (eval-when (expand load eval) (read-hash-extend #\e (lambda (chr port) (read-elisp port)))) (set! test-log-to-file #f) (define (roundtrip expr) (let ((written (call-with-output-string (cut elisp-write #f expr <>)))) (call-with-input-string written read-elisp))) (define-syntax test-roundtrip-equals (syntax-rules () ((_ expr) (let ((e1 (roundtrip expr))) (test-equal e1 (roundtrip e1)))))) (define runner (test-runner-simple)) (test-with-runner runner (test-begin "roundtrip-elisp-fixed-point") (test-roundtrip-equals 12) (test-roundtrip-equals "hello") (test-roundtrip-equals '#e#'my-fn) (test-roundtrip-equals '#e[a b c]) (test-roundtrip-equals '#e`(+ 1 2 ,@(a b) ,c)) (test-end "roundtrip-elisp-fixed-point")) (exit (test-runner-fail-count runner)) --8<---------------cut here---------------end--------------->8--- I've also hooked it up in combination with a sequence of calls to `scheme-file' -> `computed-file' called `elisp-file', but that's a bit more hacky and less relevant to the current discussion. - Jelle
On 2023-02-01 12:59, Jelle Licht wrote: > Andrew Tropin <andrew@trop.in> writes: > >> On 2023-01-23 11:18, Ludovic Courtès wrote: >> >>> Hi, >>> >>> Andrew Tropin <andrew@trop.in> skribis: >>> >>>> On 2023-01-17 10:02, Ludovic Courtès wrote: >>>> >>>>> Hi, >>>>> >>>>> Andrew Tropin <andrew@trop.in> skribis: >>>>> >>>>>>> What about accepting sexps (or gexps) instead of strings? As in: >>>>>>> >>>>>>> (init-file '((require 'whatever) (setq something t))) >>>>>> >>>>>> A quick minor note on this approach: it won't be possible to use >>>>>> #'elisp-function inside such configuration because it will be >>>>>> interpreted by guile reader, but actually rde lives without this >>>>>> functionality completely ok. >>>>> >>>>> Specifically: >>>>> >>>>> (write '#'x) >>>>> |= (syntax x) >>>>> >>>>> But we can use (guix read-print) and ensure that it prints #'. >>>>> >>>> >>>> Do you have any links to docs/sample implementations on the topic of >>>> extending guile reader, so we have an example to start with? >>> >>> It’s not the reader but rather the writer that we’d want to tweak. >> >> Right, it already can read #'x as (syntax x) and we can print it >> properly later, but AFAIK comments are ignored by the default reader. >> So I would expect to do something (very roughly) like this: >> >> --8<---------------cut here---------------start------------->8--- >> (parameterize (((@@ (guix gexp) read-procedure) read-with-comments)) >> #~(list 'hello ; Comment I would like to preserve during serialization >> 'guix)) >> --8<---------------cut here---------------end--------------->8--- >> >> Of course it doesn't work, but I hope demonstrates the idea. >> >>> >>> In (guix read-print), ‘pretty-print-with-comments’ already special >>> cases quasiquote etc. so that it prints ‘`’ (backtick) and not >>> ‘quasiquote'. We’d add clauses for ‘syntax’ and ‘quasisyntax’. >>> >> >> It seems ice-9 pretty-print also preserves backticks, but I see that >> pretty-print-with-comments also preserves gexps, which is cool. Adding >> syntax will make it even cooler. >> >>>> I think it will be cool to hook up a custom reader, ideally comment >>>> preserving, for emacs lisp inside scheme files. >>> >>> (guix read-print) is what you want. :-) >>> >> >> Can you give a hint on how to use it for preserving comments, please? >> >>>>>> Do we want something like this possible? >>>>>> >>>>>> (init-file `((require 'whatever) >>>>>> (setq something t) >>>>>> (load ,(local-file "old-init.el"))) >>>>> >>>>> It’d be nice. In that case, we’ll want it to be a gexp though: >>>>> >>>>> #~((require 'whatever) (load #$(local-file …))) >>>>> >>>> >>>> gexps are nice, but do we really need/want them here? Do you have any >>>> thoughts on what are the benifits over quasiquotes in this case? Maybe >>>> some examples? >>> >>> The benefit in the example above is that the gexp would actually work >>> whereas the sexp wouldn’t :-), unless there’s code somewhere to manually >>> traverse the sexp adn replace the <local-file> record with its store >>> item (which is what gexps are about). >>> >>> I hope that makes sense! >> >> With this simple serializer we already achieved quite good results: >> https://git.sr.ht/~abcdw/rde/tree/388d3ad95e8607543df3dcdf26d058b610e77389/src/rde/serializers/lisp.scm#L35 >> >> For this input >> --8<---------------cut here---------------start------------->8--- >> `((load ,(local-file "./feature-lists.scm")) >> ,#~(format #f "hello") ; top level gexps are evaluated >> (list ,#~(format #f "hello")) ; nested gexps are not >> ,#~";; hacky comment" >> ;; comment, which is not preserved >> #'hi-fn ; incorrectly serialized, but fixable by alternative >> ; pretty-print >> ) >> --8<---------------cut here---------------end--------------->8--- >> >> it provides quite satisfying results: >> --8<---------------cut here---------------start------------->8--- >> (load "/gnu/store/xb6ma0mcgg1zzq645s63arvy3qskmbiz-feature-lists.scm") >> hello >> (list (format #f "hello")) >> ;; hacky comment >> (syntax hi-fn) >> --8<---------------cut here---------------end--------------->8--- >> >> It's a little incosistent (top level gexp are evaluated, but nested are >> not), comments are not preserved and #' serialized incorrectly, but >> other than that it works very good. >> >> WDYT about overall approach used here? or we can do it radically >> better? > > Not saying it's better in any particular way, but I have had this locally > for all my elisp-read-by-guile-written-back-to-elisp needs: I saw it in guix-home-manager and probably you've made the thread on rde-devel too. I tried some parts of this code, but didn't succeed to get a complete working out of it, now I have a little more knowledge and hope will get better results :) > > --8<---------------cut here---------------start------------->8--- > (define-module (jlicht build elisp-write) > #:use-module (ice-9 match) > #:use-module (srfi srfi-1) > #:export (elisp-write)) > > (define (elisp-write in-list? exp port) > "Stack-blowing implementation that writes guile's internal elisp > representation to something that can be parsed by Emacs." > ;; Definitions from (language elisp parser)'s quotation-symbols: > (define symbol-strings > '((#{`}# . "`") > (#{,}# . ",") > (#{,@}# . ",@"))) > (define (elisp-symbol? sym) > (assq sym symbol-strings)) > (define (write-elisp-symbol sym port) > (format port "~A" (assq-ref symbol-strings sym))) > > (match exp > (((? elisp-symbol? sym) rest) > (write-elisp-symbol sym port) > (elisp-write in-list? rest port)) > ;; Vector expression > (#(vs ...) > (format port "[") > (elisp-write #t vs port) > (format port "]")) > ;; Guile elisp implementation detail > ('(%set-lexical-binding-mode #f) 'skip) > ;; List walker > ((e ...) > (when (not in-list?) (format port "(")) > (unless (null? e) > (elisp-write #f (car e) port) > (for-each (lambda (v) > (format port " ") > (elisp-write #f v port)) (cdr e))) > (when (not in-list?) (format port ")"))) > ;; dotted pair > ((and (? pair?) (? dotted-list? l)) > (format port "(") > (elisp-write #t (drop-right l 0) port) > (format port " . ") > (elisp-write #t (take-right l 0) port) > (format port ")")) > ;; Print simple primitives > (_ (write exp port)))) > --8<---------------cut here---------------end--------------->8--- > > On the reader side I just use guile's elisp reader: > > --8<---------------cut here---------------start------------->8--- > (define-module (jlicht test elisp) > #:use-module (language elisp parser) > #:use-module (jlicht build elisp-write) > #:use-module (srfi srfi-26) > #:use-module (srfi srfi-64)) > > (eval-when (expand load eval) > (read-hash-extend #\e (lambda (chr port) (read-elisp port)))) That's what I was looking for. If you have any links related to the topic of the reader extension, please let me know. > > (set! test-log-to-file #f) > > (define (roundtrip expr) > (let ((written (call-with-output-string (cut elisp-write #f expr <>)))) > (call-with-input-string written read-elisp))) > > (define-syntax test-roundtrip-equals > (syntax-rules () > ((_ expr) > (let ((e1 (roundtrip expr))) > (test-equal e1 (roundtrip e1)))))) > > (define runner (test-runner-simple)) > > (test-with-runner runner > (test-begin "roundtrip-elisp-fixed-point") > (test-roundtrip-equals 12) > (test-roundtrip-equals "hello") > (test-roundtrip-equals '#e#'my-fn) > (test-roundtrip-equals '#e[a b c]) > (test-roundtrip-equals '#e`(+ 1 2 ,@(a b) ,c)) It would be cool to make elisp-unquote for #e, but I think I can take a look at ungexp to understand how to implement it. > (test-end "roundtrip-elisp-fixed-point")) > > (exit (test-runner-fail-count runner)) > --8<---------------cut here---------------end--------------->8--- > > I've also hooked it up in combination with a sequence of calls to > `scheme-file' -> `computed-file' called `elisp-file', but that's a bit > more hacky and less relevant to the current discussion. > > - Jelle Thank you very much!
On 2023-01-31 17:26, Ludovic Courtès wrote: > Hi Andrew, > > Andrew Tropin <andrew@trop.in> skribis: > >>>> I think it will be cool to hook up a custom reader, ideally comment >>>> preserving, for emacs lisp inside scheme files. >>> >>> (guix read-print) is what you want. :-) >>> >> >> Can you give a hint on how to use it for preserving comments, please? > > It can be used like this: > > --8<---------------cut here---------------start------------->8--- > scheme@(guile-user)> ,use(guix read-print) > scheme@(guile-user)> (pretty-print-with-comments (current-output-port) `(list foo ,(comment ";ooh!\n" #t) bar)) > (list foo ;ooh! > bar)$5 = 10 > scheme@(guile-user)> (call-with-input-string "(list foo ;oh!\nbar)" read-with-comments) > $6 = (list foo #<<comment> str: ";oh!\n" margin?: #t> bar) > --8<---------------cut here---------------end--------------->8--- > > There’s a <comment> record type. Yep, I already experimented with it, but it's not exactly what I'm looking for, Jelle gave a few ideas and code snippets which looks closer to what I searching. Pretty printer and read function are useful, but not directly related to my question about reader. > > Now let’s see perhaps what we need to get ‘home-emacs-service-type’ > merged, and what we can keep as future work. Thoughts? We have not perfect, but working quite good implementation of lisp serializer in rde, which we use in rde's home-emacs-service-type: https://git.sr.ht/~abcdw/rde/tree/d0ea604282c9aeb0b121f51979373b4aa40f9bcb/item/tests/rde/serializers/lisp-test.scm https://git.sr.ht/~abcdw/rde/tree/d0ea604282c9aeb0b121f51979373b4aa40f9bcb/item/src/rde/home/services/emacs.scm#L93 But I'm not completely satisfyied with it and don't want to upstream it in current state, I plan to experiment with Jelle's code and get a feeling of how his approach works, also want to dive into reader extensions topic. If I get satisfying solution I will share it. It can take quite a while, so don't expect it to happen anytime soon; it can, but unlikely :) If anyone want to work in parallel and propose alternative implementation, I would be glad to give a feedback on it.
Ludovic Courtès <ludo@gnu.org> writes: > Now let’s see perhaps what we need to get ‘home-emacs-service-type’ > merged, and what we can keep as future work. Thoughts? Sorry for the long delay in getting back to this! To summarize what we've discussed so far: - It's possibly best to use g-expressions for the init.el file contents because it gives ultimate flexibility on how one can describe their Emacs configuration in Scheme while pulling in files from their local configuration folder. Are we concerned whether this may be harder for users to understand and adopt? - We'll postpone any work to ensure that comments and #'function references can be properly represented so that we can get the basic `home-emacs-service' merged for now. Future patches will improve on that situation. Is that accurate? Either way, I'll spend some time today responding to the feedback so that we can get this merged. Thanks! David
Hi David, David Wilson <david@daviwil.com> skribis: > Ludovic Courtès <ludo@gnu.org> writes: > >> Now let’s see perhaps what we need to get ‘home-emacs-service-type’ >> merged, and what we can keep as future work. Thoughts? > > Sorry for the long delay in getting back to this! To summarize what > we've discussed so far: > > - It's possibly best to use g-expressions for the init.el file contents > because it gives ultimate flexibility on how one can describe their > Emacs configuration in Scheme while pulling in files from their local > configuration folder. Are we concerned whether this may be harder for > users to understand and adopt? Ease of use should always be a concern IMO. I’d expect that writing gexps will feel more convenient than in sexps-in-Scheme-strings for the target Elisp audience. > - We'll postpone any work to ensure that comments and #'function > references can be properly represented so that we can get the basic > `home-emacs-service' merged for now. Future patches will improve on > that situation. > > Is that accurate? Either way, I'll spend some time today responding to > the feedback so that we can get this merged. Sounds good to me! Ludo’.
diff --git a/doc/guix.texi b/doc/guix.texi index 751d0957d8..62fefde1ea 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -111,6 +111,7 @@ Copyright @copyright{} 2022 (@* Copyright @copyright{} 2022 John Kehayias@* Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@* Copyright @copyright{} 2023 Giacomo Leidi@* +Copyright @copyright{} 2023 David Wilson@* Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -41061,6 +41062,7 @@ services)}. * Shepherd: Shepherd Home Service. Managing User's Daemons. * SSH: Secure Shell. Setting up the secure shell client. * Desktop: Desktop Home Services. Services for graphical environments. +* Emacs: Emacs Home Services. Services for configuring Emacs. * Guix: Guix Home Services. Services for Guix. @end menu @c In addition to that Home Services can provide @@ -41914,6 +41916,81 @@ The package providing the @code{/bin/dbus-daemon} command. @end table @end deftp +@node Emacs Home Services +@subsection Emacs Home Services + +@defvr {Scheme Variable} home-emacs-service-type +This is the service type for configuring the Emacs text editor. It +enables you to assemble @file{init.el} and @file{early-init.el} files +from snippets in your home configuration and other Emacs Lisp files you +have in your personal configuration folder. + +This service can be extended using the @code{home-emacs-extension} type. + +Note that if you have an existing @file{~/.emacs} and/or +@file{~/.emacs.d}, the configuration aspect of this service will not be +loaded, as the former location takes precedence over +@file{~/.config/emacs}. This service uses the latter path in the +interest of cleanliness. To migrate to the XDG directory, run these +commands: + +@example +$ cp ~/.emacs.d $XDG_CONFIG_HOME/emacs +$ cp ~/.emacs $XDG_CONFIG_HOME/emacs/init.el +@end example +@end defvr + +@deftp {Data Type} home-emacs-configuration +The configuration record for @code{home-emacs-service-type}. + +@table @asis +@item @code{emacs} (default: @code{emacs}) +The package providing the @file{/bin/emacs} command. + +@item @code{packages} (default: @code{'()}) +Additional packages required by the Emacs configuration. + +@item @code{user-emacs-directory} (default: @file{~/.cache/emacs}) +The directory beneath which additional per-user Emacs-specific files are placed. + +@item @code{init-file} (default: @code{'()}) +Configuration text or files to include in @file{init.el}. + +@item @code{early-init-file} (default: @code{'()}) +Configuration text or files to include in @file{early-init.el}. + +@item @code{load-paths} (default: @code{'()}) +Additional load paths to add to Emacs' @code{load-path} variable. Lines +will be inserted at the beginning of @file{early-init.el}. + +@item @code{native-compile?} (default: @code{#f}) +Whether to compile all @code{packages}, using the provided @code{emacs} +package in place of @code{emacs-minimal}, which will enable native +compilation if the @code{emacs} package supports it. All +non-@code{-minimal} Emacs packages at version 28 or above should support +native compilation. +@end table +@end deftp + +@deftp {Data Type} home-emacs-extension +The extension record for @code{home-emacs-service-type}. + +@table @asis +@item @code{packages} (default: @code{'()}) +Additional packages required by the Emacs configuration. + +@item @code{init-file} (default: @code{'()}) +Configuration text or files to include in @file{init.el}. + +@item @code{early-init-file} (default: @code{'()}) +Configuration text or files to include in @file{early-init.el}. + +@item @code{load-paths} (default: @code{'()}) +Additional load paths to add to Emacs' @code{load-path} variable. Lines +will be inserted at the beginning of @file{early-init.el}. +@end table +@end deftp + @node Guix Home Services @subsection Guix Home Services diff --git a/gnu/home/services/emacs.scm b/gnu/home/services/emacs.scm new file mode 100644 index 0000000000..7a821fed8a --- /dev/null +++ b/gnu/home/services/emacs.scm @@ -0,0 +1,185 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2022 ( <paren@disroot.org> +;;; Copyright © 2023 David Wilson <david@daviwil.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu home services emacs) + #:use-module (gnu home services) + #:autoload (gnu packages emacs) (emacs-minimal + emacs) + #:use-module (gnu services configuration) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (guix records) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + + #:export (emacs-variables + home-emacs-configuration + home-emacs-extension + home-emacs-service-type)) + +(define list-of-file-likes? + (list-of file-like?)) + +(define (string-or-file-like? val) + (or (string? val) + (file-like? val))) + +(define list-of-string-or-file-likes? + (list-of string-or-file-like?)) + +(define-configuration/no-serialization home-emacs-configuration + (emacs + (file-like emacs) + "The package providing the @file{/bin/emacs} command.") + (packages + (list-of-file-likes '()) + "Additional packages required by the Emacs configuration.") + (user-emacs-directory + (string "~/.cache/emacs") + "Directory beneath which additional per-user Emacs-specific files are placed.") + (init-file + (text-config '()) + "Configuration text or files to include in init.el.") + (early-init-file + (text-config '()) + "Configuration text or files to include in early-init.el.") + (load-paths + (list-of-string-or-file-likes '()) + "Additional load paths to add to Emacs' @code{load-path} variable. Lines +will be inserted at the beginning of early-init.el.") + (native-compile? + (boolean #f) + "Whether to compile the @code{packages} using the Emacs package +provided as the value of the @code{emacs} field, which will enable +native compilation if the @code{emacs} package supports it.")) + +(define (home-emacs-profile-packages config) + (cons (home-emacs-configuration-emacs config) + (home-emacs-configuration-packages config))) + +(define (home-emacs-transformed-packages config) + (map (if (home-emacs-configuration-native-compile? config) + (package-input-rewriting + `((,emacs-minimal + . ,(home-emacs-configuration-emacs config)))) + identity) + (let ((packages (home-emacs-configuration-packages config))) + (concatenate + (cons packages + (map (compose (cute map second <>) + package-transitive-propagated-inputs) + packages)))))) + +(define (serialize-emacs-load-paths config) + #~(string-append + ";; Additional load paths\n" + #$@(map (lambda (load-path) + #~(format #f "(add-to-list 'load-path \"~a\")" #$load-path)) + (home-emacs-configuration-load-paths config)) + "\n\n")) + +(define (serialize-emacs-user-directory config) + (format #f + ";; Set the `user-emacs-directory` to a writeable path\n(setq user-emacs-directory \"~a\")\n\n" + (home-emacs-configuration-user-emacs-directory config))) + +(define (home-emacs-xdg-configuration-files config) + `(("emacs/early-init.el" + ,(apply mixed-text-file + (cons* "early-init.el" + (serialize-emacs-load-paths config) + (serialize-emacs-user-directory config) + (home-emacs-configuration-early-init-file config)))) + ("emacs/init.el" + ,(apply mixed-text-file + (cons "init.el" + (home-emacs-configuration-init-file config)))))) + +(define-configuration/no-serialization home-emacs-extension + (packages + (list-of-file-likes '()) + "Additional packages required by the Emacs configuration.") + (init-file + (text-config '()) + "Configuration text or files to include in init.el.") + (early-init-file + (text-config '()) + "Configuration text or files to include in early-init.el.") + (load-paths + (list-of-string-or-file-likes '()) + "Additional load paths to add to Emacs' @code{load-path} variable. Lines +will be inserted at the beginning of early-init.el.")) + +(define (home-emacs-extensions original-config extension-configs) + (match-record original-config <home-emacs-configuration> + (packages load-paths init-file early-init-file) + (home-emacs-configuration + (inherit original-config) + (packages + (append packages + (append-map + home-emacs-extension-packages extension-configs))) + (init-file + (append init-file + (append-map + home-emacs-extension-init-file extension-configs))) + (early-init-file + (append early-init-file + (append-map + home-emacs-extension-early-init-file extension-configs))) + (load-paths + (append load-paths + (append-map + home-emacs-extension-load-paths extension-configs)))))) + +(define home-emacs-service-type + (service-type + (name 'home-emacs) + (extensions + (list (service-extension + home-profile-service-type + home-emacs-profile-packages) + (service-extension + home-xdg-configuration-files-service-type + home-emacs-xdg-configuration-files))) + (default-value (home-emacs-configuration)) + (compose identity) + (extend home-emacs-extensions) + (description + "Configure the GNU Emacs extensible text editor."))) + +(define scheme-value->emacs-value + (match-lambda (#t (quote 't)) + (#f (quote 'nil)) + (val val))) + +(define (emacs-variables var-alist) + "Converts an alist of variable names and values into a @code{setq} +expression that can be used in an Emacs configuration. Scheme values +@code{#t} and @code{#f} will be converted into @code{t} and @code{nil}, +respectively." + #~(string-append + "(setq" + #$@(map (lambda (var) + #~(format #f "\n ~a ~s" + (quote #$(car var)) + #$(scheme-value->emacs-value (cdr var)))) + var-alist) + ")\n\n")) diff --git a/gnu/local.mk b/gnu/local.mk index 184f43e753..35d88b4dd6 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -89,6 +89,7 @@ GNU_SYSTEM_MODULES = \ %D%/home/services.scm \ %D%/home/services/desktop.scm \ %D%/home/services/symlink-manager.scm \ + %D%/home/services/emacs.scm \ %D%/home/services/fontutils.scm \ %D%/home/services/guix.scm \ %D%/home/services/pm.scm \