Andrew Tropin: 1 rde: features: emacs: Add emacs and various aux features for it 2 files changed, 250 insertions(+), 0 deletions(-)
--- The idea is to make it possible to get the whole emacs configuration by composing small rde features, this approach is similiar to spacemacs layers or doom emacs modules, but have a few advantages over it: 1. It possible to install and configure not only emacs packages, but also system or home packages. So such emacs configuration will be self-contained and won't require any additional setup steps.
Would this mean that there could potentially be hundreds of ‘feature-emacs-*’ features for all the different packages?
2. It will be possible to rely on some other non-emacs rde features, for example feature-emacs-transmission, can check if feature-transmission is present and take the host and port from it. The same approach will work for other relatively complicated setups involving few different software tools to interact with each other (emails fetcher, indexer, password manager, emacs).
This is nice, it would be cool to have this functionality merged in Guix itself. I am not familiar with the source code of (rde features) so I don’t know how feasible this is.
3. Everything is done in declarative and reproducible way. For now I decided to generate a package with autoloads for storing feature-specific emacs configuration and making it loaded on emacs startup, it's possible to place the same code without autoloads in init.el, but I decided to try this approach, because I want to experiment with project-specific emacs subconfigurations in the future and it will be easier if all configurations will be split into packages and won't require to edit any files under XDG_CONFIG_HOME or similar places.
I think being able to configure Emacs through Guile is cool, but problem is that unlike most other programs, Emacs is very extensible. With something like GnuPG or Git, you set some options in ~/.config/git/config or something, and that’s basically all you can do. With Emacs, you can customize anything you want, the sky is the limit. I have left some comments that elaborate a bit more on this.
rde/examples/abcdw/configs.scm | 11 ++ rde/features/emacs.scm | 239 +++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 rde/features/emacs.scm diff --git a/rde/examples/abcdw/configs.scm b/rde/examples/abcdw/configs.scm index d0251bb..6f70bab 100644 --- a/rde/examples/abcdw/configs.scm +++ b/rde/examples/abcdw/configs.scm @@ -13,6 +13,7 @@ #:use-module (rde features tmux) #:use-module (rde features shells) #:use-module (rde features ssh) + #:use-module (rde features emacs) #:use-module (gnu system file-systems) #:use-module (gnu system mapped-devices) #:use-module (gnu packages) @@ -78,6 +79,16 @@ (feature-sway-run-on-tty #:sway-tty-number 2) + (feature-emacs + #:additional-elisp-packages + (pkgs "emacs-guix" "emacs-telega" "emacs-pdf-tools" "emacs-yasnippet")) + (feature-emacs-org-mode) + (feature-emacs-magit) + (feature-emacs-faces) + (feature-emacs-completion) + (feature-emacs-org-roam + #:org-roam-directory "~/work/notes/notes") + (feature-base-services) (feature-xdg-base-directories) (feature-base-packages diff --git a/rde/features/emacs.scm b/rde/features/emacs.scm new file mode 100644 index 0000000..7af068c --- /dev/null +++ b/rde/features/emacs.scm @@ -0,0 +1,239 @@ +(define-module (rde features emacs) + #:use-module (rde features) + #:use-module (rde features predicates) + #:use-module (rde emacs packages) + #:use-module (gnu home-services emacs) + #:use-module (gnu home-services-utils) + #:use-module (gnu services) + #:use-module (gnu packages emacs-xyz) + #:use-module (guix gexp) + + #:export (feature-emacs + feature-emacs-org-mode + feature-emacs-magit + feature-emacs-faces + feature-emacs-completion + feature-emacs-org-roam)) + +(define* (feature-emacs + #:key + (emacs-server-mode? #t) + (additional-elisp-packages '())) + "Setup and configure GNU Emacs." + (ensure-pred boolean? emacs-server-mode?) + (ensure-pred list-of-elisp-packages? additional-elisp-packages) + + (define (emacs-home-services config) + "Returns home services related to GNU Emacs." + (require-value 'full-name config) + (require-value 'email config) + (let* ((full-name (get-value 'full-name config)) + (email (get-value 'email config))) + (list + (service + home-emacs-service-type + (home-emacs-configuration + (package emacs-next-pgtk-latest) + (elisp-packages (append + additional-elisp-packages + (list emacs-modus-themes))) + (server-mode? emacs-server-mode?) + (xdg-flavor? #f) + (init-el + `((setq user-full-name ,full-name) + (setq user-mail-address ,email) + ,#~"" + (setq custom-file + (concat (or (getenv "XDG_CACHE_HOME") "~/.cache") + "/emacs/custom.el")) + (load custom-file t) + ,#~"" + (load-theme 'modus-operandi t) + ,#~"" + (setq message-auto-save-directory + (concat (or (getenv "XDG_CACHE_HOME") "~/.cache") + "/emacs/mail-drafts")) + ,#~"")) + (early-init-el + `(,(slurp-file-gexp (local-file "../emacs/early-init.el")))) + ;;; TODO: Rebuilding packages with emacs will be useful for + ;;; native-comp, but for some reason dash.el fails to build, + ;;; need to investigate the issue. + ;; (rebuild-elisp-packages? #t) + ))))) + + (feature + (name 'emacs) + (values (append + '((emacs . #t)) + (make-feature-values emacs-server-mode?))) + (home-services-getter emacs-home-services))) + +(define* (feature-emacs-org-mode) + "Configure org-mode for GNU Emacs." + (define (emacs-org-mode-home-services config) + "Returns home services related to org-mode." + (let* ((configure-org-mode + (elisp-configuration-package + "configure-org-mode" + `(,#~";;;###autoload" + (with-eval-after-load + 'org + (progn + (setq org-adapt-indentation nil) + (setq org-edit-src-content-indentation 0) + (setq org-startup-indented t))))))) + (list + (simple-service + 'emacs-org-mode-configurations + home-emacs-service-type + (home-emacs-extension + (elisp-packages (list emacs-org configure-org-mode))))))) + + (feature + (name 'emacs-org-mode) + (values `((emacs-org-mode . #t))) + (home-services-getter emacs-org-mode-home-services)))
How would one override the values set by ‘feature-emacs-org-mode’ for example? Say a user doesn’t want to set ‘org-startup-indented‘ to ‘t‘. If one still has to set things in init.el I feel like it kinda defeats the purpose of using Guile to configure Emacs, you now have to manage one part of the config in Guile, and another in Elisp. This is the reason I never used Nix Home Manager to configure Emacs (my init.el is over 2000 loc), I only used it to install Emacs packages.
+ +(define* (feature-emacs-magit) + "Configure Magit for GNU Emacs." + (define (emacs-magit-home-services config) + "Returns home services related to Magit." + (require-value 'git config) + (list + (simple-service + 'emacs-magit-configurations + home-emacs-service-type + (home-emacs-extension + (elisp-packages (list emacs-magit)))))) + + (feature + (name 'emacs-magit) + (values `((emacs-magit . #t))) + (home-services-getter emacs-magit-home-services)))
This ‘feature’ only seems to install Magit, with things like Spacemacs or Doom Emacs, they generally pre-configure a lot of things and setup “sensible defaults”. Is the plan to create a more opinionated config, or should the user be expected to configure additional things?
+ + +;; TODO: Move font record to apropriate module +(use-modules (rde features fontutils)) + +;; TODO: Can be useful to have different presets for different +;; environments. For easier and faster switching. +(define* (feature-emacs-faces) + "Configure faces for GNU Emacs." + (define (emacs-faces-home-services config) + "Returns home services related to faces." + (require-value 'fonts config) + (let* ((font-monospace (get-value 'font-monospace config)) + (font-sans (get-value 'font-sans config)) + + (configure-faces + (elisp-configuration-package + "configure-faces" + `(,#~";;;###autoload" + (with-eval-after-load + 'faces + (let* ((mono-fn ,(font-name font-monospace)) + (sans-fn ,(font-name font-sans)) + (mono (font-spec + :name ,(font-name font-monospace) + :size ,(font-size font-monospace) + :weight ',(or (font-weight font-monospace) 'normal))) + ;; For people coming here years later, only + ;; face which can contain size or integer + ;; height is default, everything else should + ;; set only family or relative height + ;; (decimal value), the font-spec even + ;; without height/size shouldn't be used. + ;; Otherwise text-adjust and other stuff can + ;; be broken. + (faces `((default ((t (:font ,mono)))) + (fixed-pitch ((t (:family ,mono-fn)))) + (button ((t (:inherit (fixed-pitch))))) + (variable-pitch ((t (:family ,sans-fn)))))))
How would the user change the font size, or what if they wanted ‘variable-pitch’ to be the same as the default font?
+ (dolist (face faces) + (custom-set-faces face)) + + (dolist (face faces) + (put (car face) 'saved-face nil)))))))) + + (list + (simple-service + 'emacs-fonts-configurations + home-emacs-service-type + (home-emacs-extension + (elisp-packages (list configure-faces))))))) + + (feature + (name 'emacs-faces) + (values `((emacs-faces . #t))) + (home-services-getter emacs-faces-home-services))) + +(define* (feature-emacs-completion) + "Configure completion system for GNU Emacs." + (define (emacs-completion-home-services config) + "Returns home services related to Emacs completion system." + (let* ((configure-completion + (elisp-configuration-package + "configure-completion" + `(,#~";;;###autoload" + (with-eval-after-load + 'minibuffer + (setq completion-styles '(orderless))
This sets the completion mechanism to Orderless’, but it doesn’t seem to allow the user to configure ‘orderless-style-dispatchers’. This would mean that users would have to manually ‘orderless-style-dispatchers’ in their init.el, which means that they now have to manage their Emacs config in separate files.
+ (setq savehist-file + (concat (or (getenv "XDG_CACHE_HOME") "~/.cache") + "/emacs/history")) + (savehist-mode 1))))))
What if the user doesn’t want to enable ‘savehist-mode’? How would the user override things? I think you get the point...
+ + (list + (simple-service + 'emacs-completion-configurations + home-emacs-service-type + (home-emacs-extension + (elisp-packages (list configure-completion emacs-orderless))))))) + + (feature + (name 'emacs-completion) + (values `((emacs-completion . #t))) + (home-services-getter emacs-completion-home-services))) + +(define* (feature-emacs-org-roam + #:key + (org-roam-directory #f)) + "Configure org-rome for GNU Emacs." + (define (not-boolean? x) (not (boolean? x))) + (ensure-pred not-boolean? org-roam-directory) + + (define (emacs-org-roam-home-services config) + "Returns home services related to org-roam." + (let* ((configure-org-roam + (elisp-configuration-package + "configure-org-roam" + `(,#~";;;###autoload" + (progn + (add-hook 'after-init-hook 'org-roam-mode) + (with-eval-after-load + 'org-roam + (define-key org-roam-mode-map + (kbd "C-c n n") 'org-roam) + (define-key org-roam-mode-map + (kbd "C-c n f") 'org-roam-find-file) + (define-key org-mode-map + (kbd "C-c n i") 'org-roam-insert) + (setq org-roam-directory ,org-roam-directory))))))) + + (list + (simple-service + 'emacs-completion-configurations + home-emacs-service-type + (home-emacs-extension + (elisp-packages (list configure-org-roam emacs-org-roam))))))) + + (feature + (name 'emacs-org-roam) + (values `((emacs-org-roam . #t))) + (home-services-getter emacs-org-roam-home-services))) + +;; TODO: feature-emacs-reasonable-keybindings
What does this mean? How does one define a “reasonable keybinding”? I think the default Emacs bindings are okay, but I much prefer Evil mode. The name of this feature doesn’t actually convey any useful information.rde emacs will be using vanilla keybinding, but on top of it it will be providing some consistent and adequate bindings, like it done in feature-org-roam, I think feature-emacs-reasonable-keybindings will allow to toggle this behavior (make other features aware that user don't want us to bind anything for him) and also provide binding, which are not belong to other features.I feel like Jelle’s propsal for Guix Home Manager is a better implementation, it is basically just Guile bindings for Emacs use-package. This would also mean a lot less work for maintainers, and say if someone wants to add a ‘feature-emacs-python’ who would decide what the resonable defaults would be? If people don’t like the defaults, they might just not use the feature, meaning that very few people would benefitting from the feature.Hope to this moment the answers to this questions and comments is already clear.I apologize if this got a bit ranty. Just wanted to express my opinion. :) : https://framagit.org/tyreunom/guix-home-manager/-/merge_requests/7