{"id":13675963,"url":"https://github.com/hiecaq/guix-config","last_synced_at":"2025-04-28T23:31:28.537Z","repository":{"id":114846245,"uuid":"586472175","full_name":"hiecaq/guix-config","owner":"hiecaq","description":"My literate configuration for Guix","archived":false,"fork":false,"pushed_at":"2025-04-20T01:48:26.000Z","size":1865,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-20T02:48:42.876Z","etag":null,"topics":["dotfiles","guix","guix-configuration","guix-home","literate-programming"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hiecaq.png","metadata":{"files":{"readme":"readme.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-01-08T09:07:26.000Z","updated_at":"2025-04-20T01:48:29.000Z","dependencies_parsed_at":"2025-04-21T09:31:43.863Z","dependency_job_id":null,"html_url":"https://github.com/hiecaq/guix-config","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiecaq%2Fguix-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiecaq%2Fguix-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiecaq%2Fguix-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hiecaq%2Fguix-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hiecaq","download_url":"https://codeload.github.com/hiecaq/guix-config/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251404499,"owners_count":21584100,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotfiles","guix","guix-configuration","guix-home","literate-programming"],"created_at":"2024-08-02T12:01:06.795Z","updated_at":"2025-04-28T23:31:28.519Z","avatar_url":"https://github.com/hiecaq.png","language":null,"funding_links":[],"categories":["Config Examples"],"sub_categories":["Issue tracker"],"readme":"# -*- org-use-property-inheritance: t; toc-org-max-depth: 4; org-confirm-babel-evaluate: nil; -*-\n#+title: hiecaq's guix configuration\n#+startup: indent\n#+property: header-args :comments org :results silent :mkdirp t\n\n#+toc: headlines 2\n\n* Table of Contents :TOC:noexport:\n- [[#introduction][Introduction]]\n- [[#system-configuration][System Configuration]]\n  - [[#private-info][Private Info]]\n  - [[#kernel-firmware-and-initial-ram-disk][Kernel, Firmware and Initial RAM Disk]]\n  - [[#bootloader][Bootloader]]\n  - [[#file-system][File System]]\n    - [[#btrfs-subvolumes][BTRFS subvolumes]]\n    - [[#tmp][TMP]]\n    - [[#efi][EFI]]\n    - [[#device-ids][Device IDs]]\n  - [[#users][Users]]\n  - [[#packages][Packages]]\n  - [[#services][Services]]\n    - [[#smart-card][Smart Card]]\n    - [[#game-controllers][Game Controllers]]\n    - [[#android][Android]]\n    - [[#hardware-monitor][Hardware monitor]]\n  - [[#default-services-modification][Default Services Modification]]\n    - [[#guix][Guix]]\n    - [[#gdm][GDM]]\n    - [[#audio][Audio]]\n- [[#home-configuration][Home Configuration]]\n- [[#guix-1][Guix]]\n  - [[#locales][Locales]]\n  - [[#channels][Channels]]\n    - [[#nonguix][Nonguix]]\n- [[#cross-desktop-group-xdg][Cross-Desktop Group (XDG)]]\n  - [[#base-directories][Base Directories]]\n  - [[#user-directories][User Directories]]\n- [[#shells][Shells]]\n  - [[#fish][Fish]]\n  - [[#tools][Tools]]\n    - [[#certificates][Certificates]]\n    - [[#bat][bat]]\n    - [[#eza][eza]]\n    - [[#ripgrep][ripgrep]]\n    - [[#fd][fd]]\n    - [[#direnv][Direnv]]\n  - [[#aliases][Aliases]]\n- [[#fonts][Fonts]]\n- [[#search][Search]]\n  - [[#system-service][System service]]\n- [[#desktop-environment][Desktop Environment]]\n  - [[#display][Display]]\n  - [[#d-bus][D-Bus]]\n  - [[#xdg-desktop-portal][XDG Desktop Portal]]\n  - [[#audio-and-sound][Audio and Sound]]\n- [[#emacs][Emacs]]\n  - [[#basics][Basics]]\n  - [[#early-initialization][Early Initialization]]\n    - [[#packages-1][Packages]]\n    - [[#special-key-remapping][Special Key Remapping]]\n    - [[#fonts-1][Fonts]]\n    - [[#some-configurations-that-might-make-sense-to-put-here][Some Configurations that might make sense to put here]]\n  - [[#main-configurations][Main Configurations]]\n    - [[#packages-2][Packages]]\n    - [[#setupel][setup.el]]\n    - [[#some-sane-configurations][Some Sane Configurations]]\n      - [[#android-1][Android]]\n    - [[#window-management][Window Management]]\n    - [[#universal-argument][Universal Argument]]\n    - [[#pcre][PCRE]]\n    - [[#help][Help]]\n    - [[#info][Info]]\n    - [[#xdg][Xdg]]\n    - [[#no-littering][No Littering]]\n    - [[#modus-themes][Modus Themes]]\n    - [[#mode-line][Mode Line]]\n    - [[#midnight][Midnight]]\n    - [[#auto-save][Auto Save]]\n    - [[#recentf][Recentf]]\n    - [[#save-history][Save History]]\n    - [[#editorconfig][Editorconfig]]\n    - [[#envrc][Envrc]]\n    - [[#subword][Subword]]\n    - [[#highlight-parentheses][Highlight Parentheses]]\n    - [[#transient][Transient]]\n    - [[#evil][Evil]]\n      - [[#noun-verb-editing][Noun-Verb Editing]]\n      - [[#move-up-and-down-a-list][Move up and down a list]]\n      - [[#evil-surround][Evil Surround]]\n      - [[#evil-replace-with-register][Evil Replace With Register]]\n      - [[#evil-snipe][Evil Snipe]]\n      - [[#evil-commentary][Evil Commentary]]\n      - [[#window-map][Window map]]\n    - [[#god-mode][God mode]]\n    - [[#which-key][Which key]]\n    - [[#posframe][Posframe]]\n    - [[#eldoc][Eldoc]]\n    - [[#ace-window][Ace Window]]\n    - [[#spell-checking][Spell Checking]]\n      - [[#flyspell-correct][Flyspell Correct]]\n    - [[#xref][Xref]]\n    - [[#topsy][Topsy]]\n    - [[#orderless][Orderless]]\n    - [[#vertico][Vertico]]\n    - [[#marginalia][Marginalia]]\n    - [[#consult][Consult]]\n    - [[#embark][Embark]]\n      - [[#consult-based-embark-cycle][Consult-based Embark Cycle]]\n      - [[#embark-as-the-leader-key][Embark as the leader key]]\n      - [[#open-in-chosen-window][Open in chosen window]]\n      - [[#keybindings-for-copy][Keybindings for copy]]\n      - [[#copy-file-name-with-line-number][Copy file name with line number]]\n    - [[#tempel][Tempel]]\n      - [[#eglot-tempel][Eglot-tempel]]\n    - [[#corfu][Corfu]]\n    - [[#visual-undo][Visual Undo]]\n    - [[#hideshow][Hideshow]]\n    - [[#pulse][Pulse]]\n    - [[#electric-pair-mode][electric-pair-mode]]\n    - [[#aggresive-indent][Aggresive Indent]]\n    - [[#eshell][Eshell]]\n      - [[#fish-completion][fish-completion]]\n    - [[#ediff][Ediff]]\n    - [[#magit][Magit]]\n    - [[#project][Project]]\n    - [[#emacsql][Emacsql]]\n    - [[#epub][Epub]]\n    - [[#pdf][Pdf]]\n    - [[#org-mode][Org Mode]]\n      - [[#general-settings][General Settings]]\n      - [[#task-management][Task Management]]\n      - [[#literate-programming][Literate Programming]]\n      - [[#evil-org][Evil Org]]\n      - [[#toc-org][Toc Org]]\n      - [[#org-appear][Org Appear]]\n      - [[#personal-knowledge-management][Personal Knowledge Management]]\n      - [[#style-and-faces][Style and Faces]]\n    - [[#english][English]]\n      - [[#linting][Linting]]\n      - [[#capitalizing][Capitalizing]]\n      - [[#dictionary][Dictionary]]\n    - [[#eglot][Eglot]]\n    - [[#haskell][Haskell]]\n    - [[#rust][Rust]]\n    - [[#dhall][Dhall]]\n    - [[#ron][Ron]]\n    - [[#dart][Dart]]\n    - [[#plantuml][PlantUML]]\n    - [[#yaml][YAML]]\n    - [[#email][Email]]\n      - [[#mu4e][mu4e]]\n    - [[#mpv][MPV]]\n    - [[#pyim][PYIM]]\n    - [[#vterm][vterm]]\n    - [[#eat][Eat]]\n    - [[#dired][Dired]]\n      - [[#dired-rsync][dired-rsync]]\n    - [[#emms][EMMS]]\n    - [[#guix-1][Guix]]\n    - [[#desktop-notification-daemon][Desktop Notification Daemon]]\n- [[#references-and-recommendations][References and Recommendations]]\n\n* Introduction\nThis is my all-in-one [[https://guix.gnu.org/][Guix]] configuration, working in progress. This aims to eventually replace and deprecate [[https://github.com/hiecaq/dotfiles][my dotfiles]], which has too many historical burdens.\n\nUnless explicitly stated, all code in this configuration is under GPL3 license.\n\n* System Configuration\nThis is the main entry point for =guix system=. It can be deployed with\n#+begin_src sh\n  sudo guix system -L build reconfigure build/system-configuration.scm\n#+end_src\n\n#+begin_src scheme :tangle \"build/system-configuration.scm\" :noweb yes\n  (use-modules (gnu)\n               (guix channels)\n               (gnu system nss)\n               (gnu packages haskell)\n               (hiecaq services search)\n               \u003c\u003csystem-module\u003e\u003e\n               )\n\n  (use-service-modules desktop ssh xorg linux security-token sound)\n  (use-package-modules bootloaders fonts games android)\n\n  \u003c\u003csystem-helper\u003e\u003e\n\n  (define %channels\n    (cons*\n     \u003c\u003cguix-channel\u003e\u003e\n     %default-channels))\n\n  (operating-system\n    \u003c\u003coperating-system\u003e\u003e\n\n    (users (cons*\n            \u003c\u003coperating-system-users\u003e\u003e\n            %base-user-accounts))\n\n    (packages (cons*\n               \u003c\u003csystem-packages\u003e\u003e\n               %base-packages))\n\n    (services (cons*\n               \u003c\u003csystem-services\u003e\u003e\n               (modify-services %desktop-services\n                 \u003c\u003csystem-services-modify\u003e\u003e)))\n    ;; Allow resolution of '.local' host names with mDNS.\n    (name-service-switch %mdns-host-lookup-nss))\n#+end_src\n\n** Private Info\nHost name shows up in some places, and I'm not good at naming so I just call it \"desktop\". \n#+begin_src scheme :noweb-ref operating-system\n  (host-name \"desktop\")\n#+end_src\n\nTime zone.  All Linuxes should be synchronized to the same UTC time, and local time is then displayed based on the time zone.\n#+begin_src scheme :noweb-ref operating-system\n  (timezone \"Etc/UTC\")\n#+end_src\n\nNowadays locale should be just UTF-8.\n#+begin_src scheme :noweb-ref operating-system\n    (locale \"en_US.utf8\")\n#+end_src\n\nI use standard keyboard layout too.\n#+begin_src scheme :noweb-ref operating-system\n    (keyboard-layout (keyboard-layout \"us\"))\n#+end_src\n\n** Kernel, Firmware and Initial RAM Disk\nI must confess that I have to use non-liberal firmware.\n#+begin_src scheme :noweb-ref system-module\n  (nongnu packages linux)\n  (nongnu system linux-initrd)\n#+end_src\n\nThen the kernel and firmware are from nonguix channel:\n#+begin_src scheme :noweb-ref operating-system\n  (kernel linux)\n  (initrd microcode-initrd)\n  (firmware (list linux-firmware))\n#+end_src\n\nI use =xxhash= for btrfs checksum, so the corresponding modules to be added to the Initial Ram Disk modules.  See [[https://old.reddit.com/r/btrfs/comments/17ksj0w/installing_debian_with_xxhash/][here]].\n#+begin_src scheme :noweb-ref operating-system\n  (initrd-modules (append\n                   (list \"xxhash\" \"xxhash_generic\")\n                   %base-initrd-modules))\n#+end_src\n\n** Bootloader\nuse UEFI flavored GRUB.  Note that the mount point is =/efi= (instead of =/boot/efi=), as suggested by the [[https://wiki.archlinux.org/title/EFI_system_partition#Typical_mount_points][Arch Wiki]].\n#+begin_src scheme :noweb-ref operating-system\n  (bootloader (bootloader-configuration\n               (bootloader grub-efi-bootloader)\n               (targets '(\"/efi\"))\n               (keyboard-layout keyboard-layout)))\n#+end_src\n\n** File System\nSee [[https://www.hiecaq.org/posts/20241024T234951M098.html][my blog post]] for details on how I partitioned / formatted the file system.  This preparation cannot be done from Guix system declaration itself and has to be done manually (or with shell scripts).\n\nfirstly, we need to declare the mapped devices, which handles our LUKS-encrypted device.\n#+begin_src scheme :noweb yes :noweb-ref operating-system\n  (mapped-devices\n   (list (mapped-device\n          (source (uuid \u003c\u003csystem-luks-device-uuid\u003e\u003e))\n          (target \"cryptbtrfs\")\n          (type luks-device-mapping))))\n#+end_src\n\nThen actual file systems are listed in this field: \n#+begin_src scheme :noweb yes :noweb-ref operating-system\n  (file-systems (append\n                 (list\n                  \u003c\u003cfile-system\u003e\u003e\n                  )\n                 %base-file-systems))\n#+end_src\n\n*** BTRFS subvolumes\nTo begin with, since most things are on the encrypted btrfs partition, it is important to understand when to make subvolumes.  As far as I can tell, the main decision factor is if you want to back up the subvolume separately, or simply to exclude some subvolumes from the backups.\n\nThus, mostly everything under =/=, exclude =/home=, is managed by Guix, so seldom they need to be backed up.  The exceptions here I made are:\n- =/home= :: as mentioned, I want it to be backed-up seperately\n- =/swap= :: who need to back up swap?\n- =/var/log= :: system logs, can be backed up often\n- =/var/cache= and =/var/tmp= :: These are either temporary or can be regenerated, so no need to back up\n#+begin_src scheme :noweb-ref file-system\n  (%btrfs \"@\" \"/\" mapped-devices)\n  (%btrfs \"@home\" \"/home\" mapped-devices)\n  (%btrfs \"@swap\" \"/swap\" mapped-devices)\n  (%btrfs \"@log\" \"/var/log\" mapped-devices)\n  (%btrfs \"@cache\" \"/var/cache\" mapped-devices)\n  (%btrfs \"@tmp\" \"/var/tmp\" mapped-devices)\n#+end_src\n\nNext, since I manage my home with guix home, most things do not need to be backed up either.  The exceptions are the \"well known\" user directories.\n#+begin_src scheme :noweb-ref file-system\n  (%btrfs \"@documents\" \"/home/hiecaq/documents\" mapped-devices)\n  (%btrfs \"@downloads\" \"/home/hiecaq/downloads\" mapped-devices)\n  (%btrfs \"@videos\" \"/home/hiecaq/videos\" mapped-devices)\n  (%btrfs \"@music\" \"/home/hiecaq/music\" mapped-devices)\n  (%btrfs \"@pictures\" \"/home/hiecaq/pictures\" mapped-devices)\n#+end_src\n\nThe above ~%btrfs~ is defined as\n#+begin_src scheme :noweb-ref system-helper \n  (define* (%btrfs subvol mount-point dep #:key\n                   (flags '(no-atime)))\n    (file-system\n     (device \"/dev/mapper/cryptbtrfs\")\n     (mount-point mount-point)\n     (type \"btrfs\")\n     (flags flags)\n     (options (string-append \"compress=zstd,subvol=\" subvol))\n     (dependencies dep)))\n#+end_src\n\nImportant things are:\n- =no-atime=, which is good for btrfs' CoW\n- =compress=zstd=, that is to enable btrfs' transparent compression with Zstandard algorithm.\n\n*** TMP\nGuix does not mount =/tmp= as =tmpfs= in ~%base-file-systems~ by default.  The reason is that Guix uses =/tmp= to build things, and the temporary build files can be too large to be contained in RAM, see [[https://logs.guix.gnu.org/guix/2024-10-27.log#080317][Rutherther's reply to me]].  However, my desktop's RAM is large enough, so\n#+begin_src scheme :noweb-ref file-system\n  (file-system\n   (device \"tmpfs\")\n   (mount-point \"/tmp\")\n   (type \"tmpfs\")\n   (flags '(no-suid no-dev))\n   (options \"size=80%\")\n   (check? #f))\n#+end_src\n\n*** EFI\nThe last thing is to mount the EFI system partition:\n#+begin_src scheme :noweb yes :noweb-ref file-system\n  (file-system\n   (device (uuid \u003c\u003csystem-efi-device-uuid\u003e\u003e 'fat))\n   (mount-point \"/efi\")\n   (type \"vfat\"))\n#+end_src\n\n*** Device IDs\nThis is what's displayed in =lsblk -f -o UUID /dev/nvme0n1p2=\n#+name: system-luks-device-uuid\n#+begin_src scheme :exports none\n  \"5e797e8b-d3b9-4693-b2f9-c17cf0943c34\"\n#+end_src\n\nThis is what's displayed in =lsblk -o UUID /dev/nvme0n1p1=\n#+name: system-efi-device-uuid\n#+begin_src scheme :exports none\n  \"B09A-E98C\"\n#+end_src\n\n** Users\nI only have one explicit user:\n#+begin_src scheme :noweb-ref operating-system-users\n  (user-account\n   (name \"hiecaq\")\n   (group \"users\")\n   (supplementary-groups '(\"wheel\" \"netdev\"\n                           \"input\" \"audio\" \"video\"\n                           \"plocate\" \"adbusers\")))\n#+end_src\n\n** Packages\n:PROPERTIES:\n:header-args:scheme: :noweb-ref system-packages\n:END:\nCurrently just a list of fonts:\n#+begin_src scheme\n  font-hack\n  font-google-noto\n  font-google-noto-emoji\n  font-google-noto-sans-cjk\n#+end_src\n\n** Services\n:PROPERTIES:\n:header-args:scheme: :noweb-ref system-services\n:END:\n\n*** Smart Card\n#+begin_src scheme\n  (service pcscd-service-type)\n#+end_src\n\n*** Game Controllers\nWe need the udev rules provided by Steam:\n#+begin_src scheme\n  (udev-rules-service 'steam steam-devices-udev-rules)\n#+end_src\n\n*** Android\nTo connect to Android devices, we need the following udev rule.  It requires the user to be in =adbusers= group to use =adb= / =fastboot= without root privilege.  We can automatically create the group here.\n#+begin_src scheme\n  (udev-rules-service 'android android-udev-rules\n                      #:groups '(\"adbusers\"))\n#+end_src\n\n*** Hardware monitor\nTo show the system fan speeds, my device need this kernel module:\n#+begin_src scheme\n  (service kernel-module-loader-service-type\n           '(\"nct6683\"))\n#+end_src\n\n\n** Default Services Modification\n:PROPERTIES:\n:header-args:scheme: :noweb-ref system-services-modify\n:END:\n\n*** Guix\nWe modify the system-wide channels (which will be used if a user does not have a channel list themselves) and add the substitute for Nonguix.\n#+begin_src scheme\n  (guix-service-type\n   config =\u003e (guix-configuration\n              (inherit config)\n              (substitute-urls %substitute-urls)\n              (channels %channels)\n              (authorized-keys\n               (append (list %nonguix-signing-key)\n                       %default-authorized-guix-keys))))\n#+end_src\n\n#+begin_src scheme :noweb-ref system-helper\n  (define %substitute-urls\n    '(\"https://ci.guix.gnu.org\"\n      \"https://substitutes.nonguix.org\"))\n\n  (define %nonguix-signing-key\n    (plain-file \"nonguix.pub\" \"\\\n  (public-key\n   (ecc\n    (curve Ed25519)\n    (q #C1FD53E5D4CE971933EC50C9F307AE2171A2D3B52C804642A7A35F84F3A4EA98#)))\"))\n#+end_src\n\n*** GDM\nSomehow after log-in my mouse cursor disappears.  We need to add a configuration to =/etc/X11/xorg.conf.d/= to fix it, here is how to do that in Guix's way:\n#+begin_src scheme\n  (gdm-service-type\n   config =\u003e (gdm-configuration\n              (inherit config)\n              (auto-suspend? #f)\n              (xorg-configuration\n               (let ((xorg-config ((@@(gnu services xorg) gdm-configuration-xorg) config)))\n                 (xorg-configuration\n                  (inherit xorg-config)\n                  (extra-config (list %fix-amd-disappearing-cursor-conf)))))))\n#+end_src\n\nThe fix is from https://forums.linuxmint.com/viewtopic.php?t=424779\n#+begin_src scheme :noweb-ref system-helper\n  (define %fix-amd-disappearing-cursor-conf \"\n  Section \\\"Device\\\"\n    Identifier \\\"AMD\\\"\n    Driver \\\"amdgpu\\\"\n    Option \\\"SWCursor\\\" \\\"true\\\"\n  EndSection\n  \")\n#+end_src\n\n*** Audio\nRemove pulseaudio.\n#+begin_src scheme\n  (delete pulseaudio-service-type)\n  (alsa-service-type\n   config =\u003e (alsa-configuration\n              (inherit config)\n              (pulseaudio? #f)))\n#+end_src\n\n* Home Configuration\nThis is the main entry point for =guix home=. It can be tested with\n#+begin_src sh\n  guix home -L build container build/home-configuration.scm\n#+end_src\nand deployed with\n#+begin_src sh\n  guix home -L build reconfigure build/home-configuration.scm\n#+end_src\n\n#+begin_src scheme :tangle \"build/home-configuration.scm\" :noweb yes\n  (use-modules\n   (gnu home)\n   (gnu services)\n   (gnu packages)\n   \u003c\u003chome-module\u003e\u003e\n   )\n\n  (home-environment\n   \u003c\u003chome-environment-conf\u003e\u003e\n   (services\n    (append\n     \u003c\u003chome-environment-service\u003e\u003e\n     )))\n#+end_src\n\nThis is a list of packages that are not installed by services. Eventually this list should be empty.\n#+begin_src scheme :noweb-ref home-environment-conf\n  (packages (specifications-\u003epackages\n             (list\n              \"neovim\"\n              \"guile\"\n              )))\n#+end_src\n\n* Guix\nThis file defines those settings related to Guix itself.\n#+begin_src scheme :tangle \"build/hiecaq/home/guix.scm\" :noweb yes\n  (define-module (hiecaq home guix)\n    #:use-module (gnu services)\n    #:use-module (gnu packages)\n    #:use-module (gnu home services)\n    #:use-module (gnu home services guix)\n    #:use-module (gnu home services gnupg)\n    #:use-module (gnu packages gnupg)\n    #:use-module (guix gexp)\n    #:use-module (guix channels))\n\n  (define-public services\n    (list\n     \u003c\u003cguix-service\u003e\u003e\n     ))\n#+end_src\n\nAdd this module and its services:\n#+begin_src scheme :noweb-ref home-module\n  ((hiecaq home guix) #:prefix guix:)\n#+end_src\n\n#+begin_src scheme :noweb-ref home-environment-service\n  guix:services\n#+end_src\n\n** Locales\nSet the locales as recommended in [[https://guix.gnu.org/manual/en/html_node/Application-Setup.html][the manual]].\n#+begin_src scheme :noweb-ref guix-service\n  (service\n   (service-type\n    (name 'home-locale)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list\n               (specification-\u003epackage\n                \"glibc-locales\"))))\n      (service-extension\n       home-environment-variables-service-type\n       (const '((\"GUIX_LOCPATH\" . \"${GUIX_PROFILE}/lib/locale\"))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n\n** Channels\n:PROPERTIES:\n:header-args:scheme: :noweb-ref guix-channel\n:END:\n\n*** COMMENT Home Service\nSince I'm using guix as a system, the home service is not needed.\n#+begin_src scheme :noweb-ref guix-service\n  (simple-service\n   'variant-packages-service\n   home-channels-service-type\n   (list\n    \u003c\u003cguix-channel\u003e\u003e\n    ))\n#+end_src\n\n*** COMMENT RDE\n[[https://git.sr.ht/~abcdw/rde][rde]] is a \"developers and power user friendly GNU/Linux distribution based on GNU Guix package manager\", which can be used as a channel directly. In this way, I can use the helper procedures that it defines.\n\nI no longer need functionality belongs to rde, but I keep it here for future reference.\n#+begin_src scheme\n  (channel\n   (name 'rde)\n   (url \"https://git.sr.ht/~abcdw/rde\")\n   (introduction\n    (make-channel-introduction\n     \"257cebd587b66e4d865b3537a9a88cccd7107c95\"\n     (openpgp-fingerprint\n      \"2841 9AC6 5038 7440 C7E9  2FFA 2208 D209 58C1 DEB0\"))))\n#+end_src\n\n*** Nonguix\n[[https://gitlab.com/nonguix/nonguix][nonguix]] holds non-free stuffs, including kernel, firmware, and other close-source binaries.\n#+begin_src scheme\n  (channel\n   (name 'nonguix)\n   (url \"https://gitlab.com/nonguix/nonguix\")\n   (introduction\n    (make-channel-introduction\n     \"897c1a470da759236cc11798f4e0a5f7d4d59fbc\"\n     (openpgp-fingerprint\n      \"2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5\"))))\n#+end_src\n\n* Cross-Desktop Group (XDG)\nThis section defines those settings related to the [[https://www.freedesktop.org/wiki/Specifications/][XDG]] specifications.\n#+begin_src scheme :tangle \"build/hiecaq/home/xdg.scm\" :noweb yes\n  (define-module (hiecaq home xdg)\n    #:use-module (gnu services)\n    #:use-module (gnu packages)\n    #:use-module (gnu home services)\n    #:use-module (gnu home services xdg)\n    #:use-module (guix channels))\n\n  (define-public services\n    (list\n     \u003c\u003cxdg-service\u003e\u003e\n     ))\n#+end_src\n\nAdd this module and its services:\n#+begin_src scheme :noweb-ref home-module\n  ((hiecaq home xdg) #:prefix xdg:)\n#+end_src\n\n#+begin_src scheme :noweb-ref home-environment-service\n  xdg:services\n#+end_src\n\n** Base Directories\nSee [[https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html][Enviroment Variables chapter in latest XDG Base Directory Specification]] for the description on their purposes.\n\nGuix home [[https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/home.scm#n86][instantiate]] [[https://git.savannah.gnu.org/cgit/guix.git/tree/gnu/home/services/xdg.scm#n148][it]] by default, so technically there is no configuration needed, unless we want to modify their values.\n\nNote that their values are set in =$GUIX_HOME/setup-environment=, which should be run by =$HOME/.profile=, which is sourced at the beginning of a login shell.\n\n** User Directories\nAs declared in [[https://www.freedesktop.org/wiki/Software/xdg-user-dirs/][xdg-user-dirs]], this defines \"well known\" user directories, and their localization.\n#+begin_src scheme :noweb-ref xdg-service\n  (simple-service\n   'xdg-user-directories-config-service\n   home-xdg-user-directories-service-type\n   (home-xdg-user-directories-configuration\n    (desktop     \"$HOME/desktop\")\n    (documents   \"$HOME/documents\")\n    (download    \"$HOME/downloads\")\n    (music       \"$HOME/music\")\n    (pictures    \"$HOME/pictures\")\n    (publicshare \"$HOME/public\")\n    (templates   \"$HOME/templates\")\n    (videos      \"$HOME/videos\")))\n#+end_src\n\n* Shells\n#+begin_src scheme :tangle \"build/hiecaq/home/shell.scm\"\n  (define-module (hiecaq home shell)\n    #:use-module (gnu home)\n    #:use-module (gnu services)\n    #:use-module (gnu packages)\n    #:use-module (gnu home services)\n    #:use-module (guix channels)\n    #:use-module (gnu home services guix)\n    #:use-module (gnu home services shells)\n    #:use-module (guix gexp))\n#+end_src\n\nTODO: I should split this out later.\n#+begin_src scheme :tangle \"build/hiecaq/home/shell.scm\" :noweb yes\n  (define-public services\n    (list\n     (simple-service\n      'extend-environment-variables\n      home-environment-variables-service-type\n      `((\"PS1\" . \"$ \")\n        (\"MANPAGER\" . \"nvim +Man!\")\n        (\"MANWIDTH\" . \"80\")\n        (\"QT_AUTO_SCREEN_SCALE_FACTOR\" . \"1\")\n        (\"RUSTUP_UPDATE_ROOT\" . \"https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup\")\n        (\"RUSTUP_DIST_SERVER\" . \"https://mirrors.tuna.tsinghua.edu.cn/rustup\")))\n     \u003c\u003cshell-service\u003e\u003e\n     ))\n#+end_src\n\nAdd this module and its services:\n#+begin_src scheme :tangle no :noweb-ref home-module\n  ((hiecaq home shell) #:prefix shell:)\n#+end_src\n\n#+begin_src scheme :tangle no :noweb-ref home-environment-service\n  shell:services\n#+end_src\n** Fish\n:PROPERTIES:\n:header-args:scheme: :noweb-ref shell-service\n:END:\nI use [[https://fishshell.com/][fish]] as a backup interactive-use-only shell.\n#+begin_src scheme\n  (service\n   home-fish-service-type)\n#+end_src\n** COMMENT Zsh\n:PROPERTIES:\n:header-args:scheme: :noweb-ref shell-service\n:END:\nI'm currently using [[https://www.zsh.org/][zsh]] as my primary shell.\n#+begin_src scheme\n  (service\n   home-zsh-service-type\n   (home-zsh-configuration\n    (zshrc\n     (list (slurp-file-like (local-file \"../../.zshrc\"\n                                        \"zshrc\"))\n           (slurp-file-like (local-file \"../../.aliases\"\n                                        \"aliases\"))))))\n#+end_src\n\n*** syntax highlighting\nAdd [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]], which provides \"fish shell like syntax highlighting for Zsh.\"\n#+begin_src scheme\n  (service\n   (service-type\n    (name 'home-zsh-syntax-highlighting)\n    (extensions\n     (list\n      (service-extension home-zsh-plugin-manager-service-type\n                         (const\n                          (list\n                           (specification-\u003epackage\n                            \"zsh-syntax-highlighting\"))))\n      (service-extension\n       home-zsh-service-type\n       (const\n        (home-zsh-extension\n         (zshrc '(\"# Improve highlighting\")))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\nAnd its configuration\n#+begin_src sh :tangle \"build/.zshrc\"\n  # Declare the variable\n  typeset -A ZSH_HIGHLIGHT_STYLES\n\n  # disable highlighting for unknown-token\n  ZSH_HIGHLIGHT_STYLES[unknown-token]='none'\n\n  # use blue to highlight command(e.g., git)\n  ZSH_HIGHLIGHT_STYLES[command]='fg=004'\n\n  # builtins(e.g., pwd): blue, italic\n  ZSH_HIGHLIGHT_STYLES[builtin]='fg=004,standout'\n\n  # commandseparator(;, \u0026\u0026): lighter gray\n  ZSH_HIGHLIGHT_STYLES[commandseparator]='fg=014'\n\n  # alias: blue\n  ZSH_HIGHLIGHT_STYLES[alias]='fg=004'\n\n  # single hyphen-option: darker red,italic\n  ZSH_HIGHLIGHT_STYLES[single-hyphen-option]='fg=001'\n\n  # double hyphen-option: darker red\n  ZSH_HIGHLIGHT_STYLES[double-hyphen-option]='fg=001'\n\n  # quoted arguments(strings)\n  ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=006'\n  ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=006'\n\n  # dollar quoted arguments:gold\n  ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=003'\n\n  # other commands: red\n  ZSH_HIGHLIGHT_STYLES[arg0]='fg=001'\n\n  # To define styles for nested brackets up to level 4\n  ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=010'\n  ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=014'\n  ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=010'\n  ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=014'\n  ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=001'\n  ZSH_HIGHLIGHT_STYLES[cursor-matchingbracket]='fg=007'\n\n  ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets)\n#+end_src\n\n** Tools\n:PROPERTIES:\n:header-args:scheme: :noweb-ref shell-service\n:END:\nThere are many tools that enhance the command line user experience.\n*** Certificates\nSee the [[https://guix.gnu.org/manual/en/html_node/X_002e509-Certificates.html][Guix documentation]] for details on the CA settings. TODO: Maybe this should be in a higher-level heading?\n#+begin_src scheme\n  (service\n   (service-type\n    (name 'home-certs)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list\n               (specification-\u003epackage\n                \"nss-certs\"))))\n      (service-extension\n       home-environment-variables-service-type\n       (const '((\"SSL_CERT_DIR\" . \"$HOME/.guix-home/profile/etc/ssl/certs\")\n                (\"SSL_CERT_FILE\" . \"$SSL_CERT_DIR/ca-certificates.crt\")\n                (\"GIT_SSL_CAINFO\" . \"$SSL_CERT_FILE\")\n                (\"CURL_CA_BUNDLE\" . \"$SSL_CERT_FILE\"))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n*** bat\nAdd [[https://github.com/sharkdp/bat][bat]], which is a =cat= clone with colors.\n#+begin_src scheme\n  (service\n   (service-type\n    (name 'home-bat)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list\n               (specification-\u003epackage\n                \"bat\"))))\n      (service-extension\n       home-environment-variables-service-type\n       (const '((\"BAT_THEME\" . \"TwoDark\"))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n\n*** eza\n[[https://github.com/eza-community/eza][eza]] is a community-revived fork of [[https://github.com/ogham/exa][exa]], which is \"a modern replacement for =ls=\".\n#+begin_src scheme\n  (service\n   (service-type\n    (name 'home-eza)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list\n               (specification-\u003epackage\n                \"eza\"))))\n      (service-extension\n       home-environment-variables-service-type\n       (const '((\"EZA_COLORS\" .\n                 \"*.zip=0:*.gz=0:*.rar=0:*.tar=0:*.7z=0:ex=31:di=244;1\"))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n\n*** ripgrep\nAdd [[https://github.com/BurntSushi/ripgrep][ripgrep]], which is \"a line-oriented search tool that recursively searches the current directory for a regex pattern\". In other words, it is a modern =grep=.\n#+begin_src scheme\n  (simple-service\n   'home-ripgrep\n   home-profile-service-type\n   (list\n    (specification-\u003epackage\n     \"ripgrep\")))\n#+end_src\n\n*** fd\nAdd [[https://github.com/sharkdp/fd][fd]], which is \"a simple, fast and user-friendly alternative to 'find'\".\n#+begin_src scheme\n  (simple-service\n   'home-fd\n   home-profile-service-type\n   (list\n    (specification-\u003epackage\n     \"fd\")))\n#+end_src\n\n*** Direnv\n[[https://direnv.net/][direnv]] is the environment switcher on the shell level, based on current directories.\n#+begin_src scheme\n  (simple-service\n   'home-direnv\n   home-profile-service-type\n   (list\n    (specification-\u003epackage\n     \"direnv\")))\n#+end_src\n\n** Aliases\nAnd the aliases that I'm using:\n#+begin_src sh :tangle \"build/.aliases\"\n  alias v=\"nvim\"\n  alias e=\"emacsclient -c --no-wait\"\n  alias g=\"git\"\n  alias ls=\"exa\"\n  alias l=\"exa --git-ignore\"\n  alias l.=\"ls -lah\"\n  alias gc=\"git commit -v\"\n#+end_src\n\n* Fonts\n:PROPERTIES:\n:header-args:scheme: :tangle \"build/hiecaq/home/fonts.scm\"\n:END:\n\nThis file describe how fonts are configured.\n#+begin_src scheme\n  (define-module (hiecaq home fonts)\n    #:use-module (gnu services)\n    #:use-module (gnu home services)\n    #:use-module (gnu packages fonts)\n    #:use-module (gnu packages fontutils)\n    #:use-module (guix gexp)\n    #:use-module ((gnu home services fontutils) #:prefix fontutils:))\n#+end_src\n\nThe ~home-fontconfig-service-type~ from vanilla =guix= comes with a =fonts.conf= that is literately inconfigurable, so we have to overwrite it.\nSIDE NOTES: I cannot use ~@@~ to import ~regenerate-font-cache-gexp~ from =(gnu home services fontutils)= I have totally no idea why.\n#+begin_src scheme\n  (define (add-fontconfig-config-file he-symlink-path)\n    `((\"fontconfig/fonts.conf\"\n       ,(local-file \"../../fonts.conf\"))))\n\n  (define (regenerate-font-cache-gexp _)\n    `((\"profile/share/fonts\"\n       ,#~(system* #$(file-append fontconfig \"/bin/fc-cache\") \"-fv\"))))\n\n  (define home-fontconfig-service-type\n    (service-type (name 'home-fontconfig)\n                  (extensions\n                   (list (service-extension\n                          home-xdg-configuration-files-service-type\n                          add-fontconfig-config-file)\n                         (service-extension\n                          home-run-on-change-service-type\n                          regenerate-font-cache-gexp)\n                         (service-extension\n                          home-profile-service-type\n                          (const (list fontconfig)))))\n                  (default-value #f)\n                  (description\n                   \"Provides configuration file for fontconfig and make\n  fc-* utilities aware of font packages installed in Guix Home's profile.\")))\n\n  (define-public (modify-essential-service services)\n    `(,@(modify-services\n         services\n         (delete fontutils:home-fontconfig-service-type))\n      ,(service home-fontconfig-service-type)))\n#+end_src\n\nHere is the modified =fonts.conf=:\n#+begin_src nxml :tangle \"build/fonts.conf\" :comments no\n  \u003c?xml version=\"1.0\"?\u003e\n  \u003c!DOCTYPE fontconfig SYSTEM \"fonts.dtd\"\u003e\n  \u003cfontconfig\u003e\n      \u003cdir\u003e~/.guix-home/profile/share/fonts\u003c/dir\u003e\n      \u003calias\u003e\n          \u003cfamily\u003eserif\u003c/family\u003e\n          \u003cprefer\u003e\n              \u003cfamily\u003eNoto Serif\u003c/family\u003e\n              \u003cfamily\u003eNoto Serif CJK SC\u003c/family\u003e\n              \u003cfamily\u003eNoto Serif CJK JP\u003c/family\u003e\n              \u003cfamily\u003eNoto Serif CJK TC\u003c/family\u003e\n          \u003c/prefer\u003e\n      \u003c/alias\u003e\n      \u003calias\u003e\n          \u003cfamily\u003esans-serif\u003c/family\u003e\n          \u003cprefer\u003e\n              \u003cfamily\u003eNoto Sans\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans CJK SC\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans CJK JP\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans CJK TC\u003c/family\u003e\n          \u003c/prefer\u003e\n      \u003c/alias\u003e\n      \u003calias\u003e\n          \u003cfamily\u003emonospace\u003c/family\u003e\n          \u003cprefer\u003e\n              \u003cfamily\u003eNoto Sans Mono\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans Mono CJK SC\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans Mono CJK JP\u003c/family\u003e\n              \u003cfamily\u003eNoto Sans Mono CJK TC\u003c/family\u003e\n          \u003c/prefer\u003e\n      \u003c/alias\u003e\n      \u003calias\u003e\n          \u003cfamily\u003eemoji\u003c/family\u003e\n          \u003cprefer\u003e\n              \u003cfamily\u003eNoto Color Emoji\u003c/family\u003e\n          \u003c/prefer\u003e\n      \u003c/alias\u003e\n  \u003c/fontconfig\u003e\n#+end_src\n\nthis module simply provides a single service that install the fonts needed.\n#+begin_src scheme\n  (define-public services\n    (list (simple-service\n           'extend-environment-variables\n           home-profile-service-type\n           (list\n            font-hack\n            font-google-noto\n            font-google-noto-sans-cjk))))\n#+end_src\n\n#+begin_src scheme :tangle no :noweb-ref home-module\n  ((hiecaq home fonts) #:prefix fonts:)\n#+end_src\n\n#+begin_src scheme :tangle no :noweb-ref home-environment-service\n  fonts:services\n#+end_src\n\n* Search\n#+begin_src scheme :tangle \"build/hiecaq/services/search.scm\" :noweb yes\n  (define-module (hiecaq services search)\n    #:use-module (guix gexp)\n    #:use-module (guix packages)\n    #:use-module (gnu services)\n    #:use-module (gnu services configuration)\n    #:use-module (gnu packages search)\n    #:use-module (gnu system shadow) ;; account-service-type\n    #:use-module (ice-9 match)\n    #:use-module (ice-9 string-fun)\n    #:use-module (srfi srfi-1)\n    #:use-module (srfi srfi-26)\n    #:export (locate-configuration\n              locate-configuration?\n              locate-configuration-locate\n              locate-configuration-fields\n              locate-service-type))\n\n  (define (uglify-field-name field-name)\n    (let* ((str (symbol-\u003estring field-name))\n           (up (string-upcase str)))\n      (if (string-suffix? \"?\" up)\n          (string-replace-substring (string-drop-right up 1) \"-\" \"_\")\n          (string-replace-substring up \"-\" \"\"))))\n\n  (define (strings? lst)\n    (every string? lst))\n\n  (define (serialize-field field-name value)\n    #~(string-append #$(uglify-field-name field-name)\n                     \" = \\\"\"\n                     #$value\n                     \"\\\"\\n\"))\n\n  (define (serialize-strings field-name strs)\n    (serialize-field field-name (string-join strs \" \")))\n\n  (define (serialize-boolean field-name value)\n    (serialize-field field-name (if value \"yes\" \"no\")))\n\n  (define serialize-group empty-serializer)\n  (define (group? s) (string? s))\n\n  (define-maybe strings)\n  (define-maybe boolean)\n  (define-maybe group)\n\n  (define-configuration locate-configuration\n    (locate\n     (package plocate)\n     \"The locate package to use.\")\n    (group\n     (group \"locate\")\n     \"Locate group used to run updatedb.\")\n    (prune-fs\n     maybe-strings\n     \"List of file system types (as used in /etc/mtab) which should not be scanned.\")\n    (prune-names\n     maybe-strings\n     \"List of directory names (without paths) which should not be scanned.\")\n    (prune-paths\n     maybe-strings\n     \"List of directory absolute paths which should not be scanned.\")\n    (prune-bind-mounts?\n     maybe-boolean\n     \"If true, bind mounts are not scanned.\"))\n\n  (define (locate-etc config)\n    `((\"updatedb.conf\" ,(mixed-text-file\n                         \"updatedb.conf\"\n                         \"# Generated by 'locate-service'.\\n\"\n                         (serialize-configuration\n                          config locate-configuration-fields)))))\n\n  (define (locate-group config)\n    (list\n     (user-group\n      (name (locate-configuration-group config))\n      (system? #t))))\n\n  (define locate-service-type\n    (service-type\n     (name 'locate)\n     (extensions\n      (list (service-extension profile-service-type (compose list locate-configuration-locate))\n            (service-extension etc-service-type locate-etc)\n            (service-extension account-service-type locate-group)))\n     (default-value (locate-configuration))\n     (description #f)))\n#+end_src\n\n** System service\nThis is translated from [[https://gitlab.archlinux.org/archlinux/packaging/packages/plocate/-/blob/main/updatedb.conf][Arch's configuration]]\n#+begin_src scheme :noweb-ref system-services\n  (service locate-service-type\n           (locate-configuration\n            (group \"plocate\")\n            (prune-fs '(\"9p\" \"afs\" \"anon_inodefs\"\n                        \"auto\" \"autofs\" \"bdev\" \"binfmt_misc\" \"cgroup\" \"cifs\" \"coda\" \"configfs\"\n                        \"cpuset\" \"cramfs\" \"debugfs\" \"devpts\" \"devtmpfs\" \"ecryptfs\" \"exofs\"\n                        \"ftpfs\" \"fuse\" \"fuse.encfs\" \"fuse.s3fs\" \"fuse.sshfs\" \"fusectl\" \"gfs\"\n                        \"gfs2\" \"hugetlbfs\" \"inotifyfs\" \"iso9660\" \"jffs2\" \"lustre\" \"mqueue\"\n                        \"ncpfs\" \"nfs\" \"nfs4\" \"nfsd\" \"pipefs\" \"proc\" \"ramfs\" \"rootfs\"\n                        \"rpc_pipefs\" \"securityfs\" \"selinuxfs\" \"sfs\" \"shfs\" \"smbfs\" \"sockfs\"\n                        \"sshfs\" \"sysfs\" \"tmpfs\" \"ubifs\" \"udf\" \"usbfs\" \"vboxsf\"))\n            (prune-names '(\".git\" \".hg\" \".svn\" \".cache\"))\n            (prune-paths '(\"/afs\" \"/media\" \"/mnt\"\n                           \"/net\" \"/sfs\" \"/tmp\" \"/udev\" \"/gnu/store\"\n                           \"/var/cache\" \"/var/lock\" \"/var/run\" \"/var/tmp\"))))\n#+end_src\n* Desktop Environment\nMy \"desktop environment\" is plain window management with friends.\n\n#+begin_src scheme :tangle \"build/hiecaq/home/de.scm\" :noweb yes\n  (define-module (hiecaq home de)\n    #:use-module (guix gexp)\n    #:use-module (gnu services)\n    #:use-module (gnu home services)\n    \u003c\u003cde-use-module\u003e\u003e)\n\n  (define-public services\n    (list\n     \u003c\u003cde-service\u003e\u003e))\n#+end_src\n\n#+begin_src scheme :tangle no :noweb-ref home-module\n  ((hiecaq home de) #:prefix de:)\n#+end_src\n\n#+begin_src scheme :tangle no :noweb-ref home-environment-service\n  de:services\n#+end_src\n\n** Display\nI currently use Guix's default display manager, i.e. =gdm=, and when there is no =*.desktop= of WMs available in its search path, it can log in with the user provided =~/.xsession= executable (which won't be displayed in the selection menu).\n\nSo, simply\n#+begin_src sh :tangle \"build/xsession\" :shebang #!/usr/bin/env bash\n  exec xmonad\n#+end_src\n\n#+begin_src scheme :noweb-ref de-use-module\n  #:use-module (gnu packages wm)\n#+end_src\n\n#+begin_src scheme :noweb-ref de-service\n  (service\n   (service-type\n    (name 'home-wm)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list\n               xmonad\n               ghc-xmonad-contrib\n               xmobar)))\n      (service-extension\n       home-files-service-type\n       ;; recursive to keep x bits, see https://lists.gnu.org/archive/html/help-guix/2023-03/msg00190.html\n       (const `((\".xsession\" ,(local-file \"../../xsession\" #:recursive? #t)))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n\n** D-Bus\nStart a session-specific D-Bus for unprivileged apps:\n#+begin_src scheme :noweb-ref de-use-module\n  #:use-module (gnu home services desktop)\n#+end_src\n\n#+begin_src scheme :noweb-ref de-service\n  (service home-dbus-service-type)\n#+end_src\n\n** XDG Desktop Portal\n[[https://flatpak.github.io/xdg-desktop-portal/][xdg-desktop-portal]] exposes a series of D-bus interface to give sandboxed application access to some host system functionalities, most notably file-picker, in a way similar to Android nowadays.\n\nThere are several daemons involved, and all of them will be automatically started the first time related D-bus events happen:\n- =xdg-desktop-portal=, the daemon [[https://flatpak.github.io/xdg-desktop-portal/docs/terminology.html#][provides the API that application interacts with]].\n- =xdg-document-portal=, the daemon that [[https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Documents.html][binds the shared files inside and outside sandboxes]], i.e under =/run/usr/$UID/doc=\n- =xdg-permission-store=, the daemon that [[https://github.com/flatpak/xdg-desktop-portal/wiki/The-Permission-Store/][keeps the permissions which a user has given to apps]].\n- =xdg-desktop-portal-gtk=, the daemon that is the back-end that actually handles the translated and standardized requests.\nBTW, if the portal does not work immediately after reconfigure, try reboot the system.\n\n#+begin_src scheme :noweb-ref de-use-module\n  #:use-module (gnu packages freedesktop)\n#+end_src\n\n#+begin_src scheme :noweb-ref de-service\n  (service\n   (service-type\n    (name 'home-xdg-desktop-portal)\n    (extensions\n     (list\n      (service-extension\n       home-profile-service-type\n       (const (list xdg-desktop-portal\n                    xdg-desktop-portal-gtk)))\n      (service-extension\n       home-xdg-configuration-files-service-type\n       (const `((\"xdg-desktop-portal/portals.conf\" ,(local-file \"../../portals.conf\")))))))\n    (default-value #f)\n    (description #f)))\n#+end_src\n\nThe following file set using =xdg-desktop-portal-gtk= as the default backend. There can actually be multiple backends running at the same time.\n#+begin_src conf :tangle \"build/portals.conf\"\n  [preferred]\n  default=gtk\n#+end_src\n\n** Audio and Sound\nI use a user [[https://gitlab.freedesktop.org/pipewire/pipewire][pipewire]] session.\n#+begin_src scheme :noweb-ref de-use-module\n  #:use-module (gnu home services sound)\n#+end_src\n\n#+begin_src scheme :noweb-ref de-service\n  (service home-pipewire-service-type)\n#+end_src\n\n* Emacs\n:PROPERTIES:\n:header-args:emacs-lisp: :lexical t :tangle \"build/init.el\"\n:header-args:lisp-data: :tangle \"build/templates.eld\"\n:header-args:scheme: :noweb-ref emacs-service\n:END:\nTODO: I'm still not sure if I should put some config as big as Emacs' in this file.\n\nImplement a =home-emacs-service-type= that\n- The service itself defines the Emacs version to use and the \"Emacs compiler\" to use, via =home-emacs-configuration=\n- The service's extension add Emacs packages to use, configuration file to link, etc, via =home-emacs-extension=.\nThe reason for this set-up is\n- I can easily swap between different Emacs versions, and packages will be automatically transformed to using that version's byte-codes.\n- Configurations are discrete by using extensions, so they fit this literature configuration set-up better.\n#+begin_src scheme :tangle \"build/hiecaq/home/services/emacs.scm\" :noweb-ref nil\n  (define-module (hiecaq home services emacs)\n    #:use-module (gnu services)\n    #:use-module (gnu services configuration)\n    #:use-module (gnu home services)\n    #:use-module ((gnu packages emacs) #:prefix upstream:)\n    #:use-module (guix packages)\n    #:use-module (srfi srfi-1)\n    #:export (home-emacs-configuration\n              home-emacs-extension\n              home-emacs-service-type))\n\n  (define-configuration/no-serialization home-emacs-configuration\n    (emacs\n     (package upstream:emacs)\n     \"Emacs to use.\")\n    (emacs-compiler\n     (package upstream:emacs-minimal)\n     \"Emacs used for compiling packages.\")\n    (packages\n     (list '())\n     \"List of Emacs packages to use.\")\n    (configs\n     (alist '())\n     \"Emacs configuration files.\"))\n\n  (define (home-emacs-transformed-package config)\n    (package-input-rewriting\n     `((,upstream:emacs-minimal\n        . ,(home-emacs-configuration-emacs-compiler config))\n       (,upstream:emacs-no-x\n        . ,(home-emacs-configuration-emacs config))\n       (,upstream:emacs\n        . ,(home-emacs-configuration-emacs config)))))\n\n  (define (home-emacs-profile config)\n    `(,(home-emacs-configuration-emacs config)\n      ,@(map (home-emacs-transformed-package config)\n             (home-emacs-configuration-packages config))))\n\n  (define-configuration/no-serialization home-emacs-extension\n    (packages\n     (list '())\n     \"Extra list of Emacs packages to use.\")\n    (configs\n     (alist '())\n     \"Extra Emacs configuration files.\"))\n\n  (define (home-emacs-extensions original-config extension-configs)\n    (let ((append-fields\n           (lambda (config-getter extension-getter)\n             (append (config-getter original-config)\n                     (append-map extension-getter extension-configs)))))\n      (home-emacs-configuration\n       (inherit original-config)\n       (packages (append-fields home-emacs-configuration-packages\n                                home-emacs-extension-packages))\n       (configs (append-fields home-emacs-configuration-configs\n                               home-emacs-extension-configs)))))\n\n  (define home-emacs-service-type\n    (service-type\n     (name 'home-emacs)\n     (extensions\n      (list (service-extension home-xdg-configuration-files-service-type\n                               home-emacs-configuration-configs)\n            (service-extension home-profile-service-type\n                               home-emacs-profile)\n            (service-extension home-environment-variables-service-type\n                               (const '((\"EDITOR\" . \"emacsclient -a nvim -c\")\n                                        (\"VISUAL\" . \"emacsclient -a nvim -c\"))))))\n     (compose identity)\n     (extend home-emacs-extensions)\n     (default-value (home-emacs-configuration))\n     (description #f)))\n#+end_src\n\n#+begin_src scheme :tangle \"build/hiecaq/home/emacs.scm\" :noweb yes :noweb-ref nil\n  (define-module (hiecaq home emacs)\n    #:use-module (gnu services)\n    #:use-module (gnu packages)\n    #:use-module ((gnu packages emacs) #:prefix upstream:)\n    #:use-module (gnu home services)\n    #:use-module (gnu home services shells)\n    #:use-module (hiecaq home services emacs)\n    #:use-module (guix gexp))\n\n  (define-public services\n    (list\n     \u003c\u003cemacs-service\u003e\u003e))\n#+end_src\n\nAdd this module and its services:\n#+begin_src scheme :noweb-ref home-module\n  ((hiecaq home emacs) #:prefix emacs:)\n#+end_src\n\n#+begin_src scheme :noweb-ref home-environment-service\n  emacs:services\n#+end_src\n\n** Basics\nI'm currently using =emacs= from Guix official channel.\n#+begin_src scheme\n  (service home-emacs-service-type\n           (home-emacs-configuration\n            (emacs upstream:emacs-next)\n            (emacs-compiler upstream:emacs-next-minimal)))\n#+end_src\n\nMy Guix packages definition is at =(hiecaq packages emacs-xyz)=. TODO: makes a channel!\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-module (hiecaq packages emacs-xyz)\n    #:use-module (guix utils)\n    #:use-module (guix gexp)\n    #:use-module (guix packages)\n    #:use-module (guix git-download)\n    #:use-module (guix build utils)\n    #:use-module (guix build-system emacs)\n    #:use-module (gnu packages)\n    #:use-module ((gnu packages textutils) #:prefix upstream:) ;; for vale\n    #:use-module ((gnu packages emacs) #:prefix upstream:)\n    #:use-module ((gnu packages emacs-xyz) #:prefix upstream:)\n    #:use-module ((guix licenses) #:prefix license:))\n#+end_src\n\nNOTE: the hash for git-based packages is got by following [[https://guix.gnu.org/cookbook/en/html_node/Extended-example.html][Guix Cookbook instructions]].\n\n** Early Initialization\n:PROPERTIES:\n:header-args:emacs-lisp: :lexical t :tangle \"build/early-init.el\"\n:END:\n\n#+begin_src scheme\n  (simple-service\n   'home-emacs-early-init\n   home-emacs-service-type\n   (home-emacs-extension\n    (configs `((\"emacs/early-init.el\" ,(local-file \"../../early-init.el\"))))))\n#+end_src\n\n#+begin_src emacs-lisp :comments no\n  ;;; early-init.el --- Configurations before package systems and UI systems -*- lexical-binding: t; buffer-read-only: t; eval: (auto-revert-mode 1) -*-\n#+end_src\n\n*** Packages\nI don't use the built-in =package.el= to fetch packages, so I'll turn it off:\n#+begin_src emacs-lisp\n  (setq package-enable-at-startup nil)\n#+end_src\n\n*** Special Key Remapping\ngrabbed from [[https://emacsnotes.wordpress.com/2022/09/11/three-bonus-keys-c-i-c-m-and-c-for-your-gui-emacs-all-with-zero-headache/][Three bonus keys—‘C-i’, ‘C-m’ and ‘C-[’—for your GUI Emacs; all with zero headache]]\n#+begin_src emacs-lisp\n  (add-hook\n   'after-make-frame-functions\n   (defun setup-blah-keys (frame)\n     (with-selected-frame frame\n       (when (display-graphic-p)\n         (define-key input-decode-map (kbd \"C-i\") [CTRL-i])\n         (define-key input-decode-map (kbd \"C-[\") [CTRL-lsb]) ; left square bracket\n         (define-key input-decode-map (kbd \"C-m\") [CTRL-m])))))\n#+end_src\n\n*** Fonts\n#+begin_src emacs-lisp\n  (defun my-faces-initialize (frame)\n    \"Initialize faces when the first graphic display is available.\"\n    (with-selected-frame frame\n      (when (display-graphic-p)\n        (let* ((families (font-family-list))\n               (family-p (lambda (family) (member family families)))\n               (first-family-installed\n                (lambda (family-list)\n                  (seq-find family-p family-list))))\n\n          (when-let* ((family\n                       (funcall first-family-installed '(\"Hack Nerd Font Mono\"\n                                                         \"Hack\"\n                                                         \"Noto Sans Mono\"))))\n            (set-face-attribute 'default nil :family family))\n\n          (when-let* ((family\n                       (funcall first-family-installed '(\"Noto Sans\"\n                                                         \"Sans Serif\"))))\n            (set-face-attribute 'variable-pitch nil :family family))\n\n          (when (eq system-type 'gnu/linux)\n            (set-face-attribute 'default nil :height 140))\n\n          (when-let* ((family\n                       (funcall first-family-installed '(\"Noto Sans CJK SC\"))))\n            (set-fontset-font t 'han (font-spec :family \"Noto Sans CJK SC\")))\n\n          (when-let* ((family\n                       (funcall first-family-installed '(\"Noto Sans CJK JP\"))))\n            (set-fontset-font t 'kana (font-spec :family \"Noto Sans CJK JP\")))\n\n          (when-let* ((family\n                       (funcall first-family-installed '(\"Noto Sans CJK SC\"))))\n            (set-fontset-font t 'cjk-misc (font-spec :family \"Noto Sans CJK SC\"))))\n\n        (set-face-attribute 'variable-pitch nil :weight 'normal :inherit 'default)\n        (set-face-attribute 'fixed-pitch nil    :family  (internal-get-lisp-face-attribute 'default :family))\n\n        (remove-hook 'after-make-frame-functions #'my-faces-initialize))))\n\n  (add-hook 'after-make-frame-functions #'my-faces-initialize)\n#+end_src\n\n*** Some Configurations that might make sense to put here\n~load~ prefers the newest version of a file (when suffix is not given).\n#+begin_src emacs-lisp\n  (setq load-prefer-newer t)\n#+end_src\n\n#+begin_src emacs-lisp\n  (setq load-no-native t)\n#+end_src\n\n** Main Configurations\n#+begin_src scheme\n  (simple-service\n   'home-emacs-init\n   home-emacs-service-type\n   (home-emacs-extension\n    (configs `((\"emacs/init.el\" ,(local-file \"../../init.el\"))))))\n#+end_src\n\nInit file header:\n#+begin_src emacs-lisp :comments no\n  ;;; init.el --- Main Configurations -*- lexical-binding: t; buffer-read-only: t; eval: (auto-revert-mode 1) -*-\n#+end_src\n\nUse Utf-8 as the default coding system.\n#+begin_src emacs-lisp\n  (set-language-environment \"UTF-8\")\n  (prefer-coding-system 'utf-8-unix)\n#+end_src\n\n*** Packages\nOn Android and Windows, I currently just use =package.el=, because Guix doesn't support them (yet?).\n#+begin_src emacs-lisp :noweb yes\n  (setopt package-archives\n          (if (eq system-type 'gnu/linux)\n              nil\n            '((\"gnu\"    . \"https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/\")\n              (\"nongnu\" . \"https://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/\")\n              (\"melpa\"  . \"https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/\"))))\n\n  (defvar package-selected-packages '\u003c\u003ccurrent-selected-packages()\u003e\u003e)\n\n  (when package-archives\n    (package-initialize))\n#+end_src\n\nOn Linux when I tangle the =init.el=, my generated ~package-selected-packages~ will contains the packages I need on these platforms.  Then after the Emacs on these platforms loading my =init.el=, I can manually call ~package-install-selected-packages~ to get them installed.\n#+name: current-selected-packages\n#+begin_src elisp :exports none\n  package-selected-packages\n#+end_src\n\n*** setup.el\n[[https://www.emacswiki.org/emacs/SetupEl][setup.el]] provides \"context sensitive local macros\" to \"ease repetitive configuration patterns in Emacs\". It is considered as an alternative to the now built-in [[https://github.com/jwiegley/use-package][use-package]].\n#+begin_src scheme\n  (simple-service\n   'home-emacs-setup\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list (specification-\u003epackage\n            \"emacs-setup\")))))\n#+end_src\n\nSee Alternative Macro Definer at [[https://www.emacswiki.org/emacs/SetupEl][its Emacs Wiki page]], and [[https://github.com/mfiano/emacs-config/blob/main/lisp/mf-setup.el][Michael Fiano's Emacs Configuration on this]]. Many of the following tweaks are based on them, with some modifications, mainly for the Emacs 29 changes.\n\nTODO: I should split this out later.\n#+begin_src emacs-lisp\n  (require 'setup)\n  (require 'cl-macs)\n\n  (defmacro defsetup (name signature \u0026rest body)\n    \"Shorthand for `setup-define'.\n  NAME is the name of the local macro.  SIGNATURE is used as the\n  argument list for FN.  If BODY starts with a string, use this as\n  the value for :documentation.  Any following keywords are passed\n  as OPTS to `setup-define'.\"\n    (declare (debug defun))\n    (let (opts)\n      (when (stringp (car body))\n        (setq opts (nconc (list :documentation (pop body))\n                          opts)))\n      (while (keywordp (car body))\n        (let* ((prop (pop body))\n               (val `',(pop body)))\n          (setq opts (nconc (list prop val) opts))))\n      `(setup-define ,name\n         (cl-function (lambda ,signature ,@body))\n         ,@opts)))\n\n  (put #'defsetup 'lisp-indent-function 'defun)\n  ;; use Emacs 29's new `setopt'\n  (setup-define :option\n    (setup-make-setter\n     (lambda (name)\n       `(funcall (or (get ',name 'custom-get)\n                     #'symbol-value)\n                 ',name))\n     (lambda (name val)\n       `(setopt ,name ,val)))\n\n    :documentation \"Set the option NAME to VAL.\n  NAME may be a symbol, or a cons-cell.  If NAME is a cons-cell, it\n  will use the car value to modify the behaviour.  These forms are\n  supported:\n\n  (append VAR)    Assuming VAR designates a list, add VAL as its last\n                  element, unless it is already member of the list.\n\n  (prepend VAR)   Assuming VAR designates a list, add VAL to the\n                  beginning, unless it is already member of the\n                  list.\n\n  (remove VAR)    Assuming VAR designates a list, remove all instances\n                  of VAL.\n\n  Note that if the value of an option is modified partially by\n  append, prepend, remove, one should ensure that the default value\n  has been loaded. Also keep in mind that user options customized\n  with this macro are not added to the \\\"user\\\" theme, and will\n  therefore not be stored in `custom-set-variables' blocks.\"\n    :debug '(sexp form)\n    :repeatable t)\n\n  (defsetup :global (\u0026rest body)\n    \"Use the global keymap for the BODY. This is intended to be used with ':bind'.\"\n    :debug '(sexp)\n    (let (bodies)\n      (push (setup-bind body (map 'global-map))\n            bodies)\n      (macroexp-progn (nreverse bodies))))\n\n  (defsetup :with-state (state \u0026rest body)\n    \"Change the evil STATE that BODY will bind to. If STATE is a list, apply BODY\n  to all elements of STATE. This is intended to be used with ':bind'.\"\n    :indent 1\n    :debug '(sexp setup)\n    (let (bodies)\n      (dolist (state (ensure-list state))\n        (push (setup-bind body (state state))\n              bodies))\n      (macroexp-progn (nreverse bodies))))\n\n  (defsetup :bind (key command)\n    \"Bind KEY to COMMAND in current map, and optionally for current evil states.\"\n    :after-loaded t\n    :debug '(form sexp)\n    :repeatable t\n    (let* ((map (setup-get 'map))\n           (global (or (not map) (eq map 'global) (eq map 'global-map)))\n           (state (ignore-errors (setup-get 'state))))\n      (cond\n       ((and state global)\n        `(with-eval-after-load 'evil\n           (evil-define-key* ',state 'global ,(kbd key) ,command)))\n       (state\n        `(with-eval-after-load 'evil\n           (evil-define-key* ',state ,map ,(kbd key) ,command)))\n       (global `(keymap-global-set ,key ,command))\n       (t `(keymap-set ,map ,key ,command)))))\n\n  (defsetup :unbind (key)\n    \"Unbind KEY in current map, and optionally for current evil states.\"\n    :after-loaded t\n    :debug '(form)\n    :repeatable t\n    (let* ((map (setup-get 'map))\n           (global (or (not map) (eq map 'global) (eq map 'global-map)))\n           (state (ignore-errors (setup-get 'state))))\n      (cond\n       ((and state global)\n        `(with-eval-after-load 'evil\n           (evil-define-key* ',state 'global ,(kbd key) nil)))\n       (state\n        `(with-eval-after-load 'evil\n           (evil-define-key* ',state ,map ,(kbd key) nil)))\n       (global `(keymap-global-unset ,key :remove))\n       (t `(keymap-unset ,map ,key :remove)))))\n\n  (defsetup :rebind (old-command new-command)\n    \"Bind NEW-COMMAND to OLD-COMMAND in current map,\n  and optionally for current evil states.\"\n    :after-loaded t\n    :debug '(form sexp)\n    :repeatable t\n    :ensure (func func)\n    (let ((old-command-string\n           (cadr (delete \"#'\" (split-string (format \"%s\" old-command) \"#'\")))))\n      `(:bind ,(format \"\u003cremap\u003e \u003c%s\u003e\" old-command-string) ,new-command)))\n\n  (defsetup :needs (executable)\n    \"If EXECUTABLE is not in the path, stop here.\"\n    :debug '(form)\n    `(unless (executable-find ,executable)\n       ,(setup-quit)))\n\n  (defsetup :enable ()\n    \"Enable the current mode.\"\n    :debug '(form)\n    `(,(setup-get 'mode) 1))\n\n  (defsetup :system (type)\n    \"If system-type is not TYPE, stop here.\"\n    :debug '(form)\n    :repeatable t\n    `(unless (eq system-type ,type)\n       ,(setup-quit)))\n\n  (defsetup :package (package)\n    \"Push PACKAGE into package-selected-packages.\"\n    :shorthand cadr\n    `(add-to-list 'package-selected-packages ',package))\n\n  (setup (:package setup))\n#+end_src\n\n*** Some Sane Configurations\n#+begin_src emacs-lisp\n  (setup simple\n    (:option indent-tabs-mode nil))\n\n  (setup frame\n    (:option blink-cursor-mode nil))\n\n  (setup scroll-bar\n    (:option scroll-bar-mode nil))\n\n  (setup tool-bar\n    (:option tool-bar-mode nil))\n\n  (setup menu-bar\n    (:option menu-bar-mode nil))\n#+end_src\n\nTurn off lockfiles. They cannot be moved to a different directory, and they consistently screw up with file watchers and version control systems. It'd be just easier to turn this feature off.\n#+begin_src emacs-lisp\n  (setup emacs\n    (:option create-lockfiles nil))\n#+end_src\n\n4-space indentation:\n#+begin_src emacs-lisp\n  (setup simple\n    (:option tab-width 4))\n#+end_src\n\nGeneral programming set up:\n#+begin_src emacs-lisp\n  (setup prog-mode\n    (:hook #'display-line-numbers-mode)\n    (:local-set truncate-lines t))\n#+end_src\n\nWhen Emacs writes buffers to files, by the high-level sense it replace the existing file with the content in the buffer. The buffer itself can be backuped, so that if Emacs crashes before the writing, the dirty content can be recovered. How it replaces the content is configurable, and I want to always prefer copying the existing file and then writing the buffer on top of the existing file. See [[help:make-backup-files][help]] for details.\n#+begin_src emacs-lisp\n  (setup files\n    (:option make-backup-files nil)\n    (:option backup-by-copying t))\n#+end_src\n\nAlways use =y-or-p= over =yes-or-no=, and use ~read-key~ instead of ~read-from-minibuffer~. The latter is helpful when using Embark.\n#+begin_src emacs-lisp\n  (setup emacs\n    (:option use-short-answers t\n             y-or-n-p-use-read-key t))\n#+end_src\n\nI don't want Emacs to auto-recenter when scrolling off-the-screen:\n#+begin_src emacs-lisp\n  (setup emacs\n    (:option scroll-conservatively 108))\n#+end_src\n\nEmacs comes with a customization interface, which supports setting via function calls too (good!) and saves the results in a file (bad!).  Up until Emacs 29, I set the storage to =/dev/null=. Started from Emacs 30, I find that sometimes file-defined local variables are not loaded the first time I open a buffer, so I came up with a new solution: set it to a random temporary file every time Emacs starts.\n#+begin_src emacs-lisp\n  (setup cus-edit\n    (:option custom-file null-device)\n    (defun my-custom-file-set ()\n      (:option custom-file\n               (make-temp-file \"emacs-custom-\" nil \".el\"\n                               \";; auto-generated by custom-file\\n\")))\n    (:with-function my-custom-file-set\n      (:hook-into after-init)))\n#+end_src\n\n#+end_src\n\nAllow word-wrap at any CJK character, otherwise it only wraps at spaces when there are also non-CJK characters in the physical lines, producing sparse visual lines.\n#+begin_src emacs-lisp\n  (setup emacs\n    (:option word-wrap-by-category t))\n#+end_src\n\nAlso, Emacs by default auto-renames certain buffers when a buffer with the same name is killed, which brings trouble to scripting. So I'd have this feature turned off.\n#+begin_src emacs-lisp\n  (setup uniquify\n    (:option uniquify-after-kill-buffer-p nil))\n#+end_src\n\nI found some hacks [[https://emacs-china.org/t/topic/25811/9][here]] that speed up displaying long lines by sacrificing the compatibility of bidirectional text. I don't read or type bidirectional text myself,  and these hacks can be reverted on a per-buffer basis if necessary. I currently use just the one that is not subjected against in the documentation.\n#+begin_src emacs-lisp\n  (setup emacs\n    (:option bidi-paragraph-direction 'left-to-right))\n#+end_src\n\nTime out on remote file access (in seconds), for example if they are offline, so that Emacs is not blocked forever.\n#+begin_src emacs-lisp\n  (setup files\n    (:option remote-file-name-access-timeout 3))\n#+end_src\n\n**** Android\n#+begin_src emacs-lisp\n  (setup touch-screen\n    (:system 'android)\n    (:option overriding-text-conversion-style nil)\n    (:option touch-screen-display-keyboard t))\n#+end_src\n\n*** Window Management\n#+begin_src emacs-lisp\n  (setup window\n    (:option switch-to-buffer-obey-display-actions t\n             switch-to-buffer-in-dedicated-window 'pop\n             ;; left, top, right, bottom\n             window-sides-slots '(0 0 1 1))\n    (defun fit-window-to-buffer-horiz (window)\n      \"Fit window to buffer horizontally. Suitable for `window-width'.\"\n      (let ((fit-window-to-buffer-horizontally 'only))\n        (fit-window-to-buffer window))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (defun my-window-shot (\u0026optional window)\n    \"Take screenshot of a given Emacs window.\"\n    (interactive)\n    (pcase-let ((`(,window-left ,window-top ,window-right ,window-bottom)\n                 (window-edges (window-normalize-window window t) nil t t)))\n      (let* ((geo (format \"%dx%d+%d+%d\"\n                          (- window-right window-left)\n                          (- window-bottom window-top)\n                          window-left\n                          window-top))\n             (file (expand-file-name (format \"%f.jpg\" (time-to-seconds (time-since 0)))\n                                     (xdg-user-dir \"PICTURES\"))))\n        (make-process :name \"window-shot\"\n                      :command `(\"maim\"\n                                 \"-m\" \"10\"\n                                 \"--geometry\" ,geo\n                                 ,file)))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (defvar my-window-record--process nil \"Running record process\")\n  (defun my-window-record (\u0026optional window sec)\n    \"Take screen record of a given Emacs window.\"\n    (interactive)\n    (if (process-live-p my-window-record--process)\n        (process-send-string my-window-record--process \"q\")\n      (pcase-let ((`(,window-left ,window-top ,window-right ,window-bottom)\n                   (window-edges (window-normalize-window window t) nil t t)))\n        (let* ((size (format \"%dx%d\"\n                             (- window-right window-left)\n                             (- window-bottom window-top)))\n               (geo (format \":0.0+%d,%d\"\n                            window-left\n                            window-top))\n               (file (expand-file-name (format \"%f.mp4\" (time-to-seconds (time-since 0)))\n                                       (xdg-user-dir \"PICTURES\")))\n               (proc (make-process :name \"window-record\"\n                                   :buffer \"*window-record*\"\n                                   :connection-type 'pty\n                                   :command `(\"ffmpeg\"\n                                              \"-video_size\" ,size\n                                              \"-framerate\" \"8\"\n                                              \"-f\" \"x11grab\"\n                                              \"-i\" ,geo\n                                              ,file))))\n          (setq my-window-record--process proc)\n          (unless (null sec)\n            (run-with-timer sec nil\n                            #'process-send-string proc \"q\"))))))\n#+end_src\n\n*** Universal Argument\nI am using [[https://www.kaufmann.no/roland/dvorak/][Programmer Dvorak (DVP)]], which swaps digits and special symbols. This makes typing numbers generally inconvenient. The idea behind this change is that we should define ~const~ variables to hold these numbers to reduce the chances we need to actually type numbers. However, Emacs (and Evil) use numbers to repeat commands, a situation that we still need typing digits directly. This is improved by the following tweak.\n\n=C-u= basically invokes the ~unversal-argument-map~ transient map, so we can remap the digit row's symbols to actual digits. Also I add a binding to insert current universal argument's number.\n#+begin_src emacs-lisp\n  (defvar my-dvp-digit-row-alist\n    '((7 . \"[\")\n      (5 . \"{\")\n      (3 . \"}\")\n      (1 . \"(\")\n      (9 . \"=\")\n      (0 . \"*\")\n      (2 . \")\")\n      (4 . \"+\")\n      (6 . \"]\")\n      (8 . \"!\"))\n    \"`Higher' case characters to digits mapping on dvorak digit row\")\n\n  (setup simple\n    (defun my-digit-argument (digit)\n      \"Return the command that inputs the given\n  digit as universal argument.\"\n      (lambda (arg)\n        (interactive \"P\")\n        (let ((last-command-event (+ digit ?0)))\n          (digit-argument arg))))\n    (:with-map universal-argument-map\n      (dolist (d (number-sequence 0 9))\n        (:bind (alist-get d my-dvp-digit-row-alist)\n               (my-digit-argument d)))\n      (:bind \"\u003cCTRL-i\u003e\" (lambda (arg)\n                          (interactive \"P\")\n                          (insert (format \"%s\" arg))))))\n#+end_src\n\nAlso here is a helper macro for binding commands. I personally do not like using universal argument at all.\n#+begin_src emacs-lisp\n  (defmacro my-with-universal-argument (cmd)\n    \"Wrap the given CMD with a lambda that set universal argument before\n    interactively calling CMD.\"\n    `(lambda ()\n       (interactive)\n       (let ((current-prefix-arg '(4)))\n         (call-interactively ,cmd))))\n#+end_src\n\n*** PCRE\nEmacs comes with an [[info:elisp#Rx Notation][Rx Notation]] that converts sexp DSL in that format into Emacs Regex strings. However, Emacs' regex format is a little bit different from PCRE, the most prevalent regex standard among tools outside of Emacs. [[https://github.com/joddie/pcre2el][pcre2el]] is the missing bridge between PCRE, Emacs regex string and rx notation.\n#+begin_src scheme\n  (simple-service\n   'home-emacs-pcre\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-pcre2el\")))))\n#+end_src\n*** Help\nTODO: this should not require help.\n#+begin_src emacs-lisp\n  (setup (:require help)\n    (:global (:unbind \"C-h C-h\")))\n#+end_src\n\n*** Info\nInfo is a built-in Texinfo browser.\n#+begin_src emacs-lisp\n  (setup Info\n    (:local-set truncate-lines t))\n#+end_src\n\n*** Xdg\n#+begin_src emacs-lisp\n  (setup (:require xdg))\n#+end_src\n\n\n*** No Littering\n[[https://github.com/emacscollective/no-littering][no-littering]] helps put emacs directory clean, sorting package-created files and directories into reasonable directories. One thing it misses is the distinguishing between permanent data and temporary data. I used to fork it to provide this distinguishing, but it turns out to be too troublesome to maintain. Now I simply consider this as a \"fallback\" solution. Later on for the variables from packages I really use I'll overwrite them manually.\n#+begin_src scheme\n  (simple-service\n   'home-emacs-no-littering\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-no-littering\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package no-littering)\n    (:require no-littering))\n#+end_src\n\n#+begin_src emacs-lisp\n  (defmacro def-exdg-home-dir (xdg-name)\n    (list 'progn\n          `(defvar ,(intern (format \"exdg-%s-dir\" xdg-name))\n             (expand-file-name (convert-standard-filename \"emacs/\") (,(intern (format \"xdg-%s-home\" xdg-name)))))\n          `(defun ,(intern (format \"exdg-%s\" xdg-name)) (file)\n             (expand-file-name (convert-standard-filename file) ,(intern (format \"exdg-%s-dir\" xdg-name))))))\n\n  (def-exdg-home-dir config)\n  (def-exdg-home-dir cache)\n  (def-exdg-home-dir data)\n  (def-exdg-home-dir state)\n\n  (setq exdg-config-dir (expand-file-name \"config/\" user-emacs-directory))\n#+end_src\n\n*** Modus Themes\n#+begin_src scheme\n  (simple-service\n   'home-emacs-modus-themes\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-modus-themes\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package modus-themes)\n    (:option modus-themes-mixed-fonts t)\n    (:require modus-themes)\n    (load-theme 'modus-vivendi :no-confirm))\n#+end_src\n\n*** Mode Line\n#+begin_src emacs-lisp\n  (defvar-local my-mode-line-format nil\n    \"My `mode-line-format', for easy toggle between the default version.\")\n\n  (defun my-toggle-mode-line-format ()\n    (interactive)\n    (let* ((standard (eval (car (get 'mode-line-format 'standard-value))))\n           (new-format (if (eq standard (default-value 'mode-line-format))\n                           my-mode-line-format\n                         standard)))\n      (setq-default mode-line-format new-format)\n      (kill-local-variable 'mode-line-format)\n      (force-mode-line-update)))\n\n  (defun my-mode-line-recursion--indicator ()\n    (when-let (((mode-line-window-selected-p))\n               (depth (- (recursion-depth) (if (active-minibuffer-window) 1 0)))\n               ((\u003e depth 0)))\n      (format \"R%d\" depth)))\n\n  (defvar-local my-mode-line-recursion-indicator\n      '(:eval (my-mode-line-recursion--indicator)))\n  (put 'my-mode-line-recursion-indicator 'risky-local-variable t)\n\n  (defvar-local my-mode-line-indicators (list my-mode-line-recursion-indicator\n                                              '(:eval (when find-file-literally \"L \"))\n                                              '(:eval (when buffer-read-only \"RO \"))\n                                              '(:eval (unless (string-equal (format-mode-line \"%@\") \"-\") \"Remote \"))\n                                              '(:eval (when (buffer-narrowed-p) '(:propertize \"Narrow \" face warning)))\n                                              '(:eval (when (window-dedicated-p) \"Dedi \"))\n                                              '(:eval (when (window-parameter (selected-window) 'window-side) \"Side \"))\n                                              '(current-input-method current-input-method-title)\n                                              '(god-local-mode \"God \")\n                                              '(defining-kbd-macro \"Def \")\n                                              '(flymake-mode flymake-mode-line-format)\n                                              '(:eval (when (buffer-modified-p) \"M \"))\n                                              '(:eval (unless (eq evil-state 'normal)\n                                                        (string-trim evil-mode-line-tag))))\n    \"A list of mode line indicators that is displayed on active window.\")\n\n  (put 'my-mode-line-indicators 'risky-local-variable t)\n\n  (setopt my-mode-line-format '(\"%e\"\n                                mode-line-front-space\n                                nil ;; eshell\n                                (:eval (when (mode-line-window-selected-p)\n                                         (list my-mode-line-indicators\n                                               mode-line-misc-info)))\n\n                                mode-line-format-right-align\n\n                                mode-line-buffer-identification\n                                (vc-mode vc-mode)\n                                \" \"\n                                mode-name\n                                mode-line-end-spaces))\n\n  (setup bindings\n    (:option mode-line-buffer-identification (propertized-buffer-identification \"%b\")\n             mode-line-format my-mode-line-format\n             mode-line-right-align-edge 'right-fringe))\n#+end_src\n\n*** Midnight\n=midnight= is Emacs' built-in cron-like service that run once during midnight each day. Its main purpose is to do same maintenance for the Emacs instance, such as cleaning very old unused buffers. It simply invokes ~midnight-hook~ (which contains ~#'clean-buffer-list~ by default) ~midnight-delay~ seconds after the midnight.\n\n#+begin_src emacs-lisp\n  (setup midnight\n    (:option midnight-delay (* 4 60 60))\n    (:enable))\n#+end_src\n\n*** Auto Save\n#+begin_src emacs-lisp\n  (setup files\n    (let ((autosave-dir (exdg-cache \"auto-save/\")))\n      (mkdir autosave-dir t)\n      (:option auto-save-file-name-transforms\n               `((\"\\\\`/[^/]*\\\\([^/]*/\\\\)*\\\\([^/]*\\\\)\\\\'\" ,(concat autosave-dir \"\\\\2\") t)))))\n#+end_src\n\n*** Recentf\n[[info:emacs#File Conveniences][recentf]] is an Emacs built-in minor mode that saves recent file list.\n#+begin_src emacs-lisp\n  (setup recentf\n    (:option recentf-save-file (exdg-state \"recentf-save.el\"))\n    (:enable))\n#+end_src\n\n*** Save History\n[[help:savehist-mode][savehist]] is an Emacs built-in minor mode that save minibuffer histories to a file.\n#+begin_src emacs-lisp\n  (setup savehist\n    (:option savehist-file (exdg-state \"savehist.el\"))\n    (:enable))\n#+end_src\n\n***  COMMENT Save Place\n[[https://www.emacswiki.org/emacs/SavePlace][Save Place]] is a Emacs built-in mode that \"nave place in files between sessions\".\n#+begin_src emacs-lisp\n  (setup saveplace\n    (:option save-place-forget-unreadable-files nil)\n    (save-place-mode 1))\n#+end_src\n\n*** Editorconfig\n[[https://editorconfig.org/][editorconfig]] is a very handy tool that standardize how different editors should behave according to different language, including tab width, trailing space and so on. It is not only helpful for team to maintain a codestyle standard, but also a handful tool for people use several different editors / computers, like I do.\n\n[[https://github.com/editorconfig/editorconfig-emacs][editorconfig-emacs]] implements its own =editorconfig= core, so it's logical to assume that it works on any platform. It is built-in since Emacs 30.\n#+begin_src emacs-lisp\n  (setup editorconfig\n    (:enable))\n#+end_src\n\n*** Envrc\n[[https://github.com/purcell/envrc][envrc]] is Emacs' integration with [[https://direnv.net/][direnv]] that works in buffer-local style.\n\n[[https://github.com/purcell/inheritenv][interitenv]].\n#+begin_src scheme\n  (simple-service\n   'home-emacs-envrc\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-envrc\")\n      (specification-\u003epackage\n       \"emacs-inheritenv\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup envrc\n    (:system 'gnu/linux)\n    (:also-load inheritenv)\n    (:with-mode envrc-global-mode\n      (:hook-into after-init)))\n#+end_src\n\n\n*** Subword\n[[help:subword-mode][subword-mode]] is an Emacs built-in that makes =CamelCase= be considered as 2 separate words =Camel= and =Case=. Evil also respects this minor mode. I've found that to turn on this mode is almost always positive for Evil usages, because the =io= =ao= text objects select the whole symbol anyway, pretty much covers the non-subword usage. There is also [[help:superword-mode][superword-mode]] BTW. See [[info:emacs#MixedCase Words][MixedCase Words]] and [[info:emacs#Misc for Programs][Misc for Programs]] in the documentation.\n#+begin_src emacs-lisp\n  (setup subword\n    (:hook-into text-mode prog-mode))\n#+end_src\n\n*** Highlight Parentheses\n[[https://git.sr.ht/~tsdh/highlight-parentheses.el][highlight-parentheses]], well, highlights parentheses surrounding [[info:emacs#Point][point]].\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-public emacs-highlight-parentheses\n    (let ((version \"2.2.2\")\n          (revision \"0\")\n          (url \"https://git.sr.ht/~tsdh/highlight-parentheses.el\"))\n      (package\n        (name \"emacs-highlight-parentheses\")\n        (version version)\n        (source\n         (origin\n           (method git-fetch)\n           (uri\n            (git-reference\n             (url url)\n             (commit version)))\n           (file-name (git-file-name name version))\n           (sha256\n            (base32 \"0wvhr5gzaxhn9lk36mrw9h4qpdax5kpbhqj44745nvd75g9awpld\"))))\n        (build-system emacs-build-system)\n        (home-page url)\n        (synopsis \"Highlights parentheses surrounding point in Emacs\")\n        (description \"Highlight-parentheses.el dynamically highlights\n  the parentheses surrounding point based on nesting-level using configurable\n  lists of colors, background colors, and other properties.\")\n        (license license:gpl3))))\n#+end_src\n\n#+begin_src scheme\n  (simple-service\n   'home-emacs-highlight-parentheses\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-highlight-parentheses\")))))\n#+end_src\n\nThe configs here is basically from [[https://protesilaos.com/emacs/modus-themes#h:24bab397-dcb2-421d-aa6e-ec5bd622b913][Note on highlight-parentheses.el]] in Modus Themes documentation, modified a little bit.\n#+begin_src emacs-lisp\n  (setup (:package highlight-parentheses)\n    (defvar my-highlight-parentheses-use-background t\n      \"Prefer `highlight-parentheses-background-colors'.\")\n\n    (setq my-highlight-parentheses-use-background t) ; Set to nil to disable backgrounds\n\n    (modus-themes-with-colors\n      ;; Our preference for setting either background or foreground\n      ;; styles, depending on `my-highlight-parentheses-use-background'.\n      (if my-highlight-parentheses-use-background\n\n          ;; Here we set color combinations that involve both a background\n          ;; and a foreground value.\n          (setq highlight-parentheses-background-colors (list bg-cyan-intense\n                                                              bg-magenta-intense\n                                                              bg-green-intense\n                                                              bg-yellow-intense)\n                highlight-parentheses-colors (list cyan\n                                                   magenta\n                                                   green\n                                                   yellow))\n\n        ;; And here we pass only foreground colors while disabling any\n        ;; backgrounds.\n        (setq highlight-parentheses-colors (list green-intense\n                                                 magenta-intense\n                                                 blue-intense\n                                                 red-intense)\n              highlight-parentheses-background-colors nil)))\n    (:hook-into prog-mode)\n    (:with-function highlight-parentheses-minibuffer-setup\n      (:hook-into minibuffer-setup)))\n#+end_src\n\n*** Transient\n#+begin_src emacs-lisp\n  (setup transient\n    (:option transient-history-file (exdg-state \"transient/history.el\")\n             transient-levels-file (exdg-state \"transient/levels.el\")\n             transient-values-file (exdg-state \"transient/values.el\")))\n#+end_src\n\n*** Evil\nIt's name tells everything: the Extensible Vi Layer for Emacs, [[https://github.com/emacs-evil/evil][Evil]]. It works pretty well as a Vim simulation, much better than VsCode's or Intellij's. Besides, it is charming combination of Vim's model-based editing with Emacs' keymap system, to some extent, as a personal opinion, better than the native Vim on the model-based editing system.\n\nReferences:\n- [[https://github.com/noctuid/evil-guide][evil-guide]] by noctuid\n#+begin_src scheme\n  (simple-service\n   'home-emacs-evil\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (map specification-\u003epackage\n          (list\n           \"emacs-goto-chg\"\n           \"emacs-evil\"\n           \"emacs-evil-collection-next\"\n           \"emacs-evil-surround\"\n           \"emacs-evil-snipe\"\n           \"emacs-evil-commentary\")))))\n#+end_src\n\n[[https://github.com/noctuid/annalist.el][annalist]] is a dependency of =emacs-evil-collection=, and its test dependency [[https://github.com/abo-abo/lispy][lispy]] somehow fail to build under Emacs 30 because of test failures. I simply disable tests for  =annalist= and deletes all its test dependencies.\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-public emacs-annalist-minimal\n    (package\n      (inherit upstream:emacs-annalist)\n      (name \"emacs-annalist-minimal\")\n      (native-inputs '())\n      (arguments (substitute-keyword-arguments\n                     (package-arguments upstream:emacs-annalist)\n                   ((#:tests? t) #f)))))\n#+end_src\n\nI need some latest contributions to the =evil-collection= repository:\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-public emacs-evil-collection-next\n    (let ((commit \"20c415aaa07c6541753489b166cd58d6771bd1e1\")\n          (last-release-version \"0.0.10\")\n          (revision \"0\"))\n      (package\n       (inherit upstream:emacs-evil-collection)\n       (name \"emacs-evil-collection-next\")\n       (version (git-version last-release-version revision commit))\n       (source\n        (origin\n         (method git-fetch)\n         (uri (git-reference\n               (url \"https://github.com/emacs-evil/evil-collection\")\n               (commit commit)))\n         (file-name (git-file-name name version))\n         (sha256\n          (base32\n           \"17ifxk4lpj1l52b3m2x5sj5ywdnrjyy1hbvfbvg4zwa1kc0l3ds1\"))))\n       (propagated-inputs\n        (modify-inputs (package-propagated-inputs upstream:emacs-evil-collection)\n                       (replace \"emacs-annalist\" emacs-annalist-minimal))))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package evil)\n    (:option\n     evil-want-integration t ;; require by collection\n     evil-want-keybinding nil ;; require by collection\n     evil-echo-state nil ;; Don't echo the =\u003cINSERT\u003e= etc info in minibuffer.\n     evil-undo-system 'undo-redo ;; Use Emacs 28 new ~undo-redo~ as the undo-redo system\n     evil-disable-insert-state-bindings t ;; I don't want to use Vim's insert mode bindings in insert state:\n     evil-respect-visual-line-mode t ;; When =visual-line-mode= is set (especially in =org-mode=), I want Vim to behave as visual lines are normal lines (i.e. bind =j= to =gj= etc)\n     evil-mode-line-format nil\n     evil-search-module 'evil-search)\n    (:require evil)\n    (:enable)\n    (:global\n     (:with-state (motion insert)\n       (:unbind \"C-z\"))\n     (:with-state (normal)\n       (:bind \"\u003cCTRL-i\u003e\" #'evil-jump-forward))))\n\n  (setup (:package evil-collection)\n    (:option evil-collection-setup-minibuffer t\n             evil-collection-key-blacklist '(\"SPC\" \"C-SPC\" \"DEL\" \"C-z\"))\n    (:require evil-collection)\n    (evil-collection-init))\n#+end_src\n\nI don't really use =;= at all, so I map it to ~evil-avy-goto-char-2~, which has the functionality similar to =vim-sneak=. I switched from =s= to =;= because overriding it for Magit is kind of hard, and it is even harder to come up with a new mnemonic for \"staging\".\n#+begin_src emacs-lisp\n  (setup evil\n    (:global\n     (:with-state (normal motion operator visual)\n       (:bind \";\" #'evil-avy-goto-char-2))))\n#+end_src\nAnd Dired EPA integration's keybindings conflicts with =;=, so I'd like to unbind them:\n#+begin_src emacs-lisp\n  (setup dired\n    (:with-state (normal)\n      (:unbind \";\")))\n#+end_src\n\n**** Noun-Verb Editing\nSee [[https://www.hiecaq.org/posts/20250409T225608M449.html][my blog post]] for details.\n#+begin_src emacs-lisp\n  (setup evil\n    (defun my-evil-inner-thing ()\n      (interactive)\n      (evil-visual-char)\n      (set-transient-map evil-inner-text-objects-map))\n\n    (defun my-evil-outer-thing ()\n      (interactive)\n      (evil-visual-char)\n      (set-transient-map evil-outer-text-objects-map))\n\n    (:global\n     (:with-map (evil-operator-state-map evil-visual-state-map)\n       (:unbind \"a\" \"i\")\n       (:bind \".\" evil-inner-text-objects-map\n              \",\" evil-outer-text-objects-map\n              \"a\" #'evil-append\n              \"i\" #'evil-insert))\n     (:with-state (normal)\n       (:bind \".\" #'my-evil-inner-thing\n              \",\" #'my-evil-outer-thing\n              \"#\" #'evil-repeat))))\n#+end_src\n\n**** Move up and down a list\n#+begin_src emacs-lisp\n  (setup evil\n    (:global\n     (:with-state (motion)\n       (:bind \"-\" #'backward-up-list\n              \"+\" #'down-list))))\n#+end_src\n\n**** Evil Surround\n[[https://github.com/emacs-evil/evil-surround][evil-surround]] defines operators that change/add/delete delimiters around a text object.\nI remap them all to =s= for better consistency.\n#+begin_src emacs-lisp\n  (setup (:package evil-surround)\n    (:with-state (operator visual)\n      (:unbind \"s\" \"S\" \"g S\"))\n    (:with-state (normal operator)\n      (:bind \"s\" #'evil-surround-edit\n             \"S\" #'evil-Surround-edit))\n    (:with-state visual\n      (:bind \"s\" #'evil-surround-region\n             \"S\" #'evil-Surround-region))\n    (:also-load evil)\n    (:with-function turn-on-evil-surround-mode\n      (:hook-into prog-mode text-mode wdired-mode comint-mode eshell-mode minibuffer-setup))\n    (define-advice wdired-change-to-dired-mode (:after (\u0026rest _) turn-off-evil-surround)\n      \"`wdired-mode-hook' is only called when activated, so we have to do this manually when deactivating.\"\n      (turn-off-evil-surround-mode)))\n#+end_src\n\n**** Evil Replace With Register\n[[https://github.com/Dewdrops/evil-ReplaceWithRegister][evil-replace-with-register]] defines a =replace= operator. However, we can implement its functionality easily with Evil mode itself, see [[https://emacs-china.org/t/evil-replace-with-register/27638][this post]]. I add some simple code to the solution there to make =\"\"= register work as the way I want.\n#+begin_src emacs-lisp\n  (evil-define-operator my-evil-replace-with-register (count beg end type register)\n    \"Replacing an existing text with the contents of a register\"\n    :move-point nil\n    (interactive \"\u003cvc\u003e\u003cR\u003e\u003cx\u003e\")\n    (setq count (or count 1))\n    (let ((saved (evil-get-register ?\\\")))\n      (if (eq type 'block)\n          (evil-visual-paste count register)\n        (delete-region beg end)\n        (evil-paste-before count register))\n      (evil-set-register ?\\\" saved)))\n\n  (setup evil\n    (:global (:with-state (normal visual)\n               (:bind \"g s\" #'my-evil-replace-with-register))))\n#+end_src\n\n**** Evil Snipe\n[[https://github.com/hlissner/evil-snipe][evil-snipe]] is a Evil port of Vim's [[https://github.com/rhysd/clever-f.vim][clever-f]] and [[https://github.com/justinmk/vim-sneak][vim-sneak]]. It currently does not support separating the scope for =f/F/t/T= from for =s/S=, which is a little bit annoying.\n\nThere is currently a bug in =evil-snipe='s type declarations for ~evil-snipe-scope~, so I forked it. Once the PR is merged, I'll switch back to the upstream version.\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-public emacs-evil-snipe\n    (let ((commit \"3ad53b8da0dd23093a3f2f0e5c13ecdb08ba8efa\")\n          (last-release-version \"2.0.8\") ;; from the el file version header\n          (revision \"0\")\n          (url \"https://github.com/hiecaq/evil-snipe\"))\n      (package\n       (name \"emacs-evil-snipe\")\n       (version (git-version last-release-version revision commit))\n       (source\n        (origin\n         (method git-fetch)\n         (uri (git-reference\n               (url url)\n               (commit commit)))\n         (file-name (git-file-name name version))\n         (sha256\n          (base32\n           \"0fk9nl0h1j1ig6pvb4aix3injxi2jyw9djixchxf4aky11znivgj\"))))\n       (propagated-inputs\n        (list upstream:emacs-evil))\n       (build-system emacs-build-system)\n       (home-page url)\n       (synopsis \"2-char searching ala vim-sneak \u0026 vim-seek, for evil-mode\")\n       (description \"This library It provides 2-character motions for quickly\n  (and more accurately) jumping around text, compared to evil's built-in\n  f/F/t/T motions, incrementally highlighting candidate targets as you type.\")\n       (license license:expat))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package evil-snipe)\n    (:require evil-snipe)\n    (:with-function turn-off-evil-snipe-override-mode (:hook-into magit-mode))\n    (:option evil-snipe-repeat-scope 'whole-line)\n    (:with-mode evil-snipe-override-mode\n      (:enable)))\n#+end_src\n\n**** Evil Commentary\n[[https://github.com/linktohack/evil-commentary][evil-commentary]] defines operators for commenting.\n#+begin_src emacs-lisp\n  (setup (:package evil-commentary)\n    (:also-load evil)\n    (:enable))\n#+end_src\n\n**** Window map\nAdd my helper commands to the ~evil-window-map~\n#+begin_src emacs-lisp\n  (setup evil\n    (:with-map evil-window-map\n      (:bind \"M-s\"  #'my-window-shot\n             \"M-r\"  #'my-window-record)))\n#+end_src\n\n*** God mode\n[[https://github.com/emacsorphanage/god-mode][god-mode]] provides a minor mode in which modifier keys of key bindings are handled sepecially: =C-= is not needed any more, =M-= is implied with a single key, etc.\n#+begin_src scheme\n  (simple-service\n   'home-emacs-god-mode\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-god-mode\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package god-mode)\n    (:require god-mode)\n    (:option god-mode-alist '((nil . \"C-\") (\"m\" . \"M-\") (\"M\" . \"C-M-\"))\n             god-mode-enable-function-key-translation t)\n    (:global\n     (:with-state (normal visual motion)\n       (:bind \"SPC\" #'god-execute-with-current-bindings))\n     (:with-state (insert emacs motion)\n       (:bind \"C-\u003cespace\u003e\" #'god-execute-with-current-bindings)))\n    (defun my-god-mode-lookup-key-sequence (\u0026optional key key-string-so-far)\n      \"Retry with literal KEY when the non-literal attempt failed.\"\n      (interactive)\n      (let ((sanitized-key\n             (god-mode-sanitized-key-string\n              (or key (read-event key-string-so-far)))))\n        (condition-case nil\n            (god-mode-lookup-command\n             (god-key-string-after-consuming-key sanitized-key key-string-so-far))\n          (error (when key-string-so-far\n                   (setq god-literal-sequence t)\n                   (god-mode-lookup-command\n                    (god-key-string-after-consuming-key sanitized-key key-string-so-far)))))))\n\n    (advice-add #'god-mode-lookup-key-sequence :override #'my-god-mode-lookup-key-sequence))\n#+end_src\n\n*** Which key\n[[https://github.com/justbur/emacs-which-key][which-key]] is a minor mode that hints you the keybindings prefixed with what you have typed when you get stuck. It is built-in since Emacs 30.\n\nI turned off ~which-key-show-transient-maps~ because it has cause ~embark-act~ on a non-minibuffer target to behave strangely when the binding in keymap is longer than a single key:\n- Embark loses focus on the minibuffer (and is captured to the window containing the target)  if ~embark-prefix-help-command~ is queried after giving the first key\n- ~embark-prefix-help-command~ cannot shows the correct keymap after the first key is given\n#+begin_src emacs-lisp\n  (setup which-key\n    (:option which-key-show-transient-maps nil))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:require which-key)\n    (:option which-key-use-C-h-commands nil)\n    (which-key-enable-god-mode-support)\n    (:enable))\n#+end_src\n\nAs a side note, which-key default configuration requires there to be at least 1 slot at the bottom in ~window-sides-slots~.\n*** Posframe\n[[https://github.com/tumashu/posframe][posframe]] pops a child-frame at point, connected to its root window's buffer.\n#+begin_src scheme\n  (simple-service\n   'home-emacs-posframe\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-posframe\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package posframe))\n#+end_src\n\n*** Eldoc\n#+begin_src emacs-lisp\n  (setup eldoc\n    (:option eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly\n             (prepend display-buffer-alist) `(,(rx \"*eldoc*\")\n                                              (display-buffer-reuse-mode-window display-buffer-in-direction)\n                                              (direction . right)\n                                              (window-width . fit-window-to-buffer-horiz)\n                                              (body-function . select-window)\n                                              (dedicated . t)\n                                              (window-parameters . ((mode-line-format . none))))))\n#+end_src\n\n[[https://github.com/casouri/eldoc-box][eldoc-box]] shows eldoc in a separate childframe instead of the crowded echo area.\n#+begin_src scheme\n  (simple-service\n   'home-emacs-eldoc-box\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-eldoc-box\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package eldoc-box)\n    (:option eldoc-box-clear-with-C-g t\n             eldoc-box-doc-separator\n             (concat \"\\n\"\n                     (propertize \" \" 'face 'completions-group-separator\n                                 'display '(space :align-to right)))\n             eldoc-box-max-pixel-width 1600\n             eldoc-box-max-pixel-height 1400)\n    (:with-function eldoc-box-hover-mode\n      (:hook-into text-mode prog-mode))\n\n    (defun my-eldoc-box-quit-frame-when-interactive (interactive)\n      \"When manually open the doc buffer, close eldoc-box immediately.\"\n      (when interactive\n        (eldoc-box-quit-frame)))\n    (advice-add #'eldoc-doc-buffer :before #'my-eldoc-box-quit-frame-when-interactive))\n#+end_src\n\n*** Ace Window\n[[https://github.com/abo-abo/ace-window][ace-window]] is helpful to do things the \"embark\" way: pick a window, then decide what to do with it.\n\nIts package definition in the Guix official channel is for the \"latest\" release version, which is as old as 2014. So I makes a variation to use the master branch HEAD at the time of writing.\n#+begin_src scheme :tangle \"build/hiecaq/packages/emacs-xyz.scm\" :noweb-ref nil\n  (define-public emacs-ace-window-next\n    (let ((commit \"77115afc1b0b9f633084cf7479c767988106c196\")\n          (last-release-version \"0.10.0\")\n          (revision \"0\"))\n      (package\n       (inherit upstream:emacs-ace-window)\n       (name \"emacs-ace-window-next\")\n       (version (git-version last-release-version revision commit))\n       (source\n        (origin\n         (method git-fetch)\n         (uri (git-reference\n               (url \"https://github.com/abo-abo/ace-window\")\n               (commit commit)))\n         (file-name (git-file-name name version))\n         (sha256\n          (base32\n           \"1l6rp92q4crahx9nq7s6zxqyw7ccrhkl95v70vxra7zndqpqwsbq\")))))))\n#+end_src\n\n#+begin_src scheme\n  (simple-service\n   'home-emacs-ace-window\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (list\n      (specification-\u003epackage\n       \"emacs-ace-window-next\")))))\n#+end_src\n\n#+begin_src emacs-lisp\n  (setup (:package ace-window)\n    (:also-load ace-window-posframe)\n    (:option aw-keys '(?u ?h ?e ?t ?i ?d ?o ?n ?a ?s)\n             aw-translate-char-function (lambda (c)\n                                          (pcase c\n                                            (?\\[ ?7)\n                                            (?\\{ ?5)\n                                            (?\\} ?3)\n                                            (?\\( ?1)\n                                            (?= ?9)\n                                            (?* ?0)\n                                            (?\\) ?2)\n                                            (?+ ?4)\n                                            (?\\] ?6)\n                                            (?! ?8)\n                                            (_ c)))\n             aw-dispatch-alist '((?Q aw-delete-window \"Delete Window\")\n                                 (?W aw-swap-window \"Swap Windows\")\n                                 (?M aw-move-window \"Move Window\")\n                                 (?C aw-copy-window \"Copy Window\")\n                                 (?J aw-switch-buffer-in-window \"Select Buffer\")\n                                 (?D aw-use-frame \"Make frame for window\")\n                                 (?N aw-flip-window)\n                                 (?U aw-switch-buffer-other-window \"Switch Buffer Other Window\")\n                                 (?E aw-execute-command-other-window \"Execute Command Other Window\")\n                                 (?F aw-split-window-fair \"Split Fair Window\")\n                                 (?S aw-split-window-vert \"Split horizontally\")\n                                 (?V aw-split-window-horz \"Split vertically\")\n                                 (?O delete-other-windows \"Delete Other Windows\")\n                                 (?T aw-transpose-frame \"Transpose Frame\")\n                                 ;; ?i ?r ?t are used by hyperbole.el\n                                 (?? aw-show-dispatch-help))))\n\n  (setup evil\n    (:global (:rebind #'evil-window-next #'ace-window\n                      #'other-window  #'ace-window)))\n#+end_src\n\n=ace-window= has its =posframe= integration now (which is the main reason why I need more recent commits), which use it to show the keys in the centers of buffers.\n#+begin_src emacs-lisp\n  (setup ace-window-posframe\n    (:when-loaded (:enable)))\n#+end_src\n\n*** Spell Checking\nSee the [[info:emacs#Spelling][documentation]] for details.\n\nEmacs comes with a spell checking wrapper...\n#+begin_src emacs-lisp\n  (setup ispell\n    (:needs \"hunspell\")\n    (:option ispell-program-name \"hunspell\"))\n#+end_src\n\n... and an on-the-fly spell checker(which uses ~ispell~ as the backend).\n#+begin_src emacs-lisp\n  (setup flyspell\n    (:needs \"hunspell\")\n    (:unbind \"C-;\")\n    (:hook-into text-mode)\n    (:with-mode flyspell-prog-mode\n      (:hook-into prog-mode)))\n#+end_src\n\n**** Flyspell Correct\nThe default UI for ~ispell~ is quite hard to use, and there is a package [[https://github.com/d12frosted/flyspell-correct][flyspell-correct]] that makes use of the ~completing-read~ interface to make things much more usable.\n\nNote that the version in official Guix Package Channel is =0.6.1=, which was 3 years ago. It is kind of broken on my site, so I'll use the master HEAD version instead:\n#+begin_src scheme\n  (simple-service\n   'home-emacs-flyspell\n   home-emacs-service-type\n   (home-emacs-extension\n    (packages\n     (map specification-\u003epackage\n          '(\"hunspell\"\n            \"hunspell-dict-en-us\"\n            \"emacs-flyspell-correct-next\")))))\n#+end_src\n\nI ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiecaq%2Fguix-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhiecaq%2Fguix-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhiecaq%2Fguix-config/lists"}