An open API service indexing awesome lists of open source software.

https://github.com/hiecaq/guix-config

My literate configuration for Guix
https://github.com/hiecaq/guix-config

dotfiles guix guix-configuration guix-home literate-programming

Last synced: about 1 year ago
JSON representation

My literate configuration for Guix

Awesome Lists containing this project

README

          

# -*- org-use-property-inheritance: t; toc-org-max-depth: 4; org-confirm-babel-evaluate: nil; -*-
#+title: hiecaq's guix configuration
#+startup: indent
#+property: header-args :comments org :results silent :mkdirp t

#+toc: headlines 2

* Table of Contents :TOC:noexport:
- [[#introduction][Introduction]]
- [[#system-configuration][System Configuration]]
- [[#private-info][Private Info]]
- [[#kernel-firmware-and-initial-ram-disk][Kernel, Firmware and Initial RAM Disk]]
- [[#bootloader][Bootloader]]
- [[#file-system][File System]]
- [[#btrfs-subvolumes][BTRFS subvolumes]]
- [[#tmp][TMP]]
- [[#efi][EFI]]
- [[#device-ids][Device IDs]]
- [[#users][Users]]
- [[#packages][Packages]]
- [[#services][Services]]
- [[#smart-card][Smart Card]]
- [[#game-controllers][Game Controllers]]
- [[#android][Android]]
- [[#hardware-monitor][Hardware monitor]]
- [[#default-services-modification][Default Services Modification]]
- [[#guix][Guix]]
- [[#gdm][GDM]]
- [[#audio][Audio]]
- [[#home-configuration][Home Configuration]]
- [[#guix-1][Guix]]
- [[#locales][Locales]]
- [[#channels][Channels]]
- [[#nonguix][Nonguix]]
- [[#cross-desktop-group-xdg][Cross-Desktop Group (XDG)]]
- [[#base-directories][Base Directories]]
- [[#user-directories][User Directories]]
- [[#shells][Shells]]
- [[#fish][Fish]]
- [[#tools][Tools]]
- [[#certificates][Certificates]]
- [[#bat][bat]]
- [[#eza][eza]]
- [[#ripgrep][ripgrep]]
- [[#fd][fd]]
- [[#direnv][Direnv]]
- [[#aliases][Aliases]]
- [[#fonts][Fonts]]
- [[#search][Search]]
- [[#system-service][System service]]
- [[#desktop-environment][Desktop Environment]]
- [[#display][Display]]
- [[#d-bus][D-Bus]]
- [[#xdg-desktop-portal][XDG Desktop Portal]]
- [[#audio-and-sound][Audio and Sound]]
- [[#emacs][Emacs]]
- [[#basics][Basics]]
- [[#early-initialization][Early Initialization]]
- [[#packages-1][Packages]]
- [[#special-key-remapping][Special Key Remapping]]
- [[#fonts-1][Fonts]]
- [[#some-configurations-that-might-make-sense-to-put-here][Some Configurations that might make sense to put here]]
- [[#main-configurations][Main Configurations]]
- [[#packages-2][Packages]]
- [[#setupel][setup.el]]
- [[#some-sane-configurations][Some Sane Configurations]]
- [[#android-1][Android]]
- [[#window-management][Window Management]]
- [[#universal-argument][Universal Argument]]
- [[#pcre][PCRE]]
- [[#help][Help]]
- [[#info][Info]]
- [[#xdg][Xdg]]
- [[#no-littering][No Littering]]
- [[#modus-themes][Modus Themes]]
- [[#mode-line][Mode Line]]
- [[#midnight][Midnight]]
- [[#auto-save][Auto Save]]
- [[#recentf][Recentf]]
- [[#save-history][Save History]]
- [[#editorconfig][Editorconfig]]
- [[#envrc][Envrc]]
- [[#subword][Subword]]
- [[#highlight-parentheses][Highlight Parentheses]]
- [[#transient][Transient]]
- [[#evil][Evil]]
- [[#noun-verb-editing][Noun-Verb Editing]]
- [[#move-up-and-down-a-list][Move up and down a list]]
- [[#evil-surround][Evil Surround]]
- [[#evil-replace-with-register][Evil Replace With Register]]
- [[#evil-snipe][Evil Snipe]]
- [[#evil-commentary][Evil Commentary]]
- [[#window-map][Window map]]
- [[#god-mode][God mode]]
- [[#which-key][Which key]]
- [[#posframe][Posframe]]
- [[#eldoc][Eldoc]]
- [[#ace-window][Ace Window]]
- [[#spell-checking][Spell Checking]]
- [[#flyspell-correct][Flyspell Correct]]
- [[#xref][Xref]]
- [[#topsy][Topsy]]
- [[#orderless][Orderless]]
- [[#vertico][Vertico]]
- [[#marginalia][Marginalia]]
- [[#consult][Consult]]
- [[#embark][Embark]]
- [[#consult-based-embark-cycle][Consult-based Embark Cycle]]
- [[#embark-as-the-leader-key][Embark as the leader key]]
- [[#open-in-chosen-window][Open in chosen window]]
- [[#keybindings-for-copy][Keybindings for copy]]
- [[#copy-file-name-with-line-number][Copy file name with line number]]
- [[#tempel][Tempel]]
- [[#eglot-tempel][Eglot-tempel]]
- [[#corfu][Corfu]]
- [[#visual-undo][Visual Undo]]
- [[#hideshow][Hideshow]]
- [[#pulse][Pulse]]
- [[#electric-pair-mode][electric-pair-mode]]
- [[#aggresive-indent][Aggresive Indent]]
- [[#eshell][Eshell]]
- [[#fish-completion][fish-completion]]
- [[#ediff][Ediff]]
- [[#magit][Magit]]
- [[#project][Project]]
- [[#emacsql][Emacsql]]
- [[#epub][Epub]]
- [[#pdf][Pdf]]
- [[#org-mode][Org Mode]]
- [[#general-settings][General Settings]]
- [[#task-management][Task Management]]
- [[#literate-programming][Literate Programming]]
- [[#evil-org][Evil Org]]
- [[#toc-org][Toc Org]]
- [[#org-appear][Org Appear]]
- [[#personal-knowledge-management][Personal Knowledge Management]]
- [[#style-and-faces][Style and Faces]]
- [[#english][English]]
- [[#linting][Linting]]
- [[#capitalizing][Capitalizing]]
- [[#dictionary][Dictionary]]
- [[#eglot][Eglot]]
- [[#haskell][Haskell]]
- [[#rust][Rust]]
- [[#dhall][Dhall]]
- [[#ron][Ron]]
- [[#dart][Dart]]
- [[#plantuml][PlantUML]]
- [[#yaml][YAML]]
- [[#email][Email]]
- [[#mu4e][mu4e]]
- [[#mpv][MPV]]
- [[#pyim][PYIM]]
- [[#vterm][vterm]]
- [[#eat][Eat]]
- [[#dired][Dired]]
- [[#dired-rsync][dired-rsync]]
- [[#emms][EMMS]]
- [[#guix-1][Guix]]
- [[#desktop-notification-daemon][Desktop Notification Daemon]]
- [[#references-and-recommendations][References and Recommendations]]

* Introduction
This 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.

Unless explicitly stated, all code in this configuration is under GPL3 license.

* System Configuration
This is the main entry point for =guix system=. It can be deployed with
#+begin_src sh
sudo guix system -L build reconfigure build/system-configuration.scm
#+end_src

#+begin_src scheme :tangle "build/system-configuration.scm" :noweb yes
(use-modules (gnu)
(guix channels)
(gnu system nss)
(gnu packages haskell)
(hiecaq services search)
<>
)

(use-service-modules desktop ssh xorg linux security-token sound)
(use-package-modules bootloaders fonts games android)

<>

(define %channels
(cons*
<>
%default-channels))

(operating-system
<>

(users (cons*
<>
%base-user-accounts))

(packages (cons*
<>
%base-packages))

(services (cons*
<>
(modify-services %desktop-services
<>)))
;; Allow resolution of '.local' host names with mDNS.
(name-service-switch %mdns-host-lookup-nss))
#+end_src

** Private Info
Host name shows up in some places, and I'm not good at naming so I just call it "desktop".
#+begin_src scheme :noweb-ref operating-system
(host-name "desktop")
#+end_src

Time zone. All Linuxes should be synchronized to the same UTC time, and local time is then displayed based on the time zone.
#+begin_src scheme :noweb-ref operating-system
(timezone "Etc/UTC")
#+end_src

Nowadays locale should be just UTF-8.
#+begin_src scheme :noweb-ref operating-system
(locale "en_US.utf8")
#+end_src

I use standard keyboard layout too.
#+begin_src scheme :noweb-ref operating-system
(keyboard-layout (keyboard-layout "us"))
#+end_src

** Kernel, Firmware and Initial RAM Disk
I must confess that I have to use non-liberal firmware.
#+begin_src scheme :noweb-ref system-module
(nongnu packages linux)
(nongnu system linux-initrd)
#+end_src

Then the kernel and firmware are from nonguix channel:
#+begin_src scheme :noweb-ref operating-system
(kernel linux)
(initrd microcode-initrd)
(firmware (list linux-firmware))
#+end_src

I 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]].
#+begin_src scheme :noweb-ref operating-system
(initrd-modules (append
(list "xxhash" "xxhash_generic")
%base-initrd-modules))
#+end_src

** Bootloader
use 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]].
#+begin_src scheme :noweb-ref operating-system
(bootloader (bootloader-configuration
(bootloader grub-efi-bootloader)
(targets '("/efi"))
(keyboard-layout keyboard-layout)))
#+end_src

** File System
See [[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).

firstly, we need to declare the mapped devices, which handles our LUKS-encrypted device.
#+begin_src scheme :noweb yes :noweb-ref operating-system
(mapped-devices
(list (mapped-device
(source (uuid <>))
(target "cryptbtrfs")
(type luks-device-mapping))))
#+end_src

Then actual file systems are listed in this field:
#+begin_src scheme :noweb yes :noweb-ref operating-system
(file-systems (append
(list
<>
)
%base-file-systems))
#+end_src

*** BTRFS subvolumes
To 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.

Thus, mostly everything under =/=, exclude =/home=, is managed by Guix, so seldom they need to be backed up. The exceptions here I made are:
- =/home= :: as mentioned, I want it to be backed-up seperately
- =/swap= :: who need to back up swap?
- =/var/log= :: system logs, can be backed up often
- =/var/cache= and =/var/tmp= :: These are either temporary or can be regenerated, so no need to back up
#+begin_src scheme :noweb-ref file-system
(%btrfs "@" "/" mapped-devices)
(%btrfs "@home" "/home" mapped-devices)
(%btrfs "@swap" "/swap" mapped-devices)
(%btrfs "@log" "/var/log" mapped-devices)
(%btrfs "@cache" "/var/cache" mapped-devices)
(%btrfs "@tmp" "/var/tmp" mapped-devices)
#+end_src

Next, 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.
#+begin_src scheme :noweb-ref file-system
(%btrfs "@documents" "/home/hiecaq/documents" mapped-devices)
(%btrfs "@downloads" "/home/hiecaq/downloads" mapped-devices)
(%btrfs "@videos" "/home/hiecaq/videos" mapped-devices)
(%btrfs "@music" "/home/hiecaq/music" mapped-devices)
(%btrfs "@pictures" "/home/hiecaq/pictures" mapped-devices)
#+end_src

The above ~%btrfs~ is defined as
#+begin_src scheme :noweb-ref system-helper
(define* (%btrfs subvol mount-point dep #:key
(flags '(no-atime)))
(file-system
(device "/dev/mapper/cryptbtrfs")
(mount-point mount-point)
(type "btrfs")
(flags flags)
(options (string-append "compress=zstd,subvol=" subvol))
(dependencies dep)))
#+end_src

Important things are:
- =no-atime=, which is good for btrfs' CoW
- =compress=zstd=, that is to enable btrfs' transparent compression with Zstandard algorithm.

*** TMP
Guix 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
#+begin_src scheme :noweb-ref file-system
(file-system
(device "tmpfs")
(mount-point "/tmp")
(type "tmpfs")
(flags '(no-suid no-dev))
(options "size=80%")
(check? #f))
#+end_src

*** EFI
The last thing is to mount the EFI system partition:
#+begin_src scheme :noweb yes :noweb-ref file-system
(file-system
(device (uuid <> 'fat))
(mount-point "/efi")
(type "vfat"))
#+end_src

*** Device IDs
This is what's displayed in =lsblk -f -o UUID /dev/nvme0n1p2=
#+name: system-luks-device-uuid
#+begin_src scheme :exports none
"5e797e8b-d3b9-4693-b2f9-c17cf0943c34"
#+end_src

This is what's displayed in =lsblk -o UUID /dev/nvme0n1p1=
#+name: system-efi-device-uuid
#+begin_src scheme :exports none
"B09A-E98C"
#+end_src

** Users
I only have one explicit user:
#+begin_src scheme :noweb-ref operating-system-users
(user-account
(name "hiecaq")
(group "users")
(supplementary-groups '("wheel" "netdev"
"input" "audio" "video"
"plocate" "adbusers")))
#+end_src

** Packages
:PROPERTIES:
:header-args:scheme: :noweb-ref system-packages
:END:
Currently just a list of fonts:
#+begin_src scheme
font-hack
font-google-noto
font-google-noto-emoji
font-google-noto-sans-cjk
#+end_src

** Services
:PROPERTIES:
:header-args:scheme: :noweb-ref system-services
:END:

*** Smart Card
#+begin_src scheme
(service pcscd-service-type)
#+end_src

*** Game Controllers
We need the udev rules provided by Steam:
#+begin_src scheme
(udev-rules-service 'steam steam-devices-udev-rules)
#+end_src

*** Android
To 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.
#+begin_src scheme
(udev-rules-service 'android android-udev-rules
#:groups '("adbusers"))
#+end_src

*** Hardware monitor
To show the system fan speeds, my device need this kernel module:
#+begin_src scheme
(service kernel-module-loader-service-type
'("nct6683"))
#+end_src

** Default Services Modification
:PROPERTIES:
:header-args:scheme: :noweb-ref system-services-modify
:END:

*** Guix
We 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.
#+begin_src scheme
(guix-service-type
config => (guix-configuration
(inherit config)
(substitute-urls %substitute-urls)
(channels %channels)
(authorized-keys
(append (list %nonguix-signing-key)
%default-authorized-guix-keys))))
#+end_src

#+begin_src scheme :noweb-ref system-helper
(define %substitute-urls
'("https://ci.guix.gnu.org"
"https://substitutes.nonguix.org"))

(define %nonguix-signing-key
(plain-file "nonguix.pub" "\
(public-key
(ecc
(curve Ed25519)
(q #C1FD53E5D4CE971933EC50C9F307AE2171A2D3B52C804642A7A35F84F3A4EA98#)))"))
#+end_src

*** GDM
Somehow 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:
#+begin_src scheme
(gdm-service-type
config => (gdm-configuration
(inherit config)
(auto-suspend? #f)
(xorg-configuration
(let ((xorg-config ((@@(gnu services xorg) gdm-configuration-xorg) config)))
(xorg-configuration
(inherit xorg-config)
(extra-config (list %fix-amd-disappearing-cursor-conf)))))))
#+end_src

The fix is from https://forums.linuxmint.com/viewtopic.php?t=424779
#+begin_src scheme :noweb-ref system-helper
(define %fix-amd-disappearing-cursor-conf "
Section \"Device\"
Identifier \"AMD\"
Driver \"amdgpu\"
Option \"SWCursor\" \"true\"
EndSection
")
#+end_src

*** Audio
Remove pulseaudio.
#+begin_src scheme
(delete pulseaudio-service-type)
(alsa-service-type
config => (alsa-configuration
(inherit config)
(pulseaudio? #f)))
#+end_src

* Home Configuration
This is the main entry point for =guix home=. It can be tested with
#+begin_src sh
guix home -L build container build/home-configuration.scm
#+end_src
and deployed with
#+begin_src sh
guix home -L build reconfigure build/home-configuration.scm
#+end_src

#+begin_src scheme :tangle "build/home-configuration.scm" :noweb yes
(use-modules
(gnu home)
(gnu services)
(gnu packages)
<>
)

(home-environment
<>
(services
(append
<>
)))
#+end_src

This is a list of packages that are not installed by services. Eventually this list should be empty.
#+begin_src scheme :noweb-ref home-environment-conf
(packages (specifications->packages
(list
"neovim"
"guile"
)))
#+end_src

* Guix
This file defines those settings related to Guix itself.
#+begin_src scheme :tangle "build/hiecaq/home/guix.scm" :noweb yes
(define-module (hiecaq home guix)
#:use-module (gnu services)
#:use-module (gnu packages)
#:use-module (gnu home services)
#:use-module (gnu home services guix)
#:use-module (gnu home services gnupg)
#:use-module (gnu packages gnupg)
#:use-module (guix gexp)
#:use-module (guix channels))

(define-public services
(list
<>
))
#+end_src

Add this module and its services:
#+begin_src scheme :noweb-ref home-module
((hiecaq home guix) #:prefix guix:)
#+end_src

#+begin_src scheme :noweb-ref home-environment-service
guix:services
#+end_src

** Locales
Set the locales as recommended in [[https://guix.gnu.org/manual/en/html_node/Application-Setup.html][the manual]].
#+begin_src scheme :noweb-ref guix-service
(service
(service-type
(name 'home-locale)
(extensions
(list
(service-extension
home-profile-service-type
(const (list
(specification->package
"glibc-locales"))))
(service-extension
home-environment-variables-service-type
(const '(("GUIX_LOCPATH" . "${GUIX_PROFILE}/lib/locale"))))))
(default-value #f)
(description #f)))
#+end_src

** Channels
:PROPERTIES:
:header-args:scheme: :noweb-ref guix-channel
:END:

*** COMMENT Home Service
Since I'm using guix as a system, the home service is not needed.
#+begin_src scheme :noweb-ref guix-service
(simple-service
'variant-packages-service
home-channels-service-type
(list
<>
))
#+end_src

*** COMMENT RDE
[[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.

I no longer need functionality belongs to rde, but I keep it here for future reference.
#+begin_src scheme
(channel
(name 'rde)
(url "https://git.sr.ht/~abcdw/rde")
(introduction
(make-channel-introduction
"257cebd587b66e4d865b3537a9a88cccd7107c95"
(openpgp-fingerprint
"2841 9AC6 5038 7440 C7E9 2FFA 2208 D209 58C1 DEB0"))))
#+end_src

*** Nonguix
[[https://gitlab.com/nonguix/nonguix][nonguix]] holds non-free stuffs, including kernel, firmware, and other close-source binaries.
#+begin_src scheme
(channel
(name 'nonguix)
(url "https://gitlab.com/nonguix/nonguix")
(introduction
(make-channel-introduction
"897c1a470da759236cc11798f4e0a5f7d4d59fbc"
(openpgp-fingerprint
"2A39 3FFF 68F4 EF7A 3D29 12AF 6F51 20A0 22FB B2D5"))))
#+end_src

* Cross-Desktop Group (XDG)
This section defines those settings related to the [[https://www.freedesktop.org/wiki/Specifications/][XDG]] specifications.
#+begin_src scheme :tangle "build/hiecaq/home/xdg.scm" :noweb yes
(define-module (hiecaq home xdg)
#:use-module (gnu services)
#:use-module (gnu packages)
#:use-module (gnu home services)
#:use-module (gnu home services xdg)
#:use-module (guix channels))

(define-public services
(list
<>
))
#+end_src

Add this module and its services:
#+begin_src scheme :noweb-ref home-module
((hiecaq home xdg) #:prefix xdg:)
#+end_src

#+begin_src scheme :noweb-ref home-environment-service
xdg:services
#+end_src

** Base Directories
See [[https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html][Enviroment Variables chapter in latest XDG Base Directory Specification]] for the description on their purposes.

Guix 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.

Note 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.

** User Directories
As declared in [[https://www.freedesktop.org/wiki/Software/xdg-user-dirs/][xdg-user-dirs]], this defines "well known" user directories, and their localization.
#+begin_src scheme :noweb-ref xdg-service
(simple-service
'xdg-user-directories-config-service
home-xdg-user-directories-service-type
(home-xdg-user-directories-configuration
(desktop "$HOME/desktop")
(documents "$HOME/documents")
(download "$HOME/downloads")
(music "$HOME/music")
(pictures "$HOME/pictures")
(publicshare "$HOME/public")
(templates "$HOME/templates")
(videos "$HOME/videos")))
#+end_src

* Shells
#+begin_src scheme :tangle "build/hiecaq/home/shell.scm"
(define-module (hiecaq home shell)
#:use-module (gnu home)
#:use-module (gnu services)
#:use-module (gnu packages)
#:use-module (gnu home services)
#:use-module (guix channels)
#:use-module (gnu home services guix)
#:use-module (gnu home services shells)
#:use-module (guix gexp))
#+end_src

TODO: I should split this out later.
#+begin_src scheme :tangle "build/hiecaq/home/shell.scm" :noweb yes
(define-public services
(list
(simple-service
'extend-environment-variables
home-environment-variables-service-type
`(("PS1" . "$ ")
("MANPAGER" . "nvim +Man!")
("MANWIDTH" . "80")
("QT_AUTO_SCREEN_SCALE_FACTOR" . "1")
("RUSTUP_UPDATE_ROOT" . "https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup")
("RUSTUP_DIST_SERVER" . "https://mirrors.tuna.tsinghua.edu.cn/rustup")))
<>
))
#+end_src

Add this module and its services:
#+begin_src scheme :tangle no :noweb-ref home-module
((hiecaq home shell) #:prefix shell:)
#+end_src

#+begin_src scheme :tangle no :noweb-ref home-environment-service
shell:services
#+end_src
** Fish
:PROPERTIES:
:header-args:scheme: :noweb-ref shell-service
:END:
I use [[https://fishshell.com/][fish]] as a backup interactive-use-only shell.
#+begin_src scheme
(service
home-fish-service-type)
#+end_src
** COMMENT Zsh
:PROPERTIES:
:header-args:scheme: :noweb-ref shell-service
:END:
I'm currently using [[https://www.zsh.org/][zsh]] as my primary shell.
#+begin_src scheme
(service
home-zsh-service-type
(home-zsh-configuration
(zshrc
(list (slurp-file-like (local-file "../../.zshrc"
"zshrc"))
(slurp-file-like (local-file "../../.aliases"
"aliases"))))))
#+end_src

*** syntax highlighting
Add [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]], which provides "fish shell like syntax highlighting for Zsh."
#+begin_src scheme
(service
(service-type
(name 'home-zsh-syntax-highlighting)
(extensions
(list
(service-extension home-zsh-plugin-manager-service-type
(const
(list
(specification->package
"zsh-syntax-highlighting"))))
(service-extension
home-zsh-service-type
(const
(home-zsh-extension
(zshrc '("# Improve highlighting")))))))
(default-value #f)
(description #f)))
#+end_src
And its configuration
#+begin_src sh :tangle "build/.zshrc"
# Declare the variable
typeset -A ZSH_HIGHLIGHT_STYLES

# disable highlighting for unknown-token
ZSH_HIGHLIGHT_STYLES[unknown-token]='none'

# use blue to highlight command(e.g., git)
ZSH_HIGHLIGHT_STYLES[command]='fg=004'

# builtins(e.g., pwd): blue, italic
ZSH_HIGHLIGHT_STYLES[builtin]='fg=004,standout'

# commandseparator(;, &&): lighter gray
ZSH_HIGHLIGHT_STYLES[commandseparator]='fg=014'

# alias: blue
ZSH_HIGHLIGHT_STYLES[alias]='fg=004'

# single hyphen-option: darker red,italic
ZSH_HIGHLIGHT_STYLES[single-hyphen-option]='fg=001'

# double hyphen-option: darker red
ZSH_HIGHLIGHT_STYLES[double-hyphen-option]='fg=001'

# quoted arguments(strings)
ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=006'
ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=006'

# dollar quoted arguments:gold
ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=003'

# other commands: red
ZSH_HIGHLIGHT_STYLES[arg0]='fg=001'

# To define styles for nested brackets up to level 4
ZSH_HIGHLIGHT_STYLES[bracket-level-1]='fg=010'
ZSH_HIGHLIGHT_STYLES[bracket-level-2]='fg=014'
ZSH_HIGHLIGHT_STYLES[bracket-level-3]='fg=010'
ZSH_HIGHLIGHT_STYLES[bracket-level-4]='fg=014'
ZSH_HIGHLIGHT_STYLES[bracket-error]='fg=001'
ZSH_HIGHLIGHT_STYLES[cursor-matchingbracket]='fg=007'

ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets)
#+end_src

** Tools
:PROPERTIES:
:header-args:scheme: :noweb-ref shell-service
:END:
There are many tools that enhance the command line user experience.
*** Certificates
See 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?
#+begin_src scheme
(service
(service-type
(name 'home-certs)
(extensions
(list
(service-extension
home-profile-service-type
(const (list
(specification->package
"nss-certs"))))
(service-extension
home-environment-variables-service-type
(const '(("SSL_CERT_DIR" . "$HOME/.guix-home/profile/etc/ssl/certs")
("SSL_CERT_FILE" . "$SSL_CERT_DIR/ca-certificates.crt")
("GIT_SSL_CAINFO" . "$SSL_CERT_FILE")
("CURL_CA_BUNDLE" . "$SSL_CERT_FILE"))))))
(default-value #f)
(description #f)))
#+end_src
*** bat
Add [[https://github.com/sharkdp/bat][bat]], which is a =cat= clone with colors.
#+begin_src scheme
(service
(service-type
(name 'home-bat)
(extensions
(list
(service-extension
home-profile-service-type
(const (list
(specification->package
"bat"))))
(service-extension
home-environment-variables-service-type
(const '(("BAT_THEME" . "TwoDark"))))))
(default-value #f)
(description #f)))
#+end_src

*** eza
[[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=".
#+begin_src scheme
(service
(service-type
(name 'home-eza)
(extensions
(list
(service-extension
home-profile-service-type
(const (list
(specification->package
"eza"))))
(service-extension
home-environment-variables-service-type
(const '(("EZA_COLORS" .
"*.zip=0:*.gz=0:*.rar=0:*.tar=0:*.7z=0:ex=31:di=244;1"))))))
(default-value #f)
(description #f)))
#+end_src

*** ripgrep
Add [[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=.
#+begin_src scheme
(simple-service
'home-ripgrep
home-profile-service-type
(list
(specification->package
"ripgrep")))
#+end_src

*** fd
Add [[https://github.com/sharkdp/fd][fd]], which is "a simple, fast and user-friendly alternative to 'find'".
#+begin_src scheme
(simple-service
'home-fd
home-profile-service-type
(list
(specification->package
"fd")))
#+end_src

*** Direnv
[[https://direnv.net/][direnv]] is the environment switcher on the shell level, based on current directories.
#+begin_src scheme
(simple-service
'home-direnv
home-profile-service-type
(list
(specification->package
"direnv")))
#+end_src

** Aliases
And the aliases that I'm using:
#+begin_src sh :tangle "build/.aliases"
alias v="nvim"
alias e="emacsclient -c --no-wait"
alias g="git"
alias ls="exa"
alias l="exa --git-ignore"
alias l.="ls -lah"
alias gc="git commit -v"
#+end_src

* Fonts
:PROPERTIES:
:header-args:scheme: :tangle "build/hiecaq/home/fonts.scm"
:END:

This file describe how fonts are configured.
#+begin_src scheme
(define-module (hiecaq home fonts)
#:use-module (gnu services)
#:use-module (gnu home services)
#:use-module (gnu packages fonts)
#:use-module (gnu packages fontutils)
#:use-module (guix gexp)
#:use-module ((gnu home services fontutils) #:prefix fontutils:))
#+end_src

The ~home-fontconfig-service-type~ from vanilla =guix= comes with a =fonts.conf= that is literately inconfigurable, so we have to overwrite it.
SIDE NOTES: I cannot use ~@@~ to import ~regenerate-font-cache-gexp~ from =(gnu home services fontutils)= I have totally no idea why.
#+begin_src scheme
(define (add-fontconfig-config-file he-symlink-path)
`(("fontconfig/fonts.conf"
,(local-file "../../fonts.conf"))))

(define (regenerate-font-cache-gexp _)
`(("profile/share/fonts"
,#~(system* #$(file-append fontconfig "/bin/fc-cache") "-fv"))))

(define home-fontconfig-service-type
(service-type (name 'home-fontconfig)
(extensions
(list (service-extension
home-xdg-configuration-files-service-type
add-fontconfig-config-file)
(service-extension
home-run-on-change-service-type
regenerate-font-cache-gexp)
(service-extension
home-profile-service-type
(const (list fontconfig)))))
(default-value #f)
(description
"Provides configuration file for fontconfig and make
fc-* utilities aware of font packages installed in Guix Home's profile.")))

(define-public (modify-essential-service services)
`(,@(modify-services
services
(delete fontutils:home-fontconfig-service-type))
,(service home-fontconfig-service-type)))
#+end_src

Here is the modified =fonts.conf=:
#+begin_src nxml :tangle "build/fonts.conf" :comments no



~/.guix-home/profile/share/fonts

serif

Noto Serif
Noto Serif CJK SC
Noto Serif CJK JP
Noto Serif CJK TC



sans-serif

Noto Sans
Noto Sans CJK SC
Noto Sans CJK JP
Noto Sans CJK TC



monospace

Noto Sans Mono
Noto Sans Mono CJK SC
Noto Sans Mono CJK JP
Noto Sans Mono CJK TC



emoji

Noto Color Emoji



#+end_src

this module simply provides a single service that install the fonts needed.
#+begin_src scheme
(define-public services
(list (simple-service
'extend-environment-variables
home-profile-service-type
(list
font-hack
font-google-noto
font-google-noto-sans-cjk))))
#+end_src

#+begin_src scheme :tangle no :noweb-ref home-module
((hiecaq home fonts) #:prefix fonts:)
#+end_src

#+begin_src scheme :tangle no :noweb-ref home-environment-service
fonts:services
#+end_src

* Search
#+begin_src scheme :tangle "build/hiecaq/services/search.scm" :noweb yes
(define-module (hiecaq services search)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (gnu services)
#:use-module (gnu services configuration)
#:use-module (gnu packages search)
#:use-module (gnu system shadow) ;; account-service-type
#:use-module (ice-9 match)
#:use-module (ice-9 string-fun)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:export (locate-configuration
locate-configuration?
locate-configuration-locate
locate-configuration-fields
locate-service-type))

(define (uglify-field-name field-name)
(let* ((str (symbol->string field-name))
(up (string-upcase str)))
(if (string-suffix? "?" up)
(string-replace-substring (string-drop-right up 1) "-" "_")
(string-replace-substring up "-" ""))))

(define (strings? lst)
(every string? lst))

(define (serialize-field field-name value)
#~(string-append #$(uglify-field-name field-name)
" = \""
#$value
"\"\n"))

(define (serialize-strings field-name strs)
(serialize-field field-name (string-join strs " ")))

(define (serialize-boolean field-name value)
(serialize-field field-name (if value "yes" "no")))

(define serialize-group empty-serializer)
(define (group? s) (string? s))

(define-maybe strings)
(define-maybe boolean)
(define-maybe group)

(define-configuration locate-configuration
(locate
(package plocate)
"The locate package to use.")
(group
(group "locate")
"Locate group used to run updatedb.")
(prune-fs
maybe-strings
"List of file system types (as used in /etc/mtab) which should not be scanned.")
(prune-names
maybe-strings
"List of directory names (without paths) which should not be scanned.")
(prune-paths
maybe-strings
"List of directory absolute paths which should not be scanned.")
(prune-bind-mounts?
maybe-boolean
"If true, bind mounts are not scanned."))

(define (locate-etc config)
`(("updatedb.conf" ,(mixed-text-file
"updatedb.conf"
"# Generated by 'locate-service'.\n"
(serialize-configuration
config locate-configuration-fields)))))

(define (locate-group config)
(list
(user-group
(name (locate-configuration-group config))
(system? #t))))

(define locate-service-type
(service-type
(name 'locate)
(extensions
(list (service-extension profile-service-type (compose list locate-configuration-locate))
(service-extension etc-service-type locate-etc)
(service-extension account-service-type locate-group)))
(default-value (locate-configuration))
(description #f)))
#+end_src

** System service
This is translated from [[https://gitlab.archlinux.org/archlinux/packaging/packages/plocate/-/blob/main/updatedb.conf][Arch's configuration]]
#+begin_src scheme :noweb-ref system-services
(service locate-service-type
(locate-configuration
(group "plocate")
(prune-fs '("9p" "afs" "anon_inodefs"
"auto" "autofs" "bdev" "binfmt_misc" "cgroup" "cifs" "coda" "configfs"
"cpuset" "cramfs" "debugfs" "devpts" "devtmpfs" "ecryptfs" "exofs"
"ftpfs" "fuse" "fuse.encfs" "fuse.s3fs" "fuse.sshfs" "fusectl" "gfs"
"gfs2" "hugetlbfs" "inotifyfs" "iso9660" "jffs2" "lustre" "mqueue"
"ncpfs" "nfs" "nfs4" "nfsd" "pipefs" "proc" "ramfs" "rootfs"
"rpc_pipefs" "securityfs" "selinuxfs" "sfs" "shfs" "smbfs" "sockfs"
"sshfs" "sysfs" "tmpfs" "ubifs" "udf" "usbfs" "vboxsf"))
(prune-names '(".git" ".hg" ".svn" ".cache"))
(prune-paths '("/afs" "/media" "/mnt"
"/net" "/sfs" "/tmp" "/udev" "/gnu/store"
"/var/cache" "/var/lock" "/var/run" "/var/tmp"))))
#+end_src
* Desktop Environment
My "desktop environment" is plain window management with friends.

#+begin_src scheme :tangle "build/hiecaq/home/de.scm" :noweb yes
(define-module (hiecaq home de)
#:use-module (guix gexp)
#:use-module (gnu services)
#:use-module (gnu home services)
<>)

(define-public services
(list
<>))
#+end_src

#+begin_src scheme :tangle no :noweb-ref home-module
((hiecaq home de) #:prefix de:)
#+end_src

#+begin_src scheme :tangle no :noweb-ref home-environment-service
de:services
#+end_src

** Display
I 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).

So, simply
#+begin_src sh :tangle "build/xsession" :shebang #!/usr/bin/env bash
exec xmonad
#+end_src

#+begin_src scheme :noweb-ref de-use-module
#:use-module (gnu packages wm)
#+end_src

#+begin_src scheme :noweb-ref de-service
(service
(service-type
(name 'home-wm)
(extensions
(list
(service-extension
home-profile-service-type
(const (list
xmonad
ghc-xmonad-contrib
xmobar)))
(service-extension
home-files-service-type
;; recursive to keep x bits, see https://lists.gnu.org/archive/html/help-guix/2023-03/msg00190.html
(const `((".xsession" ,(local-file "../../xsession" #:recursive? #t)))))))
(default-value #f)
(description #f)))
#+end_src

** D-Bus
Start a session-specific D-Bus for unprivileged apps:
#+begin_src scheme :noweb-ref de-use-module
#:use-module (gnu home services desktop)
#+end_src

#+begin_src scheme :noweb-ref de-service
(service home-dbus-service-type)
#+end_src

** XDG Desktop Portal
[[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.

There are several daemons involved, and all of them will be automatically started the first time related D-bus events happen:
- =xdg-desktop-portal=, the daemon [[https://flatpak.github.io/xdg-desktop-portal/docs/terminology.html#][provides the API that application interacts with]].
- =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=
- =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]].
- =xdg-desktop-portal-gtk=, the daemon that is the back-end that actually handles the translated and standardized requests.
BTW, if the portal does not work immediately after reconfigure, try reboot the system.

#+begin_src scheme :noweb-ref de-use-module
#:use-module (gnu packages freedesktop)
#+end_src

#+begin_src scheme :noweb-ref de-service
(service
(service-type
(name 'home-xdg-desktop-portal)
(extensions
(list
(service-extension
home-profile-service-type
(const (list xdg-desktop-portal
xdg-desktop-portal-gtk)))
(service-extension
home-xdg-configuration-files-service-type
(const `(("xdg-desktop-portal/portals.conf" ,(local-file "../../portals.conf")))))))
(default-value #f)
(description #f)))
#+end_src

The following file set using =xdg-desktop-portal-gtk= as the default backend. There can actually be multiple backends running at the same time.
#+begin_src conf :tangle "build/portals.conf"
[preferred]
default=gtk
#+end_src

** Audio and Sound
I use a user [[https://gitlab.freedesktop.org/pipewire/pipewire][pipewire]] session.
#+begin_src scheme :noweb-ref de-use-module
#:use-module (gnu home services sound)
#+end_src

#+begin_src scheme :noweb-ref de-service
(service home-pipewire-service-type)
#+end_src

* Emacs
:PROPERTIES:
:header-args:emacs-lisp: :lexical t :tangle "build/init.el"
:header-args:lisp-data: :tangle "build/templates.eld"
:header-args:scheme: :noweb-ref emacs-service
:END:
TODO: I'm still not sure if I should put some config as big as Emacs' in this file.

Implement a =home-emacs-service-type= that
- The service itself defines the Emacs version to use and the "Emacs compiler" to use, via =home-emacs-configuration=
- The service's extension add Emacs packages to use, configuration file to link, etc, via =home-emacs-extension=.
The reason for this set-up is
- I can easily swap between different Emacs versions, and packages will be automatically transformed to using that version's byte-codes.
- Configurations are discrete by using extensions, so they fit this literature configuration set-up better.
#+begin_src scheme :tangle "build/hiecaq/home/services/emacs.scm" :noweb-ref nil
(define-module (hiecaq home services emacs)
#:use-module (gnu services)
#:use-module (gnu services configuration)
#:use-module (gnu home services)
#:use-module ((gnu packages emacs) #:prefix upstream:)
#:use-module (guix packages)
#:use-module (srfi srfi-1)
#:export (home-emacs-configuration
home-emacs-extension
home-emacs-service-type))

(define-configuration/no-serialization home-emacs-configuration
(emacs
(package upstream:emacs)
"Emacs to use.")
(emacs-compiler
(package upstream:emacs-minimal)
"Emacs used for compiling packages.")
(packages
(list '())
"List of Emacs packages to use.")
(configs
(alist '())
"Emacs configuration files."))

(define (home-emacs-transformed-package config)
(package-input-rewriting
`((,upstream:emacs-minimal
. ,(home-emacs-configuration-emacs-compiler config))
(,upstream:emacs-no-x
. ,(home-emacs-configuration-emacs config))
(,upstream:emacs
. ,(home-emacs-configuration-emacs config)))))

(define (home-emacs-profile config)
`(,(home-emacs-configuration-emacs config)
,@(map (home-emacs-transformed-package config)
(home-emacs-configuration-packages config))))

(define-configuration/no-serialization home-emacs-extension
(packages
(list '())
"Extra list of Emacs packages to use.")
(configs
(alist '())
"Extra Emacs configuration files."))

(define (home-emacs-extensions original-config extension-configs)
(let ((append-fields
(lambda (config-getter extension-getter)
(append (config-getter original-config)
(append-map extension-getter extension-configs)))))
(home-emacs-configuration
(inherit original-config)
(packages (append-fields home-emacs-configuration-packages
home-emacs-extension-packages))
(configs (append-fields home-emacs-configuration-configs
home-emacs-extension-configs)))))

(define home-emacs-service-type
(service-type
(name 'home-emacs)
(extensions
(list (service-extension home-xdg-configuration-files-service-type
home-emacs-configuration-configs)
(service-extension home-profile-service-type
home-emacs-profile)
(service-extension home-environment-variables-service-type
(const '(("EDITOR" . "emacsclient -a nvim -c")
("VISUAL" . "emacsclient -a nvim -c"))))))
(compose identity)
(extend home-emacs-extensions)
(default-value (home-emacs-configuration))
(description #f)))
#+end_src

#+begin_src scheme :tangle "build/hiecaq/home/emacs.scm" :noweb yes :noweb-ref nil
(define-module (hiecaq home emacs)
#:use-module (gnu services)
#:use-module (gnu packages)
#:use-module ((gnu packages emacs) #:prefix upstream:)
#:use-module (gnu home services)
#:use-module (gnu home services shells)
#:use-module (hiecaq home services emacs)
#:use-module (guix gexp))

(define-public services
(list
<>))
#+end_src

Add this module and its services:
#+begin_src scheme :noweb-ref home-module
((hiecaq home emacs) #:prefix emacs:)
#+end_src

#+begin_src scheme :noweb-ref home-environment-service
emacs:services
#+end_src

** Basics
I'm currently using =emacs= from Guix official channel.
#+begin_src scheme
(service home-emacs-service-type
(home-emacs-configuration
(emacs upstream:emacs-next)
(emacs-compiler upstream:emacs-next-minimal)))
#+end_src

My Guix packages definition is at =(hiecaq packages emacs-xyz)=. TODO: makes a channel!
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-module (hiecaq packages emacs-xyz)
#:use-module (guix utils)
#:use-module (guix gexp)
#:use-module (guix packages)
#:use-module (guix git-download)
#:use-module (guix build utils)
#:use-module (guix build-system emacs)
#:use-module (gnu packages)
#:use-module ((gnu packages textutils) #:prefix upstream:) ;; for vale
#:use-module ((gnu packages emacs) #:prefix upstream:)
#:use-module ((gnu packages emacs-xyz) #:prefix upstream:)
#:use-module ((guix licenses) #:prefix license:))
#+end_src

NOTE: the hash for git-based packages is got by following [[https://guix.gnu.org/cookbook/en/html_node/Extended-example.html][Guix Cookbook instructions]].

** Early Initialization
:PROPERTIES:
:header-args:emacs-lisp: :lexical t :tangle "build/early-init.el"
:END:

#+begin_src scheme
(simple-service
'home-emacs-early-init
home-emacs-service-type
(home-emacs-extension
(configs `(("emacs/early-init.el" ,(local-file "../../early-init.el"))))))
#+end_src

#+begin_src emacs-lisp :comments no
;;; early-init.el --- Configurations before package systems and UI systems -*- lexical-binding: t; buffer-read-only: t; eval: (auto-revert-mode 1) -*-
#+end_src

*** Packages
I don't use the built-in =package.el= to fetch packages, so I'll turn it off:
#+begin_src emacs-lisp
(setq package-enable-at-startup nil)
#+end_src

*** Special Key Remapping
grabbed 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]]
#+begin_src emacs-lisp
(add-hook
'after-make-frame-functions
(defun setup-blah-keys (frame)
(with-selected-frame frame
(when (display-graphic-p)
(define-key input-decode-map (kbd "C-i") [CTRL-i])
(define-key input-decode-map (kbd "C-[") [CTRL-lsb]) ; left square bracket
(define-key input-decode-map (kbd "C-m") [CTRL-m])))))
#+end_src

*** Fonts
#+begin_src emacs-lisp
(defun my-faces-initialize (frame)
"Initialize faces when the first graphic display is available."
(with-selected-frame frame
(when (display-graphic-p)
(let* ((families (font-family-list))
(family-p (lambda (family) (member family families)))
(first-family-installed
(lambda (family-list)
(seq-find family-p family-list))))

(when-let* ((family
(funcall first-family-installed '("Hack Nerd Font Mono"
"Hack"
"Noto Sans Mono"))))
(set-face-attribute 'default nil :family family))

(when-let* ((family
(funcall first-family-installed '("Noto Sans"
"Sans Serif"))))
(set-face-attribute 'variable-pitch nil :family family))

(when (eq system-type 'gnu/linux)
(set-face-attribute 'default nil :height 140))

(when-let* ((family
(funcall first-family-installed '("Noto Sans CJK SC"))))
(set-fontset-font t 'han (font-spec :family "Noto Sans CJK SC")))

(when-let* ((family
(funcall first-family-installed '("Noto Sans CJK JP"))))
(set-fontset-font t 'kana (font-spec :family "Noto Sans CJK JP")))

(when-let* ((family
(funcall first-family-installed '("Noto Sans CJK SC"))))
(set-fontset-font t 'cjk-misc (font-spec :family "Noto Sans CJK SC"))))

(set-face-attribute 'variable-pitch nil :weight 'normal :inherit 'default)
(set-face-attribute 'fixed-pitch nil :family (internal-get-lisp-face-attribute 'default :family))

(remove-hook 'after-make-frame-functions #'my-faces-initialize))))

(add-hook 'after-make-frame-functions #'my-faces-initialize)
#+end_src

*** Some Configurations that might make sense to put here
~load~ prefers the newest version of a file (when suffix is not given).
#+begin_src emacs-lisp
(setq load-prefer-newer t)
#+end_src

#+begin_src emacs-lisp
(setq load-no-native t)
#+end_src

** Main Configurations
#+begin_src scheme
(simple-service
'home-emacs-init
home-emacs-service-type
(home-emacs-extension
(configs `(("emacs/init.el" ,(local-file "../../init.el"))))))
#+end_src

Init file header:
#+begin_src emacs-lisp :comments no
;;; init.el --- Main Configurations -*- lexical-binding: t; buffer-read-only: t; eval: (auto-revert-mode 1) -*-
#+end_src

Use Utf-8 as the default coding system.
#+begin_src emacs-lisp
(set-language-environment "UTF-8")
(prefer-coding-system 'utf-8-unix)
#+end_src

*** Packages
On Android and Windows, I currently just use =package.el=, because Guix doesn't support them (yet?).
#+begin_src emacs-lisp :noweb yes
(setopt package-archives
(if (eq system-type 'gnu/linux)
nil
'(("gnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
("nongnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/")
("melpa" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/"))))

(defvar package-selected-packages '<>)

(when package-archives
(package-initialize))
#+end_src

On 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.
#+name: current-selected-packages
#+begin_src elisp :exports none
package-selected-packages
#+end_src

*** setup.el
[[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]].
#+begin_src scheme
(simple-service
'home-emacs-setup
home-emacs-service-type
(home-emacs-extension
(packages
(list (specification->package
"emacs-setup")))))
#+end_src

See 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.

TODO: I should split this out later.
#+begin_src emacs-lisp
(require 'setup)
(require 'cl-macs)

(defmacro defsetup (name signature &rest body)
"Shorthand for `setup-define'.
NAME is the name of the local macro. SIGNATURE is used as the
argument list for FN. If BODY starts with a string, use this as
the value for :documentation. Any following keywords are passed
as OPTS to `setup-define'."
(declare (debug defun))
(let (opts)
(when (stringp (car body))
(setq opts (nconc (list :documentation (pop body))
opts)))
(while (keywordp (car body))
(let* ((prop (pop body))
(val `',(pop body)))
(setq opts (nconc (list prop val) opts))))
`(setup-define ,name
(cl-function (lambda ,signature ,@body))
,@opts)))

(put #'defsetup 'lisp-indent-function 'defun)
;; use Emacs 29's new `setopt'
(setup-define :option
(setup-make-setter
(lambda (name)
`(funcall (or (get ',name 'custom-get)
#'symbol-value)
',name))
(lambda (name val)
`(setopt ,name ,val)))

:documentation "Set the option NAME to VAL.
NAME may be a symbol, or a cons-cell. If NAME is a cons-cell, it
will use the car value to modify the behaviour. These forms are
supported:

(append VAR) Assuming VAR designates a list, add VAL as its last
element, unless it is already member of the list.

(prepend VAR) Assuming VAR designates a list, add VAL to the
beginning, unless it is already member of the
list.

(remove VAR) Assuming VAR designates a list, remove all instances
of VAL.

Note that if the value of an option is modified partially by
append, prepend, remove, one should ensure that the default value
has been loaded. Also keep in mind that user options customized
with this macro are not added to the \"user\" theme, and will
therefore not be stored in `custom-set-variables' blocks."
:debug '(sexp form)
:repeatable t)

(defsetup :global (&rest body)
"Use the global keymap for the BODY. This is intended to be used with ':bind'."
:debug '(sexp)
(let (bodies)
(push (setup-bind body (map 'global-map))
bodies)
(macroexp-progn (nreverse bodies))))

(defsetup :with-state (state &rest body)
"Change the evil STATE that BODY will bind to. If STATE is a list, apply BODY
to all elements of STATE. This is intended to be used with ':bind'."
:indent 1
:debug '(sexp setup)
(let (bodies)
(dolist (state (ensure-list state))
(push (setup-bind body (state state))
bodies))
(macroexp-progn (nreverse bodies))))

(defsetup :bind (key command)
"Bind KEY to COMMAND in current map, and optionally for current evil states."
:after-loaded t
:debug '(form sexp)
:repeatable t
(let* ((map (setup-get 'map))
(global (or (not map) (eq map 'global) (eq map 'global-map)))
(state (ignore-errors (setup-get 'state))))
(cond
((and state global)
`(with-eval-after-load 'evil
(evil-define-key* ',state 'global ,(kbd key) ,command)))
(state
`(with-eval-after-load 'evil
(evil-define-key* ',state ,map ,(kbd key) ,command)))
(global `(keymap-global-set ,key ,command))
(t `(keymap-set ,map ,key ,command)))))

(defsetup :unbind (key)
"Unbind KEY in current map, and optionally for current evil states."
:after-loaded t
:debug '(form)
:repeatable t
(let* ((map (setup-get 'map))
(global (or (not map) (eq map 'global) (eq map 'global-map)))
(state (ignore-errors (setup-get 'state))))
(cond
((and state global)
`(with-eval-after-load 'evil
(evil-define-key* ',state 'global ,(kbd key) nil)))
(state
`(with-eval-after-load 'evil
(evil-define-key* ',state ,map ,(kbd key) nil)))
(global `(keymap-global-unset ,key :remove))
(t `(keymap-unset ,map ,key :remove)))))

(defsetup :rebind (old-command new-command)
"Bind NEW-COMMAND to OLD-COMMAND in current map,
and optionally for current evil states."
:after-loaded t
:debug '(form sexp)
:repeatable t
:ensure (func func)
(let ((old-command-string
(cadr (delete "#'" (split-string (format "%s" old-command) "#'")))))
`(:bind ,(format " <%s>" old-command-string) ,new-command)))

(defsetup :needs (executable)
"If EXECUTABLE is not in the path, stop here."
:debug '(form)
`(unless (executable-find ,executable)
,(setup-quit)))

(defsetup :enable ()
"Enable the current mode."
:debug '(form)
`(,(setup-get 'mode) 1))

(defsetup :system (type)
"If system-type is not TYPE, stop here."
:debug '(form)
:repeatable t
`(unless (eq system-type ,type)
,(setup-quit)))

(defsetup :package (package)
"Push PACKAGE into package-selected-packages."
:shorthand cadr
`(add-to-list 'package-selected-packages ',package))

(setup (:package setup))
#+end_src

*** Some Sane Configurations
#+begin_src emacs-lisp
(setup simple
(:option indent-tabs-mode nil))

(setup frame
(:option blink-cursor-mode nil))

(setup scroll-bar
(:option scroll-bar-mode nil))

(setup tool-bar
(:option tool-bar-mode nil))

(setup menu-bar
(:option menu-bar-mode nil))
#+end_src

Turn 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.
#+begin_src emacs-lisp
(setup emacs
(:option create-lockfiles nil))
#+end_src

4-space indentation:
#+begin_src emacs-lisp
(setup simple
(:option tab-width 4))
#+end_src

General programming set up:
#+begin_src emacs-lisp
(setup prog-mode
(:hook #'display-line-numbers-mode)
(:local-set truncate-lines t))
#+end_src

When 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.
#+begin_src emacs-lisp
(setup files
(:option make-backup-files nil)
(:option backup-by-copying t))
#+end_src

Always use =y-or-p= over =yes-or-no=, and use ~read-key~ instead of ~read-from-minibuffer~. The latter is helpful when using Embark.
#+begin_src emacs-lisp
(setup emacs
(:option use-short-answers t
y-or-n-p-use-read-key t))
#+end_src

I don't want Emacs to auto-recenter when scrolling off-the-screen:
#+begin_src emacs-lisp
(setup emacs
(:option scroll-conservatively 108))
#+end_src

Emacs 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.
#+begin_src emacs-lisp
(setup cus-edit
(:option custom-file null-device)
(defun my-custom-file-set ()
(:option custom-file
(make-temp-file "emacs-custom-" nil ".el"
";; auto-generated by custom-file\n")))
(:with-function my-custom-file-set
(:hook-into after-init)))
#+end_src

#+end_src

Allow 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.
#+begin_src emacs-lisp
(setup emacs
(:option word-wrap-by-category t))
#+end_src

Also, 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.
#+begin_src emacs-lisp
(setup uniquify
(:option uniquify-after-kill-buffer-p nil))
#+end_src

I 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.
#+begin_src emacs-lisp
(setup emacs
(:option bidi-paragraph-direction 'left-to-right))
#+end_src

Time out on remote file access (in seconds), for example if they are offline, so that Emacs is not blocked forever.
#+begin_src emacs-lisp
(setup files
(:option remote-file-name-access-timeout 3))
#+end_src

**** Android
#+begin_src emacs-lisp
(setup touch-screen
(:system 'android)
(:option overriding-text-conversion-style nil)
(:option touch-screen-display-keyboard t))
#+end_src

*** Window Management
#+begin_src emacs-lisp
(setup window
(:option switch-to-buffer-obey-display-actions t
switch-to-buffer-in-dedicated-window 'pop
;; left, top, right, bottom
window-sides-slots '(0 0 1 1))
(defun fit-window-to-buffer-horiz (window)
"Fit window to buffer horizontally. Suitable for `window-width'."
(let ((fit-window-to-buffer-horizontally 'only))
(fit-window-to-buffer window))))
#+end_src

#+begin_src emacs-lisp
(defun my-window-shot (&optional window)
"Take screenshot of a given Emacs window."
(interactive)
(pcase-let ((`(,window-left ,window-top ,window-right ,window-bottom)
(window-edges (window-normalize-window window t) nil t t)))
(let* ((geo (format "%dx%d+%d+%d"
(- window-right window-left)
(- window-bottom window-top)
window-left
window-top))
(file (expand-file-name (format "%f.jpg" (time-to-seconds (time-since 0)))
(xdg-user-dir "PICTURES"))))
(make-process :name "window-shot"
:command `("maim"
"-m" "10"
"--geometry" ,geo
,file)))))
#+end_src

#+begin_src emacs-lisp
(defvar my-window-record--process nil "Running record process")
(defun my-window-record (&optional window sec)
"Take screen record of a given Emacs window."
(interactive)
(if (process-live-p my-window-record--process)
(process-send-string my-window-record--process "q")
(pcase-let ((`(,window-left ,window-top ,window-right ,window-bottom)
(window-edges (window-normalize-window window t) nil t t)))
(let* ((size (format "%dx%d"
(- window-right window-left)
(- window-bottom window-top)))
(geo (format ":0.0+%d,%d"
window-left
window-top))
(file (expand-file-name (format "%f.mp4" (time-to-seconds (time-since 0)))
(xdg-user-dir "PICTURES")))
(proc (make-process :name "window-record"
:buffer "*window-record*"
:connection-type 'pty
:command `("ffmpeg"
"-video_size" ,size
"-framerate" "8"
"-f" "x11grab"
"-i" ,geo
,file))))
(setq my-window-record--process proc)
(unless (null sec)
(run-with-timer sec nil
#'process-send-string proc "q"))))))
#+end_src

*** Universal Argument
I 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.

=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.
#+begin_src emacs-lisp
(defvar my-dvp-digit-row-alist
'((7 . "[")
(5 . "{")
(3 . "}")
(1 . "(")
(9 . "=")
(0 . "*")
(2 . ")")
(4 . "+")
(6 . "]")
(8 . "!"))
"`Higher' case characters to digits mapping on dvorak digit row")

(setup simple
(defun my-digit-argument (digit)
"Return the command that inputs the given
digit as universal argument."
(lambda (arg)
(interactive "P")
(let ((last-command-event (+ digit ?0)))
(digit-argument arg))))
(:with-map universal-argument-map
(dolist (d (number-sequence 0 9))
(:bind (alist-get d my-dvp-digit-row-alist)
(my-digit-argument d)))
(:bind "" (lambda (arg)
(interactive "P")
(insert (format "%s" arg))))))
#+end_src

Also here is a helper macro for binding commands. I personally do not like using universal argument at all.
#+begin_src emacs-lisp
(defmacro my-with-universal-argument (cmd)
"Wrap the given CMD with a lambda that set universal argument before
interactively calling CMD."
`(lambda ()
(interactive)
(let ((current-prefix-arg '(4)))
(call-interactively ,cmd))))
#+end_src

*** PCRE
Emacs 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.
#+begin_src scheme
(simple-service
'home-emacs-pcre
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-pcre2el")))))
#+end_src
*** Help
TODO: this should not require help.
#+begin_src emacs-lisp
(setup (:require help)
(:global (:unbind "C-h C-h")))
#+end_src

*** Info
Info is a built-in Texinfo browser.
#+begin_src emacs-lisp
(setup Info
(:local-set truncate-lines t))
#+end_src

*** Xdg
#+begin_src emacs-lisp
(setup (:require xdg))
#+end_src

*** No Littering
[[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.
#+begin_src scheme
(simple-service
'home-emacs-no-littering
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-no-littering")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package no-littering)
(:require no-littering))
#+end_src

#+begin_src emacs-lisp
(defmacro def-exdg-home-dir (xdg-name)
(list 'progn
`(defvar ,(intern (format "exdg-%s-dir" xdg-name))
(expand-file-name (convert-standard-filename "emacs/") (,(intern (format "xdg-%s-home" xdg-name)))))
`(defun ,(intern (format "exdg-%s" xdg-name)) (file)
(expand-file-name (convert-standard-filename file) ,(intern (format "exdg-%s-dir" xdg-name))))))

(def-exdg-home-dir config)
(def-exdg-home-dir cache)
(def-exdg-home-dir data)
(def-exdg-home-dir state)

(setq exdg-config-dir (expand-file-name "config/" user-emacs-directory))
#+end_src

*** Modus Themes
#+begin_src scheme
(simple-service
'home-emacs-modus-themes
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-modus-themes")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package modus-themes)
(:option modus-themes-mixed-fonts t)
(:require modus-themes)
(load-theme 'modus-vivendi :no-confirm))
#+end_src

*** Mode Line
#+begin_src emacs-lisp
(defvar-local my-mode-line-format nil
"My `mode-line-format', for easy toggle between the default version.")

(defun my-toggle-mode-line-format ()
(interactive)
(let* ((standard (eval (car (get 'mode-line-format 'standard-value))))
(new-format (if (eq standard (default-value 'mode-line-format))
my-mode-line-format
standard)))
(setq-default mode-line-format new-format)
(kill-local-variable 'mode-line-format)
(force-mode-line-update)))

(defun my-mode-line-recursion--indicator ()
(when-let (((mode-line-window-selected-p))
(depth (- (recursion-depth) (if (active-minibuffer-window) 1 0)))
((> depth 0)))
(format "R%d" depth)))

(defvar-local my-mode-line-recursion-indicator
'(:eval (my-mode-line-recursion--indicator)))
(put 'my-mode-line-recursion-indicator 'risky-local-variable t)

(defvar-local my-mode-line-indicators (list my-mode-line-recursion-indicator
'(:eval (when find-file-literally "L "))
'(:eval (when buffer-read-only "RO "))
'(:eval (unless (string-equal (format-mode-line "%@") "-") "Remote "))
'(:eval (when (buffer-narrowed-p) '(:propertize "Narrow " face warning)))
'(:eval (when (window-dedicated-p) "Dedi "))
'(:eval (when (window-parameter (selected-window) 'window-side) "Side "))
'(current-input-method current-input-method-title)
'(god-local-mode "God ")
'(defining-kbd-macro "Def ")
'(flymake-mode flymake-mode-line-format)
'(:eval (when (buffer-modified-p) "M "))
'(:eval (unless (eq evil-state 'normal)
(string-trim evil-mode-line-tag))))
"A list of mode line indicators that is displayed on active window.")

(put 'my-mode-line-indicators 'risky-local-variable t)

(setopt my-mode-line-format '("%e"
mode-line-front-space
nil ;; eshell
(:eval (when (mode-line-window-selected-p)
(list my-mode-line-indicators
mode-line-misc-info)))

mode-line-format-right-align

mode-line-buffer-identification
(vc-mode vc-mode)
" "
mode-name
mode-line-end-spaces))

(setup bindings
(:option mode-line-buffer-identification (propertized-buffer-identification "%b")
mode-line-format my-mode-line-format
mode-line-right-align-edge 'right-fringe))
#+end_src

*** Midnight
=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.

#+begin_src emacs-lisp
(setup midnight
(:option midnight-delay (* 4 60 60))
(:enable))
#+end_src

*** Auto Save
#+begin_src emacs-lisp
(setup files
(let ((autosave-dir (exdg-cache "auto-save/")))
(mkdir autosave-dir t)
(:option auto-save-file-name-transforms
`(("\\`/[^/]*\\([^/]*/\\)*\\([^/]*\\)\\'" ,(concat autosave-dir "\\2") t)))))
#+end_src

*** Recentf
[[info:emacs#File Conveniences][recentf]] is an Emacs built-in minor mode that saves recent file list.
#+begin_src emacs-lisp
(setup recentf
(:option recentf-save-file (exdg-state "recentf-save.el"))
(:enable))
#+end_src

*** Save History
[[help:savehist-mode][savehist]] is an Emacs built-in minor mode that save minibuffer histories to a file.
#+begin_src emacs-lisp
(setup savehist
(:option savehist-file (exdg-state "savehist.el"))
(:enable))
#+end_src

*** COMMENT Save Place
[[https://www.emacswiki.org/emacs/SavePlace][Save Place]] is a Emacs built-in mode that "nave place in files between sessions".
#+begin_src emacs-lisp
(setup saveplace
(:option save-place-forget-unreadable-files nil)
(save-place-mode 1))
#+end_src

*** Editorconfig
[[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.

[[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.
#+begin_src emacs-lisp
(setup editorconfig
(:enable))
#+end_src

*** Envrc
[[https://github.com/purcell/envrc][envrc]] is Emacs' integration with [[https://direnv.net/][direnv]] that works in buffer-local style.

[[https://github.com/purcell/inheritenv][interitenv]].
#+begin_src scheme
(simple-service
'home-emacs-envrc
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-envrc")
(specification->package
"emacs-inheritenv")))))
#+end_src

#+begin_src emacs-lisp
(setup envrc
(:system 'gnu/linux)
(:also-load inheritenv)
(:with-mode envrc-global-mode
(:hook-into after-init)))
#+end_src

*** Subword
[[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.
#+begin_src emacs-lisp
(setup subword
(:hook-into text-mode prog-mode))
#+end_src

*** Highlight Parentheses
[[https://git.sr.ht/~tsdh/highlight-parentheses.el][highlight-parentheses]], well, highlights parentheses surrounding [[info:emacs#Point][point]].
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-public emacs-highlight-parentheses
(let ((version "2.2.2")
(revision "0")
(url "https://git.sr.ht/~tsdh/highlight-parentheses.el"))
(package
(name "emacs-highlight-parentheses")
(version version)
(source
(origin
(method git-fetch)
(uri
(git-reference
(url url)
(commit version)))
(file-name (git-file-name name version))
(sha256
(base32 "0wvhr5gzaxhn9lk36mrw9h4qpdax5kpbhqj44745nvd75g9awpld"))))
(build-system emacs-build-system)
(home-page url)
(synopsis "Highlights parentheses surrounding point in Emacs")
(description "Highlight-parentheses.el dynamically highlights
the parentheses surrounding point based on nesting-level using configurable
lists of colors, background colors, and other properties.")
(license license:gpl3))))
#+end_src

#+begin_src scheme
(simple-service
'home-emacs-highlight-parentheses
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-highlight-parentheses")))))
#+end_src

The 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.
#+begin_src emacs-lisp
(setup (:package highlight-parentheses)
(defvar my-highlight-parentheses-use-background t
"Prefer `highlight-parentheses-background-colors'.")

(setq my-highlight-parentheses-use-background t) ; Set to nil to disable backgrounds

(modus-themes-with-colors
;; Our preference for setting either background or foreground
;; styles, depending on `my-highlight-parentheses-use-background'.
(if my-highlight-parentheses-use-background

;; Here we set color combinations that involve both a background
;; and a foreground value.
(setq highlight-parentheses-background-colors (list bg-cyan-intense
bg-magenta-intense
bg-green-intense
bg-yellow-intense)
highlight-parentheses-colors (list cyan
magenta
green
yellow))

;; And here we pass only foreground colors while disabling any
;; backgrounds.
(setq highlight-parentheses-colors (list green-intense
magenta-intense
blue-intense
red-intense)
highlight-parentheses-background-colors nil)))
(:hook-into prog-mode)
(:with-function highlight-parentheses-minibuffer-setup
(:hook-into minibuffer-setup)))
#+end_src

*** Transient
#+begin_src emacs-lisp
(setup transient
(:option transient-history-file (exdg-state "transient/history.el")
transient-levels-file (exdg-state "transient/levels.el")
transient-values-file (exdg-state "transient/values.el")))
#+end_src

*** Evil
It'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.

References:
- [[https://github.com/noctuid/evil-guide][evil-guide]] by noctuid
#+begin_src scheme
(simple-service
'home-emacs-evil
home-emacs-service-type
(home-emacs-extension
(packages
(map specification->package
(list
"emacs-goto-chg"
"emacs-evil"
"emacs-evil-collection-next"
"emacs-evil-surround"
"emacs-evil-snipe"
"emacs-evil-commentary")))))
#+end_src

[[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.
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-public emacs-annalist-minimal
(package
(inherit upstream:emacs-annalist)
(name "emacs-annalist-minimal")
(native-inputs '())
(arguments (substitute-keyword-arguments
(package-arguments upstream:emacs-annalist)
((#:tests? t) #f)))))
#+end_src

I need some latest contributions to the =evil-collection= repository:
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-public emacs-evil-collection-next
(let ((commit "20c415aaa07c6541753489b166cd58d6771bd1e1")
(last-release-version "0.0.10")
(revision "0"))
(package
(inherit upstream:emacs-evil-collection)
(name "emacs-evil-collection-next")
(version (git-version last-release-version revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/emacs-evil/evil-collection")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32
"17ifxk4lpj1l52b3m2x5sj5ywdnrjyy1hbvfbvg4zwa1kc0l3ds1"))))
(propagated-inputs
(modify-inputs (package-propagated-inputs upstream:emacs-evil-collection)
(replace "emacs-annalist" emacs-annalist-minimal))))))
#+end_src

#+begin_src emacs-lisp
(setup (:package evil)
(:option
evil-want-integration t ;; require by collection
evil-want-keybinding nil ;; require by collection
evil-echo-state nil ;; Don't echo the == etc info in minibuffer.
evil-undo-system 'undo-redo ;; Use Emacs 28 new ~undo-redo~ as the undo-redo system
evil-disable-insert-state-bindings t ;; I don't want to use Vim's insert mode bindings in insert state:
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)
evil-mode-line-format nil
evil-search-module 'evil-search)
(:require evil)
(:enable)
(:global
(:with-state (motion insert)
(:unbind "C-z"))
(:with-state (normal)
(:bind "" #'evil-jump-forward))))

(setup (:package evil-collection)
(:option evil-collection-setup-minibuffer t
evil-collection-key-blacklist '("SPC" "C-SPC" "DEL" "C-z"))
(:require evil-collection)
(evil-collection-init))
#+end_src

I 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".
#+begin_src emacs-lisp
(setup evil
(:global
(:with-state (normal motion operator visual)
(:bind ";" #'evil-avy-goto-char-2))))
#+end_src
And Dired EPA integration's keybindings conflicts with =;=, so I'd like to unbind them:
#+begin_src emacs-lisp
(setup dired
(:with-state (normal)
(:unbind ";")))
#+end_src

**** Noun-Verb Editing
See [[https://www.hiecaq.org/posts/20250409T225608M449.html][my blog post]] for details.
#+begin_src emacs-lisp
(setup evil
(defun my-evil-inner-thing ()
(interactive)
(evil-visual-char)
(set-transient-map evil-inner-text-objects-map))

(defun my-evil-outer-thing ()
(interactive)
(evil-visual-char)
(set-transient-map evil-outer-text-objects-map))

(:global
(:with-map (evil-operator-state-map evil-visual-state-map)
(:unbind "a" "i")
(:bind "." evil-inner-text-objects-map
"," evil-outer-text-objects-map
"a" #'evil-append
"i" #'evil-insert))
(:with-state (normal)
(:bind "." #'my-evil-inner-thing
"," #'my-evil-outer-thing
"#" #'evil-repeat))))
#+end_src

**** Move up and down a list
#+begin_src emacs-lisp
(setup evil
(:global
(:with-state (motion)
(:bind "-" #'backward-up-list
"+" #'down-list))))
#+end_src

**** Evil Surround
[[https://github.com/emacs-evil/evil-surround][evil-surround]] defines operators that change/add/delete delimiters around a text object.
I remap them all to =s= for better consistency.
#+begin_src emacs-lisp
(setup (:package evil-surround)
(:with-state (operator visual)
(:unbind "s" "S" "g S"))
(:with-state (normal operator)
(:bind "s" #'evil-surround-edit
"S" #'evil-Surround-edit))
(:with-state visual
(:bind "s" #'evil-surround-region
"S" #'evil-Surround-region))
(:also-load evil)
(:with-function turn-on-evil-surround-mode
(:hook-into prog-mode text-mode wdired-mode comint-mode eshell-mode minibuffer-setup))
(define-advice wdired-change-to-dired-mode (:after (&rest _) turn-off-evil-surround)
"`wdired-mode-hook' is only called when activated, so we have to do this manually when deactivating."
(turn-off-evil-surround-mode)))
#+end_src

**** Evil Replace With Register
[[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.
#+begin_src emacs-lisp
(evil-define-operator my-evil-replace-with-register (count beg end type register)
"Replacing an existing text with the contents of a register"
:move-point nil
(interactive "")
(setq count (or count 1))
(let ((saved (evil-get-register ?\")))
(if (eq type 'block)
(evil-visual-paste count register)
(delete-region beg end)
(evil-paste-before count register))
(evil-set-register ?\" saved)))

(setup evil
(:global (:with-state (normal visual)
(:bind "g s" #'my-evil-replace-with-register))))
#+end_src

**** Evil Snipe
[[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.

There 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.
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-public emacs-evil-snipe
(let ((commit "3ad53b8da0dd23093a3f2f0e5c13ecdb08ba8efa")
(last-release-version "2.0.8") ;; from the el file version header
(revision "0")
(url "https://github.com/hiecaq/evil-snipe"))
(package
(name "emacs-evil-snipe")
(version (git-version last-release-version revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url url)
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32
"0fk9nl0h1j1ig6pvb4aix3injxi2jyw9djixchxf4aky11znivgj"))))
(propagated-inputs
(list upstream:emacs-evil))
(build-system emacs-build-system)
(home-page url)
(synopsis "2-char searching ala vim-sneak & vim-seek, for evil-mode")
(description "This library It provides 2-character motions for quickly
(and more accurately) jumping around text, compared to evil's built-in
f/F/t/T motions, incrementally highlighting candidate targets as you type.")
(license license:expat))))
#+end_src

#+begin_src emacs-lisp
(setup (:package evil-snipe)
(:require evil-snipe)
(:with-function turn-off-evil-snipe-override-mode (:hook-into magit-mode))
(:option evil-snipe-repeat-scope 'whole-line)
(:with-mode evil-snipe-override-mode
(:enable)))
#+end_src

**** Evil Commentary
[[https://github.com/linktohack/evil-commentary][evil-commentary]] defines operators for commenting.
#+begin_src emacs-lisp
(setup (:package evil-commentary)
(:also-load evil)
(:enable))
#+end_src

**** Window map
Add my helper commands to the ~evil-window-map~
#+begin_src emacs-lisp
(setup evil
(:with-map evil-window-map
(:bind "M-s" #'my-window-shot
"M-r" #'my-window-record)))
#+end_src

*** God mode
[[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.
#+begin_src scheme
(simple-service
'home-emacs-god-mode
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-god-mode")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package god-mode)
(:require god-mode)
(:option god-mode-alist '((nil . "C-") ("m" . "M-") ("M" . "C-M-"))
god-mode-enable-function-key-translation t)
(:global
(:with-state (normal visual motion)
(:bind "SPC" #'god-execute-with-current-bindings))
(:with-state (insert emacs motion)
(:bind "C-" #'god-execute-with-current-bindings)))
(defun my-god-mode-lookup-key-sequence (&optional key key-string-so-far)
"Retry with literal KEY when the non-literal attempt failed."
(interactive)
(let ((sanitized-key
(god-mode-sanitized-key-string
(or key (read-event key-string-so-far)))))
(condition-case nil
(god-mode-lookup-command
(god-key-string-after-consuming-key sanitized-key key-string-so-far))
(error (when key-string-so-far
(setq god-literal-sequence t)
(god-mode-lookup-command
(god-key-string-after-consuming-key sanitized-key key-string-so-far)))))))

(advice-add #'god-mode-lookup-key-sequence :override #'my-god-mode-lookup-key-sequence))
#+end_src

*** Which key
[[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.

I 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:
- 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
- ~embark-prefix-help-command~ cannot shows the correct keymap after the first key is given
#+begin_src emacs-lisp
(setup which-key
(:option which-key-show-transient-maps nil))
#+end_src

#+begin_src emacs-lisp
(setup (:require which-key)
(:option which-key-use-C-h-commands nil)
(which-key-enable-god-mode-support)
(:enable))
#+end_src

As a side note, which-key default configuration requires there to be at least 1 slot at the bottom in ~window-sides-slots~.
*** Posframe
[[https://github.com/tumashu/posframe][posframe]] pops a child-frame at point, connected to its root window's buffer.
#+begin_src scheme
(simple-service
'home-emacs-posframe
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-posframe")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package posframe))
#+end_src

*** Eldoc
#+begin_src emacs-lisp
(setup eldoc
(:option eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly
(prepend display-buffer-alist) `(,(rx "*eldoc*")
(display-buffer-reuse-mode-window display-buffer-in-direction)
(direction . right)
(window-width . fit-window-to-buffer-horiz)
(body-function . select-window)
(dedicated . t)
(window-parameters . ((mode-line-format . none))))))
#+end_src

[[https://github.com/casouri/eldoc-box][eldoc-box]] shows eldoc in a separate childframe instead of the crowded echo area.
#+begin_src scheme
(simple-service
'home-emacs-eldoc-box
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-eldoc-box")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package eldoc-box)
(:option eldoc-box-clear-with-C-g t
eldoc-box-doc-separator
(concat "\n"
(propertize " " 'face 'completions-group-separator
'display '(space :align-to right)))
eldoc-box-max-pixel-width 1600
eldoc-box-max-pixel-height 1400)
(:with-function eldoc-box-hover-mode
(:hook-into text-mode prog-mode))

(defun my-eldoc-box-quit-frame-when-interactive (interactive)
"When manually open the doc buffer, close eldoc-box immediately."
(when interactive
(eldoc-box-quit-frame)))
(advice-add #'eldoc-doc-buffer :before #'my-eldoc-box-quit-frame-when-interactive))
#+end_src

*** Ace Window
[[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.

Its 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.
#+begin_src scheme :tangle "build/hiecaq/packages/emacs-xyz.scm" :noweb-ref nil
(define-public emacs-ace-window-next
(let ((commit "77115afc1b0b9f633084cf7479c767988106c196")
(last-release-version "0.10.0")
(revision "0"))
(package
(inherit upstream:emacs-ace-window)
(name "emacs-ace-window-next")
(version (git-version last-release-version revision commit))
(source
(origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/abo-abo/ace-window")
(commit commit)))
(file-name (git-file-name name version))
(sha256
(base32
"1l6rp92q4crahx9nq7s6zxqyw7ccrhkl95v70vxra7zndqpqwsbq")))))))
#+end_src

#+begin_src scheme
(simple-service
'home-emacs-ace-window
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-ace-window-next")))))
#+end_src

#+begin_src emacs-lisp
(setup (:package ace-window)
(:also-load ace-window-posframe)
(:option aw-keys '(?u ?h ?e ?t ?i ?d ?o ?n ?a ?s)
aw-translate-char-function (lambda (c)
(pcase c
(?\[ ?7)
(?\{ ?5)
(?\} ?3)
(?\( ?1)
(?= ?9)
(?* ?0)
(?\) ?2)
(?+ ?4)
(?\] ?6)
(?! ?8)
(_ c)))
aw-dispatch-alist '((?Q aw-delete-window "Delete Window")
(?W aw-swap-window "Swap Windows")
(?M aw-move-window "Move Window")
(?C aw-copy-window "Copy Window")
(?J aw-switch-buffer-in-window "Select Buffer")
(?D aw-use-frame "Make frame for window")
(?N aw-flip-window)
(?U aw-switch-buffer-other-window "Switch Buffer Other Window")
(?E aw-execute-command-other-window "Execute Command Other Window")
(?F aw-split-window-fair "Split Fair Window")
(?S aw-split-window-vert "Split horizontally")
(?V aw-split-window-horz "Split vertically")
(?O delete-other-windows "Delete Other Windows")
(?T aw-transpose-frame "Transpose Frame")
;; ?i ?r ?t are used by hyperbole.el
(?? aw-show-dispatch-help))))

(setup evil
(:global (:rebind #'evil-window-next #'ace-window
#'other-window #'ace-window)))
#+end_src

=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.
#+begin_src emacs-lisp
(setup ace-window-posframe
(:when-loaded (:enable)))
#+end_src

*** Spell Checking
See the [[info:emacs#Spelling][documentation]] for details.

Emacs comes with a spell checking wrapper...
#+begin_src emacs-lisp
(setup ispell
(:needs "hunspell")
(:option ispell-program-name "hunspell"))
#+end_src

... and an on-the-fly spell checker(which uses ~ispell~ as the backend).
#+begin_src emacs-lisp
(setup flyspell
(:needs "hunspell")
(:unbind "C-;")
(:hook-into text-mode)
(:with-mode flyspell-prog-mode
(:hook-into prog-mode)))
#+end_src

**** Flyspell Correct
The 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.

Note 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:
#+begin_src scheme
(simple-service
'home-emacs-flyspell
home-emacs-service-type
(home-emacs-extension
(packages
(map specification->package
'("hunspell"
"hunspell-dict-en-us"
"emacs-flyspell-correct-next")))))
#+end_src

I