{ config, inputs, lib, pkgs, ... }: let cfg = config.nixfiles.modules.emacs; in { options.nixfiles.modules.emacs.enable = lib.mkEnableOption "GNU Emacs"; config = lib.mkIf cfg.enable { secrets.authinfo = { file = "${inputs.self}/secrets/authinfo"; owner = lib.my.username; }; nixfiles.modules = { common.xdg.defaultApplications.emacsclient = [ "application/atom+xml" "application/json" "application/rss+xml" "application/schema+json" "application/xhtml+xml" "application/xml" "text/csv" "text/plain" ]; git.client.enable = true; gnupg.enable = true; password-store.enable = true; profiles.dev.enable = true; profiles.email.enable = true; }; hm = { stylix.targets.emacs.enable = false; xdg.configFile = lib.mapAttrs ( _: value: value // { onChange = with config.hm.programs; '' export EMACSDIR="''${XDG_CONFIG_HOME:-$HOME/.config}/emacs" export DOOMDIR="''${XDG_CONFIG_HOME:-$HOME/.config}/doom" if [[ ! -d "$EMACSDIR/.git" ]]; then ${lib.getExe git.package} clone --depth=1 --branch=master \ "https://github.com/doomemacs/doomemacs" "$EMACSDIR" fi if [[ ! -d "$DOOMDIR/snippets" ]]; then mkdir -p "$DOOMDIR/snippets" fi if [[ -x "$EMACSDIR/bin/doom" ]]; then if [[ ! -d "$EMACSDIR/.local" ]]; then PATH="''${PATH:-/bin}:${emacs.package}/bin:${git.package}/bin" \ "$EMACSDIR/bin/doom" install --aot --force --verbose fi PATH="''${PATH:-/bin:}:${emacs.package}/bin:${git.package}/bin" \ "$EMACSDIR/bin/doom" sync -e --gc --aot --force --verbose fi ''; } ) { "doom/init.el".source = ./doom/init.el; "doom/packages.el".source = ./doom/packages.el; "doom/config.el" = { text = lib.concatLines [ ( let extraBins = with pkgs; [ (aspellWithDicts ( p: with p; [ en ru ] )) # :checkers (spell +aspell) asmfmt # :editor format shirepyright # :lang (python +lsp) bash-language-server # :lang (sh +lsp) cabal-install # :lang haskell cargo # :lang rust clang-tools # :lang (cc +lsp) :editor format cmake-format # :lang cc :editor format cmigemo # :lang japanese config.hm.programs.emacs.package # !doom config.nix.package # !doom delve # :lang go :tools debugger dockerfile-language-server-nodejs # :tools (docker +lsp) dockfmt # :tools docker :editor format editorconfig-core-c # :tools editorconfig eslint # :lang (json +lsp) fd # doom! gcc # :lang cc gdb # :tools debugger ghc # :lang haskell gnuplot # :lang (org +gnuplot) gnutar # :tools tree-sitter gnutls # doom! :app irc go # :lang go godef # :lang go gomodifytags # :lang go gopls # :lang (go +lsp) gore # :lang go gotests # :lang go gotools # :lang go graphviz # :lang (org +roam2) :lang plantuml gzip # :tools tree-sitter haskell-language-server # :lang (haskell +lsp) haskellPackages.cabal-fmt # :lang haskell :editor format haskellPackages.hoogle # :lang haskell html-tidy # :lang web :editor format jdk # :lang java :lang plantuml :checkers grammar languagetool # :checkers grammar libxml2 # :lang data :editor format markdownlint-cli # :lang markdown nixd # :lang (nix +lsp) nixfmt # :lang nix :editor format nls # :lang (nickel +lsp) nodePackages.js-beautify # :lang web nodePackages.prettier # :editor format nodejs # :tools debugger ormolu # :lang haskell :editor format pandoc # :lang org markdown latex pinentry-emacs # doom! pre-commit # :tools magit python3 # :lang python ripgrep # doom! rust-analyzer # :lang (rust +lsp) rustc # :lang rust rustfmt # :lang rust shellcheck # :lang sh shfmt # :lang sh :editor format sops sqlite # :lang (org +roam2) :tools lookup stylelint # :lang web terraform-ls # :tools (terraform +lsp) texlab # lang (tex +lsp) texlive.combined.scheme-full # :lang org tex tinymist typst unzip # :tools debugger uv # :lang python vscode-langservers-extracted # :lang (json +lsp) (web +lsp) wordnet # :tools (lookup +dictionary +offline) yaml-language-server # :lang (yaml +lsp) zstd # :emacs undo ]; in '' ;; Integrate packages which are required by various modules ;; without polluting the user's profile. (setq exec-path (append exec-path '(${lib.concatMapStringsSep " " (x: ''"${x}/bin"'') extraBins}))) (setenv "PATH" (concat (getenv "PATH") ":${lib.concatMapStringsSep ":" (x: "${x}/bin") extraBins}")) (appendq! auth-sources '(("${config.secrets.authinfo.path}"))) ;; HACK Explicitly load specific Emacs packages from ;; Nixpkgs. For some reason providing them as ;; "extraPackages" doesn't work. (add-to-list 'load-path "${pkgs.mu.mu4e}/share/emacs/site-lisp/mu4e") (add-to-list 'load-path "${pkgs.emacsPackages.vterm}/share/emacs/site-lisp/elpa/vterm-${pkgs.emacsPackages.vterm.version}") (load "${ pkgs.fetchurl { name = "tvl.el"; url = "https://code.tvl.fyi/plain/tools/emacs-pkgs/tvl/tvl.el?id=84a82f6f41b422f7f76e44e3705ee509b6c6eaf6"; hash = "sha256-vJsV7w4aMV9Y9zbXLHMIR53xAsXdUVBIXrxUTjdvty0="; } }") ;; :input japanese (setq migemo-dictionary "${pkgs.cmigemo}/share/migemo/utf-8/migemo-dict" skk-large-jisyo "${pkgs.skkDictionaries.l}/share/skk/SKK-JISYO.L") ;; :editor parinfer (setq parinfer-rust-library "${pkgs.parinfer-rust-emacs}/lib/libparinfer_rust.so") ;; :lang (org +roam2) :email mu4e (setq emacsql-sqlite-executable "${lib.getExe pkgs.emacsql-sqlite}") ;; :lang plantuml (setq plantuml-jar-path "${pkgs.plantuml}/lib/plantuml.jar" plantuml-executable-path "${lib.getExe' pkgs.plantuml "plantuml"}" org-plantuml-jar-path plantuml-jar-path org-plantuml-executable-path plantuml-executable-path) ;; :app irc (setq circe-default-nick "${lib.my.username}" circe-default-realname "${lib.my.email}" circe-default-user circe-default-nick) '' ) (with config.stylix.fonts; '' (setq doom-font "${monospace.name}-${toString sizes.terminal}" doom-serif-font "${serif.name}-${toString sizes.terminal}" doom-variable-pitch-font "${sansSerif.name}-${toString sizes.terminal}" doom-emoji-font "${emoji.name}-${toString sizes.terminal}") '') ( let mu4eAccounts = config.hm.accounts.email.accounts |> lib.attrValues |> lib.filter (x: x.mu.enable) |> lib.concatMapStringsSep "\n" (a: '' (set-email-account! "${a.name}" '((user-full-name . "${a.realName}") (user-mail-address . "${a.address}") (mu4e-inbox-folder . "/${a.name}/${a.folders.inbox}") (mu4e-sent-folder . "/${a.name}/${a.folders.sent}") (mu4e-drafts-folder . "/${a.name}/${a.folders.drafts}") (mu4e-trash-folder . "/${a.name}/${a.folders.trash}") (mu4e-refile-folder . "/${a.name}/Archive") ${lib.optionalString (a.signature.showSignature != "none") ''(mu4e-compose-signature . "${lib.replaceStrings [ "\n" ] [ "\\n" ] a.signature.text}")'' } (+mu4e-personal-addresses . (${lib.concatMapStringsSep " " (x: ''"${x}"'') a.aliases}))) t) ''); in '' (setq mu4e-root-maildir "${config.hm.accounts.email.maildirBasePath}") ${mu4eAccounts} '' ) (builtins.readFile ./doom/config.el) ]; }; }; programs = { emacs = { enable = true; package = pkgs.emacs29-pgtk; }; bash.initExtra = lib.mkAfter '' export PATH="$PATH:$XDG_CONFIG_HOME/emacs/bin" # https://github.com/akermu/emacs-libvterm?tab=readme-ov-file#shell-side-configuration if [[ "$INSIDE_EMACS" = vterm ]] && [[ -n "$EMACS_VTERM_PATH" ]] && [[ -f "$EMACS_VTERM_PATH/etc/emacs-vterm-bash.sh" ]]; then source "$EMACS_VTERM_PATH/etc/emacs-vterm-bash.sh" fi # Not sourced from inside Emacs for some reason. Maybe it's not # considered an interactive shell? [[ -f ~/.profile ]] && . ~/.profile ''; }; }; }; }