Dotfiles

Table of Contents

Personal Information

Local Directories

home directory

echo -n '~'

config directory

echo '~/.config'

document directory

(file-name-concat "~/" "<<laptop-node()>>" "doc")

data directory

realpath "${HOME}/data"

share directory

My share directory is where my variable data stay.

echo -n '~/.local/share'

media directory

<<media-dir()>>
echo -n '~/.local/share/video'

trash directory

(funcall (if (= full-path 0) #'identity #'expand-file-name)
         "~/.local/share/Trash")

delete file which lives longer than 1 week in trash directory.

d <<trash-dir(full-path=1)>>/files - - - 1w

log file directory

echo -n '~/.local/state/log'
<<log-dir()>>

Email Settings

Smtp Server Host   crypt

–—BEGIN PGP MESSAGE–—

hQEMA9uWvE5LJ2ejAQgAgdgJUS0v+ASUy6B5/RsqDdm/3ll8VK3bq+GlNPdPUIca hO2qmuJfgiwXWmNZtr4wSFLYOEU6l9PjM8lCS+U1dcv18XcX0v3cjIZKb/0EiABl PLnxdI0Nnkk3RBrWICP1+DLE48rCuJQvdHLzZ8wsVw1Wh4/BizIvY+t7+TxSDKLy 4UissNE9C+lftBL7caIaSUOyeppoQCKUaM4TaRiJU/Yv0QpCZvp6x03dSoTkr10h l73EhhlkhbwvAUAg8Sb2hX7bAFwR8qp6BC8XkbtZM5u2uqrYUAmXGIYmHWOYSvQZ hsxj9jzPkt8Y+xIZ//+oXxhN0UFigwGHn5IbIlB2TNJ+AYMlUGxMiv13YoI19N09 aGTmms93QHI7c86kfJNi4bI20kDLOwZThFK3gXEpzVFFT0rD1LOVga3Dqw1VsO0N EEcaZWs9POlc4rdeF3kn3ZSB0P3J77vidiaIFOh5lNpU/9k7nEmT9QE0K++fpTxs obm9ZjahsnwNP10Vn2pQ =rUbc –—END PGP MESSAGE–—

Dummy Smtp Server Host

This is a dummy SMTP server definition that serves as a fallback when the genuine definition (encrypted above) is absent.

smtp.dummy.com

My Email Address   crypt

–—BEGIN PGP MESSAGE–—

hQEMA9uWvE5LJ2ejAQgAtC7Jd2eChH/yaCKEx5ecJFRpmv0JJlymqJU7Jf2ShbgF FQeDPbqQaBAyp/vV9RVxeiYV5EsOofTJYhlLvvdiv4zq4NiaaEDjrUblOGWxaEHp KBMdKLqqLADM9RfvPv2vrofI7FtxAG7PF+wRFeW1vXyKakjhnPA/ydJpDdOIgjzh HCPmcZnYObSz9pwKPM05Rhfd7eLtOtaWBCxmTLGHhVd9xU0dXJ3eQwO0OeqYns7l QWq4lQ9Q825aZ7h+hKkamleVuKy8kjrBxnfdsyHGMaPUQlQcTTnNmS50+EmiHh4Z 0I9N+vIhAM6yuz5Y/C4pndMgggFjKYCpmVAnIJLLjdKBASx2bGgpUfmRqdHeOwY5 /3zipNj2QRjif9iZuSNdgthn2Yz0wtZuzwzc+CGib/fqtY+N5Nt36mhydgbac36v y3bLp9UlGFAAUsqmvw1EP0nmtONIX+9SZDL3B08OV6OdtmmINKNAR/8rkDZN8o0d 5F/lEfK8uU00O1Cwjl6O8YK/ =qchL –—END PGP MESSAGE–—

Dummy Email Address

This is a dummy email address that serves as a fallback when the genuine (encrypted above) is absent or not available.

user@dummy.com

Full Name   crypt

–—BEGIN PGP MESSAGE–—

hQEMA9uWvE5LJ2ejAQf/fpFgrhLTsuuXLWW4RU3WzAee1B+bBReCkH3mBbS/YWhO 8Cdle+MnLNbZ9WMOxZuSA8Zrb+6GM9rijC5UEwkT172KlmikttltCUyn4If6M/R3 zGyWy0Y0CAYL5QT7v/IMnBgwjo5nPvfjXQASbuvf/PhNZvl1Gi3Zcz+VAQGtFkhU JK6h+HRb//NyzkwqY9WhXPxQePRxF2xQcFJnWLSg+viV5fid6ANRMBVPZAUhj1pg Ew/uj4a4jGK14L2zrlhbtzYygpo3TrOiJRPJ2g8nmr9g6CBb7qAFo+27kqTPHk2P qsZENoKtr3yzbLLL9SOOO0OD6A+bzH775hRySkt2ONJ/Ae8d3Fl+tke/QjaLpjC1 I17sR6dQ3Qz24BdNkg4rC5DvOftdqppf2vxJXN5Th7izSAmZHly6KGauoMi+qdnH foMOc7y5cb5uQiPYIfNQICKo4Ut9yWjfnI79b94LUZopRTJpcVc7uCbcZ0E2iHTG Kk2NxYDwNZELB7iqDq9mbQ== =O5aZ –—END PGP MESSAGE–—

Dummy Full Name

This is a dummy full name that serves as a fallback when the genuine (encrypted above) is absent or not available.

taro.yamada

My Login Name

user-login-name

My Domain Name

echo -n "${EMAIL}" | cut -d'@' -f2

Machines

Primary Laptop

uname -n

Linux Distributions

Ubuntu

initialization script for a ubuntu account

mkdir -p <<make-dir>>

apt-install
npm-install
start-systemd-services

<<ubuntu-setup>>

Apt Package Manager

  • apt-managed packages

    sudo apt-get install -y \
         <<apt-pkg>>
    
  • update apt packages every weekend

    pwsudo apt update && pwsudo apt upgrade --yes
    

Linux Mint (Cinnamon DE)

  • Back-up Cinnamon Desktop Configuration

    dconf dump /org/cinnamon/ > <<home-dir()>>/.local/state/cinnamon/desktop.conf
    
  • Commands/Applications that need to use LinuxMint comfortably

    "xclip"
    

GNU Guix

Below is Elisp code defining Guix templates for TempEL.

(guix-search "guix search " (p (my/region-string-trans-buffer)))
(activate-profiles "activate-profiles "
                   (completing-read "profiles: "
                                    (mapcar (lambda (elt)
                                              (string-join elt " "))
                                            (my/combinations
                                             '("base" "desktop" "emacs")))))

Channels

Guix channels of my choice, providing package definitions.

(list (channel
       (name 'nonguix)
       (url "https://gitlab.com/nonguix/nonguix"))
      (channel
       (name 'guix)
       (url "https://git.savannah.gnu.org/git/guix.git"))
      (channel
       (name 'guix-jp)
       (url "https://gitlab.com/guix-jp/channel")
       (branch "main"))
      (channel
       (name 'p-snow)
       (url "https://github.com/p-snow/guix-channel")
       (introduction
        (make-channel-introduction
         "3a10227fbc2d5e9744aced43f820a0d3bf64add5"
         (openpgp-fingerprint
          "CF56 FC53 3AD6 6A67 6FDC  1D73 0D2B AF0E 8AEF 0306")))))

A script for updating channels

This script makes it easy to update all channels to the latest commit based on an original channel file.

update-channels
guix pull --channels=$HOME/.config/guix/base-channels.scm \
  && guix describe --format=channels > ~/.config/guix/channels.scm

Profiles

Below is an initialization code for Guix profiles in Bash.

echo -n '~/.guix-extra-profiles'
export GUIX_EXTRA_PROFILES=<<guix/ex-prof()>>

# fill in a variable below like a comment line
# if you'd like to acticate specific profiles under GUIX_EXTRA_PROFILES
ACTIVE_PROFILE_NAMES=()
# ACTIVE_PROFILE_NAMES=(base emacs)

profiles=()
if [ ${#ACTIVE_PROFILE_NAMES[@]} -eq 0 ]; then
  profiles="${GUIX_EXTRA_PROFILES}/*"
else
  for name in ${ACTIVE_PROFILE_NAMES[@]}; do
    profiles+=("${GUIX_EXTRA_PROFILES}/${name}")
  done
fi

for profile in ${profiles[@]}; do
  GUIX_PROFILE="${profile}/$(basename ${profile})"
  if [ -f ${GUIX_PROFILE}/etc/profile ]; then
    . "${GUIX_PROFILE}"/etc/profile
  fi
  if [ -d ${GUIX_PROFILE}/share ]; then
    export XDG_DATA_DIRS=${GUIX_PROFILE}/share${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS
  fi
  if [ -d ${GUIX_PROFILE}/share/man ]; then
    export MANPATH=${GUIX_PROFILE}/share/man${MANPATH:+:}$MANPATH
  fi
done

# export GUIX_PROFILE="$HOME/.guix-profile"
# . "$GUIX_PROFILE/etc/profile"

export GUIX_PROFILE="$HOME/.config/guix/current"
. "$GUIX_PROFILE/etc/profile"


if [ -v GUIX_ENVIRONMENT ]; then
  if [[ $PS1 =~ (.*)"\\$" ]]; then
    PS1="${BASH_REMATCH[1]} [env]\\\$ "
  fi
fi

activate-profiles is a script that accepts space-separated profile names, whose packages will be fetched:

activate-profiles base emacs
GREEN='\033[1;32m'
RED='\033[1;30m'
NC='\033[0m'
GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles

profiles=$*
if [ $# -eq 0 ]; then
  profiles="$HOME/.config/guix/manifests/*.scm";
fi

for profile in $profiles; do
  # Remove the path and file extension, if any
  profileName=$(basename $profile)
  profileName="${profileName%.*}"
  profilePath="$GUIX_EXTRA_PROFILES/$profileName"
  manifestPath=$HOME/.config/guix/manifests/$profileName.scm

  if [ -f $manifestPath ]; then
    echo
    echo -e "${GREEN}Activating profile:" $manifestPath "${NC}"
    echo

    mkdir -p $profilePath
    guix package --manifest=$manifestPath --profile="$profilePath/$profileName"

    # Source the new profile
    GUIX_PROFILE="$profilePath/$profileName"
    if [ -f $GUIX_PROFILE/etc/profile ]; then
      . "$GUIX_PROFILE"/etc/profile
    else
      echo -e "${RED}Couldn't find profile:" $GUIX_PROFILE/etc/profile "${NC}"
    fi
  else
    echo "No profile found at path" $profilePath
  fi
done
  • Automating Script Update packages in base profile at morning

    pwsudo -i guix pull && pwsudo systemctl restart guix-daemon.service
    update-channels && activate-profiles base emacs desktop && guix gc -d 2m -F 50G
    
  • guix base profile
    (specifications->manifest
     '("coreutils"
       "diffutils"
       "parallel"
       "gawk"
       "sed"
       "tar"
       "zip"
       "unzip"
       "zstd"
       "p7zip"
       "shadow"
       "sshfs"
       "grep"
       "ripgrep"
       "man-db"
       "less"
       "pwgen"
       "file"
       "nkf"
       "lsof"
       "tree"
       "poppler"
       "stow"
       "pandoc"
       "nss-certs"
       "openssl"
       "glibc"
       "procps"
       "time"
       "htop"
       "net-tools"
       "curl"
       "wget"
       "httrack"
       "ghc-tldr"
       "network-manager"
       "wakelan"
       "speedtest-cli"
       "parted"
       "fdisk"
       "gptfdisk"
       "cryptsetup"
       "smartmontools"
       "ddrescue"
       "sqlite"
       "libarchive"
       "imagemagick"
       "pngquant"
       "ffmpeg"
       "dav1d"
       "jq"
       "pup"
       "htmlq"
       "python-feedparser"
       "util-linux"
       "binutils"
       "make"
       "llvm"
       "cmake"
       "libtool"
       "pkgconf"
       "texinfo"
       "shellcheck"
       "global"
       "perl"
       "pfetch"
       "neofetch"
       <<guix-base>>))
    
  • guix desktop profile
    (specifications->manifest
     '("gparted"
       "gcompris-qt"
       "gnome-disk-utility"
       "kdeconnect"
       "dia"
       "shotwell"
       "vlc"
       <<guix-desktop>>))
    
  • guix creative profile   ARCHIVE
  • guix emacs profile
    (specifications->manifest
     '(<<guix-emacs>>
       "sicp"
       "isync"
       "stunnel"
       "wordnet"
       "emacs-transient"
       "emacs-compat"))
    

Home Configuration

;; This "home-environment" file can be passed to 'guix home reconfigure'
;; to reproduce the content of your profile.  This is "symbolic": it only
;; specifies package names.  To reproduce the exact same profile, you also
;; need to capture the channels being used, as returned by "guix describe".
;; See the "Replicating Guix" section in the manual.

(use-modules (gnu home)
             (gnu home services)
             (gnu home services shells)
             (gnu home services ssh)
             (gnu home services dotfiles)
             (gnu packages)
             (gnu packages base)
             (gnu packages fonts)
             (gnu packages fontutils)
             (gnu packages gnupg)
             (gnu services)
             (guix gexp))

(home-environment
 ;; Below is the list of packages that will show up in your
 ;; Home profile, under ~/.guix-home/profile.
 (packages (list glibc-locales fontconfig which
                 font-iosevka font-ipa font-ipa-ex font-google-noto font-inconsolata
                 font-adobe-source-han-sans))

 ;; Below is the list of Home services.  To search for available
 ;; services, run 'guix home search KEYWORD' in a terminal.
 (services
  (list
   (simple-service 'basic-env-vars-service
                   home-environment-variables-service-type
                   `(("EMAIL" . "<<email>>")
                     ("VIEWER" . "less")
                     ("EDITOR" . "emacsclient -c -a emacs")))
   (service home-bash-service-type
            (home-bash-configuration
             (guix-defaults? #t)
             (aliases '(<<print-aliases(lang="scheme")>>))
             (bashrc (list (local-file "./.bashrc" "bashrc")))))
   (service home-openssh-service-type
            (home-openssh-configuration
             (hosts
              (list (openssh-host (name "pc02")
                                  (host-name "192.168.100.127"))))))
   (service home-dotfiles-service-type
            (home-dotfiles-configuration
             (layout 'plain)
             (source-directory
              (let ((path "<<ghq-proj-path(name="p-snow/environment")>>"))
                (if (string-prefix? "~" path)
                    (string-append (getenv "HOME") (substring path 1))
                    path)))
             (directories '("home")))))))

A script for activating home configuration

(activate-home "cd ~/environment && make")

Home Environment

Fontconfig   ARCHIVE

Bash

bashrc

export PATH="${HOME}/.local/bin:$PATH"
export PS1="[\u@$(uname -n):\\W]\n\$ "

<<bashrc>>

bash functions   ARCHIVE

Shell Command Aliases

.. cd ../  
e exit  
l ls -F $*
ll ls -lh $*
lld l -ld $*
la l -a $*
lal l -alh $*
cp cp -i $*
mv mv -i $*
rm rm -i $*
rmf rm -rf $*
mkdir mkdir -pv $*
rmdir rmdir -v $*
ipad hostname -I | cut -f1 -d' '  
kem pkill -kill emacs  
(seq-do-indexed (lambda (alias index)
                  (let ((ali (car alias))
                        (def (replace-regexp-in-string "\\\\vert" "|" (cadr alias)))
                        (param (caddr alias)))
                    (pcase lang
                      ('"plain"
                       (princ (funcall 'string-join `(,ali ,def ,param) " "))
                       (when (< index (1- (length aliases))) (princ "\n")))
                      ('"scheme"
                       (prin1 (funcall 'cons ali def))
                       (princ "\n")))))
                aliases)

Systemd

<<systemd-service>>

systemd-tmpfiles

systemd-tmpfiles manages file creation and deletion. I mainly use systemd-tmpfiles to delete obsolete files in specific directories by running the following command.

$ systemd-tmpfiles --user --clean

or set up timer shown below.

systemctl --user enable systemd-tmpfiles-clean.timer
d /home/<<login-name()>>/tmp - - - 5d
d /home/<<login-name()>>/Downloads - - - 4w
<<tmpfiles-cleanup>>

Vixie Cron

A script to reinitialize a set of cron jobs

crontab <<home-dir()>>/.config/crontab/<<login-name()>>
cron-reinit.sh
MAILTO="<<email>>"

BIN_DIR="<<home-dir()>>/bin"
LOG_DIR="<<log-dir()>>"

<<cronjob-user>>

Btrfs

Btrfs is a CoW (Copy on Write) file system supports snapshot and send/recv mechanism.

"btrfs-progs"

btrbk

Btrbk supports for taking snapshots and backups

"btrbk"
  • btrbk.conf

    btrbk.conf documentation

    timestamp_format long
    incremental yes
    
    snapshot_preserve_min 6h
    snapshot_preserve 36h 3d 3w
    
    # Create snapshots only if the backup disk is attached
    #snapshot_create ondemand
    
    target_preserve_min latest
    
    snapshot_dir snapshots
    volume /mnt/homes
    target /mnt/exbak/<<laptop-node()>>
    
    target_preserve 2w 3m
    subvolume home-mint
    
    target_preserve 8w 6m 2y
    subvolume data
    
  • snapshot/backup cron job
    */15 * * * * /usr/bin/bash -ci "which btrbk | xargs -I _ pwsudo _ --config ~/.config/btrbk/btrbk.conf run"
    

snapshots management   ARCHIVE

backup to another device   ARCHIVE

XDG

"xdg-utils"

xdg-mime

  • ask default application for text/plain

    $ xdg-mime query default text/plain
    
[Added Associations]
inode/directory=io.github.celluloid_player.Celluloid.desktop;nemo.desktop;

[Default Applications]
inode/directory=nemo.desktop

GnuPG (gpg)

GnuPG - ArchWiki

I've chosen to use gpg/gpg-agent and pass on Ubuntu system since pass (password-store) spouts a warning saying there's gpg version mismatch persistently.

;; "gnupg"
  • gpg config

    with-keygrip
    
  • gpg-agent config

    pinentry-program /usr/bin/pinentry-curses
    allow-emacs-pinentry
    allow-loopback-pinentry
    enable-ssh-support
    max-cache-ttl <<hours-in-sec(h=700)>>
    default-cache-ttl <<hours-in-sec(h=700)>>
    default-cache-ttl-ssh <<hours-in-sec(h=48)>>
    
  • mandatory config to use pinentry-curses for gpg-agent

    export GPG_TTY=$(tty)
    
    # Refresh gpg-agent tty in case user switches into an X session
    gpg-connect-agent updatestartuptty /bye >/dev/null
    

Key Remappers

Xremap

Xremap is a key remapper for Wayland/X11. It's astoundingly fast, customizable and easy-to-use.

"xremap-x11"
  • xremap config
    modmap:
      - name: CapsCtrlSwap
        remap:
          CapsLock: Ctrl_L
          Ctrl_L: CapsLock
          Ctrl_R: Super_R
      - name: Enter as RCtrl
        remap:
          KEY_ENTER:
            held: Ctrl_R
            alone: KEY_ENTER
            alone_timeout_millis: 500
      - name: Quote as Hyper
        remap:
          KEY_APOSTROPHE:
            held: Super_L
            alone: KEY_APOSTROPHE
      - name: SandS
        remap:
          Space:
            held: Shift_L
            alone: Space
            alone_timeout_millis: 500
      - name: Left/Right Shift to Equal/Minus
        remap:
          Shift_L:
            held: Shift_L
            alone: KEY_EQUAL
            alone_timeout_millis: 300
          Shift_R:
            held: Shift_R
            alone: KEY_MINUS
            alone_timeout_millis: 300
    keymap:
      - name: M-u/H-u to C-u
        exact_match: true
        remap:
          M-KEY_U: Ctrl-KEY_U
          Super-KEY_U: Ctrl-KEY_U
      - name: asdf1234
        exact_match: true
        remap:
          C-Super-a: KEY_1
          C-Super-s: KEY_2
          C-Super-d: KEY_3
          C-Super-f: KEY_4
          C-Super-g: KEY_5
          C-Super-h: KEY_6
          C-Super-j: KEY_7
          C-Super-k: KEY_8
          C-Super-l: KEY_9
          C-Super-semicolon: KEY_0
    

    As of [2024-03-29 Fri], the following configuration to propagate Emacs key bindings for all applications except Emacs does not seem to work correctly.

    # keymap:
    #   - name: Emacs
    #     application:
    #       not: [emacs.Emacs]
    #     remap:
    #       # Cursor
    #       C-b: { with_mark: left }
    #       C-f: { with_mark: right }
    #       C-p: { with_mark: up }
    #       C-n: { with_mark: down }
    #       # Forward/Backward word
    #       M-b: { with_mark: C-left }
    #       M-f: { with_mark: C-right }
    #       # Beginning/End of line
    #       C-a: { with_mark: home }
    #       C-e: { with_mark: end }
    #       # Page up/down
    #       M-v: { with_mark: pageup }
    #       C-v: { with_mark: pagedown }
    #       # Beginning/End of file
    #       M-Shift-comma: { with_mark: C-home }
    #       M-Shift-dot: { with_mark: C-end }
    #       # Newline
    #       C-m: enter
    #       C-j: enter
    #       C-o: [enter, left]
    #       # Copy
    #       C-w: [C-x, { set_mark: false }]
    #       M-w: [C-c, { set_mark: false }]
    #       C-y: [C-v, { set_mark: false }]
    #       # Delete
    #       C-d: [delete, { set_mark: false }]
    #       M-d: [C-delete, { set_mark: false }]
    #       # Kill line
    #       C-k: [Shift-end, C-x, { set_mark: false }]
    #       # Kill word backward
    #       Alt-backspace: [C-backspace, {set_mark: false}]
    #       # set mark next word continuously.
    #       C-M-space: [C-Shift-right, {set_mark: true}]
    #       # Undo
    #       C-slash: [C-z, { set_mark: false }]
    #       C-Shift-ro: C-z
    #       # Mark
    #       C-space: { set_mark: true }
    #       # Search
    #       C-s: C-f
    #       C-r: Shift-F3
    #       M-Shift-5: C-h
    #       # Cancel
    #       C-g: [esc, { set_mark: false }]
    #       # C-x YYY
    #       # C-x:
    #       #   remap:
    #       #     # C-x h (select all)
    #       #     h: [C-home, C-a, { set_mark: true }]
    #       #     # C-x C-f (open)
    #       #     C-f: C-o
    #       #     # C-x C-s (save)
    #       #     C-s: C-s
    #       #     # C-x k (kill tab)
    #       #     k: C-f4
    #       #     # C-x C-c (exit)
    #       #     C-c: C-q
    #       #     # C-x u (undo)
    #       #     u: [C-z, { set_mark: false }]
    
  • xremap systemd service
    sudo systemctl enable xremap
    sudo systemctl start xremap
    
    [Unit]
    Description=xremap daemon
    
    [Service]
    ExecStart=<<which(filename="xremap")>> --watch=device,config <<conf-dir()>>/xremap/config.yml <<conf-dir()>>/xremap/emacs.yml
    Restart=always
    Type=simple
    
    [Install]
    WantedBy=multi-user.target
    

XKB   ARCHIVE

Interception Tools   ARCHIVE

Application Configurations

(expand-file-name "~/.local/bin")

GNU Emacs

"emacs"
;; "emacs-next"
no-littering-var-directory

basic configuration

  • early-init.el

    Early init file defines fundamental variables used from both normal Emacs session and batch mode.

    • Paths for tangling

      Variables that start with "tangle/" and are used during the tangling process are also required for the main Emacs session.

      <<tangle-def()>>
      
      (let ((syms (string)))
        (obarray-map (lambda (sym)
                       (when (string-prefix-p "tangle/" (symbol-name sym))
                         (setq syms
                               (concat syms
                                       (format "(setq %s \"%s\")\n"
                                               sym (symbol-value sym))))))
                     obarray)
        syms)
      
    • package.el
      (require 'package)
      
      (setopt package-archives
              '(("gnu" . "https://elpa.gnu.org/packages/")
                ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                ("melpa" . "https://melpa.org/packages/")
                ("p-snow" . "https://p-snow.org/packages/")))
      (setopt package-archive-priorities
              '(("gnu" . 3)
                ("melpa" . 2)
                ("nongnu" . 1)))
      (setopt package-enable-at-startup t)
      
      (require 'package-vc)
      
      (package-initialize)
      
    • load cl-lib

      Use 'cl-lib' rather than 'cl' package since it is officially deprecated.

      (require 'cl-lib)
      
    • use-package

      Official manual is handy especially when you look up Keywords.

      (require 'use-package)
      (require 'use-package-ensure)
      
      (setopt use-package-compute-statistics t
              use-package-verbose nil)
      (with-eval-after-load 'my-launch-app
        (keymap-set my/invoke-list-command-map (kbd "u")
                    #'use-package-report))
      

      use-package depends on following packages internally

      • diminish
        "emacs-diminish"
        
      • delight
        "emacs-delight"
        
    • straight.el   ARCHIVE
    • safe local eval forms

      Following hook statements are likely written in .dir-locals.el at the top level of git repositories.

      (add-to-list 'safe-local-eval-forms
                   '(add-hook 'find-file-hook
                              (lambda () (setq-local buffer-save-without-query t))))
      
      (add-to-list 'safe-local-eval-forms
                   '(remove-hook 'find-file-hook
                                 (lambda () (setq-local buffer-save-without-query t))))
      
    • no-littering
      "emacs-no-littering"
      
      (setq no-littering-var-directory
            (file-name-concat "<<share-dir()>>" "emacs/var/"))
      
      (require 'no-littering)
      
  • loading sequence
    • custom file
      (setopt custom-file
              (expand-file-name "custom.el" user-emacs-directory))
      
      (when (file-readable-p custom-file)
        (load custom-file))
      
    • init.el

      init.el devote itself to call for remaining config files.

      (push (file-name-concat user-emacs-directory "lisp") load-path)
      (push (file-name-concat user-emacs-directory "site-lisp") load-path)
      
      (let ((load-match "\\.el$"))
        (mapc #'load-file
              (append (directory-files (file-name-concat user-emacs-directory "lisp") t load-match)
                      (directory-files (file-name-concat user-emacs-directory "lisp/dedicated") t load-match))))
      
  • Themes

    The theme of my choice at present

    (with-eval-after-load 'ef-themes
      (load-theme 'ef-bio :no-confirm))
    
    • modus-themes

      I love modus-theme which conforms to WCAG AAA. This blog post explains how this package determines colors theoretically and perceptually.

      Following configuration is for version 4 of modus-themes which takes breaking change from previous major version.

      (use-package modus-themes
        :disabled t
        :custom
        (modus-themes-to-toggle '(modus-vivendi
                                  modus-vivendi-tinted
                                  modus-vivendi-deuteranopia))
        (modus-themes-org-blocks 'tinted-background)
        (modus-themes-bold-constructs t)
        :config
        (customize-set-variable 'modus-themes-common-palette-overrides
                                modus-themes-preset-overrides-faint)
        (bind-keys ("<f6>" . modus-themes-toggle)))
      
    • ef-themes
      "emacs-ef-themes"
      
      (use-package ef-themes
        :init
        (setq ef-bio-palette-overrides
              '((cursor "#ffffff")))
        :custom
        (ef-themes-to-toggle ef-themes-dark-themes)
        :config
        (bind-keys ("<f9>" . ef-themes-toggle)))
      
  • Basic Preferences

    Settings in this section are influenced a great deal by my personal preference. Please be meticulous when you borrow.

    • Modifier Keys
      (cond
       ((string= window-system "x")
        (setf x-meta-keysym 'meta
              x-alt-keysym 'meta
              x-super-keysym 'hyper))
       ((string= window-system "ns")
        ;; IME inline patch
        (setf mac-use-input-method-on-system nil)
        (setf mac-control-modifier       'control
              mac-command-modifier       'meta
              mac-option-modifier        'super
              mac-right-option-modifier  'alt
              mac-right-control-modifier 'super
              mac-function-modifier      'hyper)))
      
    • Basic Key Bindings
      (keymap-global-set "M-F" #'forward-to-word)
      (keymap-global-set "M-B" #'backward-to-word)
      (keymap-global-set "C-c d" #'duplicate-dwim)
      (keymap-global-set "C-c j" #'join-line)
      (keymap-global-set "C-c M-d" #'copy-from-above-command)
      (keymap-global-set "M-z" #'zap-up-to-char)
      (keymap-global-set "C-z" #'repeat)
      (keymap-global-set "C-x M-t" #'transpose-sentences)
      (keymap-global-set "M-T" #'transpose-paragraphs)
      
      (keymap-global-set "C-c k" #'kill-this-buffer)
      (keymap-global-set "C-x M-r" #'rename-visited-file)
      (keymap-global-set "C-M-<delete>" #'restart-emacs)
      (keymap-global-set "C-M-S-<delete>" #'save-buffers-kill-emacs)
      
      (substitute-key-definition 'upcase-region
                                 'upcase-dwim
                                 global-map)
      (substitute-key-definition 'downcase-region
                                 'downcase-dwim
                                 global-map)
      (keymap-global-set "C-x C-c" #'capitalize-dwim)
      
      • Bind for C-x C-b, use ibuffer which has more features than list-buffers based on this advice.

        (substitute-key-definition 'list-buffers
                                   'ibuffer
                                   global-map)
        
      • bind find-functions and finder commands

        (find-function-setup-keys)
        
        (keymap-set ctl-x-map "C" 'finder-commentary)
        
    • Unleash Disabled Commands
      (put 'upcase-region 'disabled nil)
      (put 'downcase-region 'disabled nil)
      (put 'narrow-to-region 'disabled nil)
      (put 'set-goal-column 'disabled nil)
      (put 'buffer-save-without-query 'disabled nil)
      (put 'scroll-left 'disabled nil)
      (put 'scroll-right 'disabled nil)
      
    • Language

      emacs#Language Environments

      ;; language and locale
      (set-language-environment "Japanese")
      (setq system-time-locale "C")
      
      ;; coding system
      (set-default-coding-systems 'utf-8-unix)
      (prefer-coding-system 'utf-8-unix)
      (set-selection-coding-system 'utf-8-unix)
      
      ;; prefer-coding-system take effect equally to follows
      (set-buffer-file-coding-system 'utf-8-unix)
      (set-file-name-coding-system 'utf-8-unix)
      (set-terminal-coding-system 'utf-8-unix)
      (set-keyboard-coding-system 'utf-8-unix)
      (setq locale-coding-system 'utf-8-unix)
      
    • Fontsets
      (create-fontset-from-ascii-font "IPAGothic" nil "default")
      
      (create-fontset-from-ascii-font "IPAexMincho" nil "exmincho")
      (set-fontset-font "fontset-exmincho" 'japanese-jisx0208 "IPAexMincho")
      
      (create-fontset-from-ascii-font "IPAexGothic" nil "exgothic")
      (set-fontset-font "fontset-exgothic" 'japanese-jisx0208 "IPAexGothic")
      
      (create-fontset-from-ascii-font "IPAMincho" nil "mincho")
      (set-fontset-font "fontset-mincho" 'japanese-jisx0208 "IPAMincho")
      (set-fontset-font "fontset-mincho" 'symbol "IPAMincho")
      
      (create-fontset-from-ascii-font "Iosevka" nil "code")
      (set-fontset-font "fontset-code" 'latin (font-spec :family "Iosevka" :weight 'Light :width 'Normal))
      
      ;; foreign fonts for all fontsets
      (set-fontset-font t 'emoji "Noto Color Emoji")
      (set-fontset-font t 'symbol "Noto Sans CJK JP" nil 'append)
      (set-fontset-font t 'symbol "Noto Sans Symbols" nil 'append)
      (set-fontset-font t 'symbol "Noto Sans Symbols2" nil 'append)
      
    • Faces

      Caveat: I am visually impaired, so the font size for the default face is extraordinarily large.

      face name font
      default IPAGothic
      fixed-pitch IPAMincho
      fixed-pitch-serif Iosevka
      variable-pitch IPAExMincho
      variable-pitch-text IPAExGothic
      (defvar my/default-pixel-width 2560 "Default display width in pixel.")
      (defmacro my/normalized-font-size (original-size)
        "This macro culculates normalized font size for display resolution at runtime.
      It tries to proportionate ORIGINAL-SIZE in `my/default-pixel-width' in the display at runtime."
        `(truncate (* ,original-size (/ (float (x-display-pixel-width))
                                        ,my/default-pixel-width))))
      
      (set-face-attribute 'default
                          nil
                          :font "fontset-default"
                          :height (my/normalized-font-size 620))
      (set-face-attribute 'fixed-pitch
                          nil
                          :font "fontset-mincho")
      (set-face-attribute 'fixed-pitch-serif
                          nil
                          :font "fontset-code")
      (set-face-attribute 'variable-pitch
                          nil
                          :font "fontset-exmincho")
      (set-face-attribute 'variable-pitch-text
                          nil
                          :font "fontset-exgothic")
      
      (use-package face
        :no-require t
        :hook
        ((eww-mode mastodon-mode nov-mode mu4e-view-mode elfeed-show-mode)
         . (lambda ()
             (buffer-face-set (check-face 'variable-pitch-text))
             (text-scale-set 5.0)))
        ((Info-mode help-mode helpful-mode woman-mode devdocs-mode)
         . (lambda ()
             (buffer-face-set (check-face 'default))
             (text-scale-set 1.3)))
        ((org-mode text-mode mu4e-compose-mode)
         . (lambda ()
             (buffer-face-set (check-face 'default))))
        ((prog-mode shell-mode term-mode vterm-mode eshell-mode calendar-mode)
         . (lambda ()
             (buffer-face-set (check-face 'fixed-pitch-serif))
             (text-scale-set -1.2)))
        ((dired-mode mu4e-headers-mode elfeed-search-update)
         . (lambda ()
             (buffer-face-set (check-face 'fixed-pitch)))))
      
    • Performance Tuning
      (setq auto-window-vscroll nil)
      (setq-default bidi-display-reordering nil)
      (setq bidi-inhibit-bpa t)
      
  • Custom Variables

    Entries in this section represnets Customization Groups hierarchy.

    • Files
      (setopt create-lockfiles nil
              remote-file-name-inhibit-locks t)
      
      • Auto Revert

        Auto Revert: Keeping buffers automatically up-to-date.

        (use-package autorevert
          :diminish (global-auto-revert-mode auto-revert-mode)
          :custom
          (auto-revert-verbose nil)
          (global-auto-revert-non-file-buffers t)
          (auto-revert-interval 3)
          (auto-revert-check-vc-info t)
          (global-auto-revert-mode t))
        
      • Auto Save

        Auto Save mode saves the file you are editing periodically, whereas auto-save-visited-mode saves all open buffers in Emacs session.

        (setopt auto-save-default t
                auto-save-interval 200
                auto-save-timeout 25
                auto-save-file-name-transforms
                `((".*" ,(no-littering-expand-var-file-name "auto-save/files") t))
                delete-auto-save-files t
                kill-buffer-delete-auto-save-files t
                auto-save-no-message t)
        
        (setopt auto-save-visited-mode t
                auto-save-visited-interval 10)
        (put 'auto-save-visited-mode 'disabled nil)
        
        (setopt delete-by-moving-to-trash t)
        (setopt save-some-buffers-default-predicate 'save-some-buffers-root)
        
      • Uniquify

        Uniquify shows buffer name easy to distinguish.

        (use-package uniquify
          :custom
          (uniquify-buffer-name-style 'post-forward))
        
      • Recentf

        emacs#File Conveniences

        (use-package recentf
          :hook (after-init . recentf-mode)
          :custom
          (recentf-exclude '(".gz" ".xz" ".zip" ".gpg"))
          (recentf-max-saved-items 500)
          ;; clean-up when idle time reaches 7 minutes
          (recentf-auto-cleanup (* 7 60)))
        
      • Tramp
        (use-package tramp
          :defer t
          :custom
          (tramp-default-method "ssh")
          :config
          (add-to-list 'tramp-remote-path 'tramp-own-remote-path)
          (add-to-list 'tramp-remote-path "~/bin"))
        
      • Find File
        (setopt large-file-warning-threshold 30000000)
        (setopt revert-buffer-quick-short-answers t)
        
      • Backup

        I no longer use backup functionality. Even if I reinstate it, backing up by copying must be rational.

        (setopt make-backup-files nil
                backup-by-copying t
                backup-directory-alist
                `(("." . ,(no-littering-expand-var-file-name "backup"))))
        
    • Environment
      • Initialization
        (setopt initial-scratch-message "")
        (setopt inhibit-startup-screen t)
        
      • Frames
        (tool-bar-mode -1)
        (menu-bar-mode -1)
        (scroll-bar-mode -1)
        
        • Fringe
          (set-fringe-mode 15)
          (setq-default indicate-buffer-boundaries 'left)
          
        • Two Column
          (use-package two-column
            :commands (2C-two-columns 2C-split 2C-associate-buffer)
            :custom
            (2C-window-width 25))
          
        • Desktop

          Info manual for Saving Emacs Sessions describes how to set up desktop-save-mode. '–no-desktop' option for emacs command will disable forcibly desktop-save-mode .

          (use-package desktop
            :commands (desktop-save)
            :custom
            (desktop-restore-frames t)
            (desktop-restore-eager 1)
            (desktop-lazy-idle-delay 5)
            :config
            (desktop-change-dir (expand-file-name "desktop" user-emacs-directory))
            (desktop-save-mode 1))
          
        • Cursor
          (setopt blink-cursor-blinks 15
                  blink-cursor-delay 0.7
                  blink-cursor-interval 0.35
                  blink-cursor-mode t)
          
        • Scrolling
          (setopt fast-but-imprecise-scrolling t)
          
      • Display
        ;; do not use visual bell
        (setopt visible-bell nil)
        
        (setopt text-scale-mode-step 1.0625
                highlight-nonselected-windows t
                truncate-lines t
                x-underline-at-descent-line nil)
        ;; avoid to break at whitespace in Japanese
        (setopt word-wrap-by-category t)
        ;; suppress curved quotes in docstring (for emacs25)
        (setopt text-quoting-style 'straight)
        ;; resize frame size responding to font size
        (setopt global-text-scale-adjust-resizes-frames t)
        
      • Windows

        This blog post is must-read when you tweak display-buffer facilities or something related to display settings.

        (setopt scroll-step 1
                scroll-conservatively 101
                next-screen-context-lines 2
                scroll-preserve-screen-position t)
        ;; display buffer
        (setopt display-buffer-base-action
                '((display-buffer-same-window
                   display-buffer-reuse-window
                   display-buffer-reuse-mode-window
                   display-buffer-in-previous-window)))
        
        (bind-keys :map other-window-repeat-map
                   ("0" . delete-window)
                   ("1" . delete-other-windows))
        
        • Winner

          Triple Escape (M-ESC ESC) has got to reset window layout by tweaking buffer-quit-function.

          (use-package winner
            :bind (("H-z" . winner-undo)
                   ("H-M-z" . winner-redo))
            :custom
            (winner-mode t)
            :config
            (setq buffer-quit-function 'winner-undo))
          
        • Windmove

          You can now switch windows with your shift key by pressing S-<left>, S-<right>, S-<up>, S-<down>.

          (use-package windmove
            :bind (("C-x C-<left>"    . windmove-left)
                   ("C-x C-<right>"   . windmove-right)
                   ("C-x C-<up>"      . windmove-up)
                   ("C-x C-<down>"    . windmove-down)
                   ("C-x C-S-<left>"  . windmove-swap-states-left)
                   ("C-x C-S-<right>" . windmove-swap-states-right)
                   ("C-x C-S-<up>"    . windmove-swap-states-up)
                   ("C-x C-S-<down>"  . windmove-swap-states-down)
                   ("C-x C-M-<left>"  . windmove-delete-left)
                   ("C-x C-M-<right>" . windmove-delete-right)
                   ("C-x C-M-<up>"    . windmove-delete-up)
                   ("C-x C-M-<down>"  . windmove-delete-down))
            :custom
            (windmove-mode t)
            (windmove-wrap-around t)
            :config
            (defvar-keymap my/windmove-repeat-map
              :doc "Keymap to repeat windmove commands."
              :repeat (:enter (windmove-left
                               windmove-right
                               windmove-up
                               windmove-down
                               windmove-swap-states-left
                               windmove-swap-states-right
                               windmove-swap-states-up
                               windmove-swap-states-down
                               windmove-delete-left
                               windmove-delete-right
                               windmove-delete-up
                               windmove-delete-down))
              "<left>"  #'windmove-left
              "<right>" #'windmove-right
              "<up>"    #'windmove-up
              "<down>"  #'windmove-down
              "S-<left>"  #'windmove-swap-states-left
              "S-<right>" #'windmove-swap-states-right
              "S-<up>"    #'windmove-swap-states-up
              "S-<down>"  #'windmove-swap-states-down
              "M-<left>"  #'windmove-delete-left
              "M-<right>" #'windmove-delete-right
              "M-<up>"    #'windmove-delete-up
              "M-<down>"  #'windmove-delete-down))
          
      • Minibuffer

        Formerly, I used to bind C-h to `backward-delete-char' and M-h to `backward-kill-word', which was very comfortable. However, I realized that C-h must be a help key, as many Emacs packages are developed based on the assumption that "C-h is a help key". Having said that, in the minibuffer where a help key does not necessarily play a significant role, I decided to bind C-h to `backward-delete-char'.

        (keymap-set minibuffer-mode-map
                    "C-h" #'delete-backward-char)
        (keymap-set minibuffer-mode-map
                    "M-h" #'backward-kill-word)
        (keymap-set minibuffer-mode-map "TAB" 'minibuffer-complete)
        
        (setopt history-length 1500)
        (setopt history-delete-duplicates t)
        (setopt enable-recursive-minibuffers t)
        (setopt minibuffer-depth-indicate-mode t)
        (setopt read-file-name-completion-ignore-case t)
        (setopt read-minibuffer-restore-windows t)
        (setopt minibuffer-default-prompt-format " [%s]")
        (setopt completion-cycle-threshold 1)
        (setopt completions-detailed t)
        
        (add-hook 'minibuffer-setup-hook 'my/minibuffer-setup-function)
        (defun my/minibuffer-setup-function ()
          ;; disable input method in mini buffer
          (when current-input-method
            (deactivate-input-method))
          ;; decrease font size to 90% in minibuffer
          (setq-local face-remapping-alist '((default :height 0.9))))
        
        • Savehist

          savehist-mode saves minibuffer history and additionals.

          (use-package savehist
            :custom
            (savehist-save-minibuffer-history t)
            (savehist-additional-variables '((kill-ring . 1000)
                                             (compile-command . 1000)))
            (savehist-autosave-interval 180)
            :hook
            (after-init . savehist-mode))
          
      • Menu

        This blog post demonstrates for emacsers who have disabled file pickers and dialog boxes to adversely use them temporalily.

        (setopt use-short-answers t)
        (setopt use-file-dialog nil)
        
      • Mode Line
        (defvar my/mode-line-buffer-name-length-max 15
          "Fixed length for displaying buffer name in mode line.")
        
        (setopt line-number-mode nil
                column-number-mode nil
                mode-line-compact t)
        (setopt
         mode-line-format
         '("%e"
           mode-line-front-space
           (:eval
            (let ((mode-line-buffer-name
                   (replace-regexp-in-string " %\\([[:ascii:]]\\)" " %%\\1"
                                             (truncate-string-to-width
                                              (buffer-name) my/mode-line-buffer-name-length-max nil ? t))))
              (cond
               (buffer-read-only
                (propertize mode-line-buffer-name 'face 'underline))
               ((buffer-modified-p)
                (propertize mode-line-buffer-name 'face 'warning))
               (mode-line-buffer-name))))
           (:eval
            (cond
             ((and line-number-mode
                   column-number-mode)
              mode-line-position-column-line-format)
             (line-number-mode mode-line-position-line-format)
             (column-number-mode mode-line-position-column-format)))
           " "
           global-mode-string))
        
        • Display Time
          (setopt
           display-time-string-forms
           '((propertize (format-time-string "%H:%M" now) 'face 'mode-line-highlight)))
          (setopt display-time-mode t)
          
      • Mouse

        Mouse needs to be unobtrusive in my Emacs experience.

        (use-package mouse
          :custom
          (mouse-1-click-follows-link nil)
          (mouse-highlight nil)
          (mouse-wheel-mode nil))
        
        (use-package pixel-scroll
          :custom
          (pixel-scroll-precision-mode t))
        
        • Tool Tips
          (tooltip-mode -1)
          (setq x-gtk-use-system-tooltips nil)
          
      • Hardware
        • Battery
          (use-package battery
            :after my-launch-app
            :bind (:map my/launch-app-map
                        ("b" . battery)))
          
      • Installation
        (use-package tutorial
          :commands help-with-tutorial
          :preface
          (defun my/tutorial--saved-dir ()
            (locate-user-emacs-file
             (no-littering-expand-var-file-name "tutorial/")
             (expand-file-name "tutorial/" user-emacs-directory)))
          :config
          (advice-add #'tutorial--saved-dir :override
                      #'my/tutorial--saved-dir))
        
    • Convenience

      subword-mode enables to recognize 'RSS' and 'Feed' are separate words in 'RSSFeed'

      (repeat-mode 1)
      (global-subword-mode 1)
      
      • Abbreviations
        (use-package abbrev
          :diminish abbrev-mode
          :custom
          (save-abbrevs t)
          :config
          (setq-default abbrev-mode 1)
          (quietly-read-abbrev-file))
        
      • Hippie Expand

        As this blog post mentions, Hippie Expansion is superior to dabbrev, skeleton and company in the field of auto typing.

        (use-package hippie-exp
          :bind ([remap dabbrev-expand] . hippie-expand))
        
      • Hl Line
        (use-package hl-line
          :hook (vterm-mode . (lambda () (hl-line-mode -1)))
          :config
          (hl-line-mode 1))
        
      • Visual Line
        (use-package visual-line
          :no-require t
          :after adaptive-wrap
          :hook
          ((feed-show-mode eww-after-render help-mode helpful-mode Info-mode woman-mode
                           mu4e-view-mode nov-mode devdocs-mode)
           . visual-line-mode)
          ((feed-show-mode eww-after-render help-mode helpful-mode Info-mode woman-mode
                           mu4e-view-mode nov-mode devdocs-mode)
           . adaptive-wrap-prefix-mode)
          :custom
          (global-visual-line-mode nil))
        
      • Whitespace

        emacs#Useless Whitespace

        (use-package whitespace
          :diminish ((global-whitespace-mode . "Ws")
                     (whitespace-mode . "ws"))
          :hook
          ((org-mode prog-mode dired-mode) . whitespace-mode)
          (eww-mode . whitespace-turn-off)
          (before-save . delete-trailing-whitespace)
          :custom
          (whitespace-style
           '(face trailing tabs tab-mark spaces space-mark empty missing-newline-at-eof))
          (whitespace-action '(cleanup auto-cleanup))
          (whitespace-space-regexp "\\(\x3000+\\)")
          (whitespace-trailing-regexp "\\([ \t\u00A0]+\\)$")
          (whitespace-display-mappings
           '((space-mark ?\x3000 [?\u2423])
             (tab-mark   ?\t   [?\u00BB ?\t])))
          (global-whitespace-mode nil))
        
      • So Long
        (setopt global-so-long-mode t)
        
      • Tab Bar
        (use-package tab-bar
          :disabled t
          :bind (:map ctl-x-map
                      ("t" . tab-prefix-map)
                      :map tab-bar-map
                      ("M-[" . tab-bar-history-back)
                      ("M-]" . tab-bar-history-forward))
          :custom
          (tab-bar-mode t)
          (tab-bar-show 2)
          (tab-bar-history-mode t)
          (tab-bar-tab-hints t))
        
      • Ffap
        (require 'ffap)
        
        (ffap-bindings)
        
      • Kmacro

        The power of keyboard macro is more than repeating editing commands. This post explains fluently.

        (require 'kmacro)
        
        (defalias 'kmacro-insert-macro 'insert-kbd-macro)
        (keymap-set kmacro-keymap "I" #'kmacro-insert-macro)
        
      • Register
        (use-package register
          :bind ("H-r" . jump-to-register)
          :custom
          (register-preview-delay nil)
          :config
          (with-eval-after-load 'savehist
            (add-to-list 'savehist-additional-variables
                         'register-alist)))
        
    • Editing
      ;; the point will be at the beginning of duplicated lines
      (setopt duplicate-line-final-position 1)
      
      • Indent

        See also [BROKEN LINK: id:4a58219c-74dd-4135-b56d-876b0db2cd83]

        (setopt tab-always-indent 'complete
                indent-tabs-mode nil
                tab-first-completion 'word-or-paren-or-punct)
        
      • Electricity
        (setopt electric-indent-mode nil)
        
      • Fill
        (setopt fill-column 80
                sentence-end-double-space nil)
        
      • Killing
        (setopt yank-pop-change-selection t)
        (setopt delete-active-region 'kill)
        (setopt kill-do-not-save-duplicates nil)
        (setopt kill-whole-line nil)
        
      • Undo

        The older undo step which exceeds undo-limit in byte is eliminated at garbage collection. The oldest undo step, if undo info exceeds undo-strong-limit in total, is removed instantaneously. No more new undo step than undo-outer-limit could not be registered.

        (setopt undo-limit 320000
                undo-strong-limit 480000
                undo-outer-limit 48000000
                undo-no-redo nil)
        
      • Matching
        • Isearch
          (use-package isearch
            :custom
            (isearch-allow-motion t)
            (isearch-lazy-count t)
            (isearch-lax-whitespace t)
            (isearch-regexp-lax-whitespace t)
            (search-whitespace-regexp ".*")
            :config
            (bind-keys :map isearch-mode-map
                       ("C-j" . isearch-exit)))
          
        • Bookmark
          (use-package bookmark
            :bind ("C-x 5 B" . bookmark-jump-other-frame)
            :custom
            (bookmark-save-flag 1)
            (bookmark-menu-confirm-deletion t)
            (bookmark-watch-bookmark-file 'silent))
          
      • Paragraph
        (setopt bidi-paragraph-direction 'left-to-right)
        
    • Multimedia
      • Image

        This post teaches me how to enable converting external formats (i.e. webp) to internal ones.

        (use-package image
          :custom
          (image-use-external-converter t))
        
    • Programming
      • Tools
        • Xref
          (use-package xref
            :custom
            (xref-show-definitions-function 'xref-show-definitions-completing-read)
            (xref-search-program (cond
                                  ((or (executable-find "ripgrep")
                                       (executable-find "rg"))
                                   'ripgrep)
                                  ((executable-find "ugrep")
                                   'ugrep)
                                  (t
                                   'grep))))
          
        • Ediff
          • Ediff Window
            (use-package ediff
              :commands ediff-files
              :custom
              (ediff-window-setup-function 'ediff-setup-windows-plain)
              (ediff-split-window-function 'split-window-horizontally))
            
    • Development
      • Internal
        • Storage Allocation

          If GC time at Emacs init is problematic, checking gcs-done and gc-elapsed may help you. Increasing gc-cons-* variables would minimize Emacs init time, but this also cause each GC time which may noticeable as freeze. This presentation advise to increase gc-cons-threshold to 20-80Mb if you have Emacs with slow startup, and to increase consult–gc-percentage to 0.2-1.0 if you have slow and frequent GC.

          (setq gc-cons-threshold (* 30 gc-cons-threshold)
                gc-cons-percentage 0.3)
          
      • Lisp
        • Shortdoc
          (use-package shortdoc
            :bind ("<help> D" . shortdoc-display-group))
          
        • Re Builder
          (use-package re-builder
            :custom
            (reb-re-syntax 'string))
          
        • Comp

          For native compilation feature introduced at Emacs 28.1.

          (use-package comp
            :init
            ;; enable asynchronous native compilation
            (setq native-comp-jit-compilation t)
            :custom
            (native-comp-async-report-warnings-errors 'silent)
            (native-comp-async-query-on-exit t)
            (native-comp-verbose 1))
          
        • Eldoc
          (use-package eldoc
            :preface
            (setq eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly)
            :custom
            (eldoc-echo-area-prefer-doc-buffer 'maybe)
            :config
            (eldoc-add-command-completions "paredit-"))
          
      • Debug
        (setopt message-log-max 10000)
        
      • Development
        • Extensions
          • Transient
            "emacs-transient"
            
            (use-package transient
              :custom
              (transient-save-history t)
              ;; :config
              ;; (mapc (lambda (face) (set-face-attribute face nil
              ;;                                      :font "fontset-code"
              ;;                                      :height (my/normalized-font-size 420)))
              ;;       (seq-filter (lambda (face) (string-match-p "transient-.+" (symbol-name face)))
              ;;                   (face-list)))
              )
            
    • Help
      (setopt help-enable-variable-value-editing t)
      (setopt help-enable-symbol-autoload t)
      (setopt describe-bindings-outline t)
      
    • Data
      • Save Place

        File-related tweaks including Customizing Saving of Files.

        (require 'saveplace)
        
        (setopt save-place-abbreviate-file-names t
                save-place-version-control t
                save-place-mode t)
        
      • Compression
        • Jka Compr (auto compression mode)

          (setopt auto-compression-mode t)
          
      • Tar
        (require 'tar-mode)
        
      • Archive
        (require 'archive-mode)
        
    • Editing Basics
      (setopt set-mark-command-repeat-pop t
              mark-ring-max 64
              next-line-add-newlines nil
              cycle-spacing-actions '(delete-all-space just-one-space restore))
      
      ;; delsel
      ;;; inserting Replacing the string in the region with the typed character
      ;;; instead of simply inserting it.
      (setopt delete-selection-mode t)
      
      ;; files
      (setopt mode-require-final-newline 'visit-save
              enable-remote-dir-locals t)
      (setq delete-auto-save-files t)
      
    • External
      • EasyPG
        (use-package epg
          :defer t
          :custom
          (epg-pinentry-mode 'loopback))
        
        • Epa (EasyPG Assistant)

          EasyPG Assistant (epa) enables users to manage their GnuPG keys and exert encryption/sign with them.

          (use-package epa
            :bind (("C-x : l" . epa-list-keys)
                   ("C-x : L" . epa-list-secret-keys)
                   :map dired-mode-map
                   (": E" . my/epa-dired-do-encrypt-armor))
            :preface
            (defun my/epa-dired-do-encrypt-armor ()
              "Encrypt mark files in ASCII armored format."
              (interactive)
              (let ((epa-armor t))
                (epa-dired-do-encrypt)))
            :config
            (setq epa-file-encrypt-to "<<email>>"))
          
      • Server
        (use-package server
          :hook (after-init . (lambda () (unless (server-running-p)
                                       (server-start))))
          :custom
          (server-name "server-<<hash-string(seed="server", len=6)>>")
          (server-client-instructions t))
        
      • Processes
        • Proced

          This blog post explains how to use proced, process monitoring package for emacs.

          (use-package proced
            :commands proced
            :custom
            (proced-auto-update-flag t)
            (proced-auto-update-interval 3)
            (proced-show-remote-processes t)
            (proced-show-remote-processes t))
          
        • Shell
          (setopt shell-command-prompt-show-cwd t)
          
      • Browse Url
        (use-package browse-url
          :init
          (keymap-global-set "C-c C-o" #'browse-url-at-point)
          :custom
          (browse-url-new-window-flag t)
          (browse-url-browser-function 'eww-browse-url)
          (browse-url-secondary-browser-function 'browse-url-firefox))
        
        • Webjump
          (use-package webjump
            :commands webjump
            :custom
            (webjump-use-internal-browser t))
          
      • locate
        (use-package locate
          :commands locate
          :custom
          (locate-command "plocate")
          (locate-make-command-line #'my/plocate-make-command-line)
          (locate-fcodes-file (expand-file-name "<<plocate-db()>>"))
          (locate-update-path (expand-file-name "~/")))
        
        (defun my/plocate-make-command-line (search-string)
          (list locate-command "-d" (expand-file-name "<<plocate-db()>>") "--ignore-case" "--existing" "--regexp" search-string))
        
      • With Editor
        (use-package with-editor
          :custom
          (shell-command-with-editor-mode t))
        
    • Applications
      • Mail
        • Smtpmail
          (use-package smtpmail
            :after mu4e
            :init
            (setq smtpmail-default-smtp-server "<<smtp-host>>")
            :custom
            (smtpmail-smtp-service 465)
            (smtpmail-smtp-user user-mail-address)
            (smtpmail-stream-type 'ssl)
            (smtpmail-mail-address user-mail-address))
          
        • Message
          • Message Mail
            (use-package message
              :after (dired mu4e)
              :custom
              (message-send-mail-function 'smtpmail-send-it))
            
        • Mime Security
          (use-package mm-encode
            :after mu4e
            :custom
            (mm-sign-option nil))
          
          (use-package mml-sec
            :after (mu4e dired)
            :custom
            (mml-secure-openpgp-sign-with-sender t)
            (mml-default-sign-method 'pgpmime))
          
      • Ispell/FlySpell
        (use-package ispell
          :if (executable-find "aspell")
          :custom
          (ispell-program-name "aspell")
          :config
          ;; avoid checking for Japanese characters
          (add-to-list 'ispell-skip-region-alist '("[^\000-\377]+"))
          (setq-default ispell-extra-args '("--sug-mode=ultra"
                                            "--lang=en_US"))
          (let ((arg "--camel-case"))
            (and (string-match-p arg
                                 (shell-command-to-string (concat ispell-program-name " --help")))
                 (push arg ispell-extra-args))))
        
        (use-package flyspell
          :after ispell
          :hook ((text-mode . (lambda ()
                                (unless (derived-mode-p 'org-mode)
                                  (flyspell-mode 1))))
                 (prog-mode . flyspell-prog-mode))
          :preface (setq flyspell-mode-map nil)
          :bind (:map flyspell-mode-map
                      :prefix "C-$"
                      :prefix-map my/flyspell-mode-map
                      :prefix-docstring "Keymap for Flyspell"
                      ("n" . flyspell-goto-next-error)
                      ("C-$" . flyspell-auto-correct-word)
                      ("C-" . flyspell-auto-correct-previous-word)
                      ("$" . flyspell-correct-word-before-point))
          :custom
          (flyspell-issue-message-flag nil))
        
      • News
        • Gnus
          • Auth Source
            (use-package auth-source
              :custom
              (auth-source-gpg-encrypt-to `(,user-mail-address))
              :config
              (add-to-list 'auth-sources "~/.netrc.gpg"))
            
            (use-package auth-source-pass
              :config
              (auth-source-pass-enable))
            
      • Calc (The GNU Emacs Calculator)
        (use-package calc
          :bind ("<f7>" . calc)
          :commands (calc))
        
      • Calendar
        (use-package calendar
          :commands (calendar)
          :hook
          (calendar-today-visible . calendar-mark-today)
          :custom
          (calendar-left-margin 0)
          (calendar-right-margin 0)
          (calendar-intermonth-spacing 1)
          (calendar-mark-holidays-flag t)
          ;; Below is lat/lon for Japna used in sunrise-sunset command
          (calendar-latitude 36.2)
          (calendar-longitude 138.3)
          :custom-face
          (holiday ((t (:foreground "dark orange" :box t)))))
        
        • Midnight
          (use-package midnight
            :defer t
            :custom
            (midnight-mode t)
            (midnight-delay "4:00"))
          
      • Package
        (use-package package
          :custom
          (package-native-compile t)
          (package-install-upgrade-built-in t)
          (package-quickstart t)
          :config
          (with-eval-after-load 'my-launch-app
            (keymap-set my/invoke-list-command-map (kbd "p")
                        #'list-packages)))
        
      • Eglot

        Eglot

        (use-package eglot
          :disabled t
          :hook ((sh-mode ruby-mode python-mode graphviz-dot-mode) . eglot-ensure)
          :custom
          (eglot-extend-to-xref t))
        
    • Text
      • View
        (use-package view
          :defer t)
        
    • Hypermedia
      • Dictionary
        dictd dict \
              dict-gcide dict-wn \
              dict-jargon dict-foldoc dict-vera \
              dict-freedict-eng-jpn dict-freedict-jpn-eng
        
        sudo systemctl start dictd
        
        (use-package dictionary
          :commands (dictionary-search dictionary-lookup-definition)
          :custom
          (dictionary-use-single-buffer t)
          (dictionary-server nil)
          :config
          (setq switch-to-buffer-obey-display-actions t)
          (add-to-list 'display-buffer-alist
                       '("^\\*Dictionary\\*"
                         display-buffer-in-tab)))
        
    • Communication
      • Net Utils
        (use-package net-utils
          :after my-launch-app
          :bind (:map my/launch-app-map
                      ("n d" . run-dig)
                      ("n i" . ifconfig)
                      ("n w" . iwconfig)
                      ("n p" . ping)))
        
  • Input Methods
    • ddskk
      "emacs-ddskk"
      
      ("https://github.com/skk-dev/ddskk/releases.atom" soft_update)
      
      (defvar skk-dir (expand-file-name "skk" "<<share-dir()>>"))
      
      (use-package skk
        :defer t
        :init
        (setopt default-input-method "japanese-skk")
        (defface skk-candidate `((t . (:font "fontset-default"
                                             :height ,(my/normalized-font-size 860))))
          "Default face for ddskk candidates."
          :group 'skk-dcomp)
        (setq skk-get-jisyo-directory "<<skk-jisyo-path()>>")
        :custom
        (skk-user-directory (no-littering-expand-etc-file-name "ddskk"))
        (skk-init-file (expand-file-name "ddskk-init.el" skk-user-directory))
        (skk-byte-compile-init-file t)
        ;; cursor color
        (skk-use-color-cursor t)
        (skk-cursor-hiragana-color "orange")
        (skk-cursor-katakana-color "OrangeRed3")
        (skk-cursor-latin-color "DodgerBlue3")
        (skk-cursor-jisx0201-color "purple3")
        ;; mode line string
        (skk-latin-mode-string "A")
        (skk-hiragana-mode-string "あ")
        (skk-katakana-mode-string "ア")
        (skk-jisx0201-mode-string "ア")
        (skk-jisx0208-latin-mode-string "A")
        ;; AZIK
        (skk-use-azik t)
        (skk-azik-keyboard-type 'us101)
        ;; conversion
        (skk-egg-like-newline t)
        (skk-henkan-strict-okuri-precedence t)
        (skk-check-okurigana-on-touroku t)
        ;; annotation
        (skk-show-annotation t)
        (skk-annotation-delay 0.3)
        ;; how candidates behave
        (skk-show-candidates-always-pop-to-buffer t)
        (skk-henkan-number-to-display-candidates 10)
        (skk-show-candidates-nth-henkan-char 3)
        (skk-henkan-show-candidates-keys
         '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?0))
        ;; set face for candidates list
        (skk-treat-candidate-appearance-function
         (lambda (candidate listing-p)
           (cond
            ((string-match ";" candidate)
             (put-text-property 0 (match-beginning 0)
                                'face 'skk-candidate
                                candidate)
             (put-text-property (match-beginning 0)
                                (length candidate) 'face 'shadow candidate))
            (t
             (put-text-property 0 (length candidate)
                                'face 'skk-candidate
                                candidate)))
           candidate))
        ;; bind C-q for hankaku-kana input mode
        (skk-use-jisx0201-input-method t)
        ;; dynamic conversion
        (skk-dcomp-activate nil)
        (skk-dcomp-multiple-activate nil)
        ;; config file
        (skk-record-file (expand-file-name "record" skk-dir))
        (skk-emacs-id-file (expand-file-name "emacs-id" skk-dir))
        ;; jisyo
        (skk-share-private-jisyo t)
        (skk-compare-jisyo-size-when-saving t)
        (skk-save-jisyo-instantly t)
        ;; jisyo file/directory
        (skk-jisyo `(,(no-littering-expand-var-file-name "ddskk/jisyo.txt") . utf-8))
        (skk-backup-jisyo (expand-file-name "jisyo.bak" skk-dir))
        (skk-large-jisyo (expand-file-name "SKK-JISYO.L" skk-get-jisyo-directory))
        (skk-itaiji-jisyo (expand-file-name "SKK-JISYO.itaiji" skk-get-jisyo-directory))
        (skk-extra-jisyo-file-list
         (seq-remove (lambda (dic)
                       (seq-some (lambda (suffix)
                                   (string-suffix-p (symbol-name suffix) dic))
                                 '(L itaiji tar)))
                     (append (file-expand-wildcards (expand-file-name "SKK-JISYO.*" skk-get-jisyo-directory))
                             (file-expand-wildcards (expand-file-name "open-jisyo/SKK-JISYO.*" skk-dir)))))
        ;; jisyo server
        ;; (skk-server-host "localhost")
        ;; (skk-server-portnum 1178)
        ;; (skk-server-inhibit-startup-server t)
        ;; study
        (skk-study-file (expand-file-name "study" skk-dir))
        (skk-study-backup-file (expand-file-name "study.bak" skk-dir))
        :config
        ;; ward off activating skk-auto-fill-mode inadvertently
        (bind-keys ("C-x j" . skk-mode)))
      
      • ddskk init file
      ;; -*- mode:emacs-lisp; -*-
      (setq skk-rom-kana-rule-list
            (append skk-rom-kana-rule-list
                    '(("!" nil skk-purge-from-jisyo)
                      ("xka" nil ("ヵ" . "ヵ"))
                      ("xke" nil ("ヶ" . "ヶ"))
                      ("n" nil nil)
                      ("nn" nil ("ナノ" . "なの"))
                      ("nm" nil ("ノミ" . "のみ"))
                      ("ks" nil ("コソ" . "こそ"))
                      ("kna" nil ("カナ" . "かな"))
                      ("kno" nil ("コノ" . "この"))
                      ("ym" nil ("ヤマ" . "やま"))
                      ("yk" nil ("ユキ" . "ゆき"))
                      ("tga" nil ("タガ" . "たが"))
                      ("vj" nil ("ヴン" . "ぶん"))
                      ("hm" nil ("ハマ" . "はま"))
                      ;; followings are for preventing from changing to zenkaku eisu mode by pressing 'L'
                      ("bL" nil ("ボン" . "ぼん"))
                      ("byL" nil ("ビョン" . "びょん"))
                      ("cL" nil ("チョン" . "ちょん"))
                      ("dL" nil ("ドン" . "どん"))
                      ("fL" nil ("フォン" . "ふぉん"))
                      ("gL" nil ("ゴン" . "ごん"))
                      ("gyL" nil ("ギョン" . "ぎょん"))
                      ("hL" nil ("ホン" . "ほん"))
                      ("hgL" nil ("ヒョン" . "ひょん"))
                      ("hyL" nil ("ヒョン" . "ひょん"))
                      ("jL" nil ("ジョン" . "じょん"))
                      ("kL" nil ("コン" . "こん"))
                      ("kgL" nil ("キョン" . "きょん"))
                      ("kyL" nil ("キョン" . "きょん"))
                      ("mL" nil ("モン" . "もん"))
                      ("mgL" nil ("ミョン" . "みょん"))
                      ("myL" nil ("ミョン" . "みょん"))
                      ("nL" nil ("ノン" . "のん"))
                      ("ngL" nil ("ニョン" . "にょん"))
                      ("nyL" nil ("ニョン" . "にょん"))
                      ("pL" nil ("ポン" . "ぽん"))
                      ("pgL" nil ("ピョン" . "ぴょん"))
                      ("pyL" nil ("ピョン" . "ぴょん"))
                      ("rL" nil ("ロン" . "ろん"))
                      ("ryL" nil ("リョン" . "りょん"))
                      ("sL" nil ("ソン" . "そん"))
                      ("syL" nil ("ション" . "しょん"))
                      ("tL" nil ("トン" . "とん"))
                      ("tyL" nil ("チョン" . "ちょん"))
                      ("vL" nil ("ヴォン" . "う゛ぉん"))
                      ("wL" nil ("ウォン" . "うぉん"))
                      ("xL" nil ("ション" . "しょん"))
                      ("xxL" nil ("→" . "→"))
                      ("yL" nil ("ヨン" . "よん"))
                      ("zL" nil ("ゾン" . "ぞん"))
                      ("zyL" nil ("ジョン" . "じょん")))))
      
      (add-hook 'skk-azik-load-hook
                (lambda ()
                  (dolist (key '("kA" "kE" "tU" "wA"))
                    (setq skk-rom-kana-rule-list
                          (skk-del-alist key skk-rom-kana-rule-list)))))
      
  • Dired
    (use-package dired
      :bind (:map dired-mode-map
                  ("C-j" . dired-find-file)
                  ("^" . dired-up-directory)
                  ("(" . dired-hide-details-mode)
                  (")" . dired-hide-details-mode)
                  ("E" . dired-create-empty-file)
                  ("Y" . dired-do-relsymlink)
                  ("e" . wdired-change-to-wdired-mode)
                  ("RET" . dired-open-file)
                  ("C-c C-o" . dired-open-file)
                  ("C-c C-s" . my/dired-share))
      :hook (dired-mode . dired-hide-details-mode)
      :custom
      (dired-kill-when-opening-new-dired-buffer t)
      (dired-do-revert-buffer t)
      (dired-auto-revert-buffer t)
      (dired-copy-dereference t)
      (dired-recursive-copies 'always)
      (dired-recursive-deletes 'top)
      (dired-listing-switches "-ahgG --time-style=long-iso --group-directories-first")
      (dired-dwim-target 'dired-dwim-target-next)
      (dired-hide-details-hide-information-lines nil)
      (dired-hide-details-hide-symlink-targets nil)
      (dired-compress-file-default-suffix ".zst")
      (dired-compress-directory-default-suffix ".7z")
      (dired-isearch-filenames t)
      (dired-open-use-nohup t)
      (dired-open-query-before-exit nil)
      (completion-ignored-extensions nil)
      :config
      (with-eval-after-load 'open-file
        (setq dired-guess-shell-alist-user
              `((,(regexp-opt (append my/open-file-media-extensions
                                      my/open-file-compressed-media-extensions)
                              "\\.\\(")
                 "mpv")
                (,(regexp-quote ".kdenlive") "kdenlive")
                (,(regexp-quote ".xcf") "gimp"))))
      (advice-add #'dired-do-delete :around #'my/advice-dired-control-deletion)
      (advice-add #'dired-do-flagged-delete :around #'my/advice-dired-control-deletion)
      (put 'dired-find-alternate-file 'disabled nil))
    
    (use-package dired-x
      :after dired
      :config
      (lambda-key dired-mode-map "."
                  (lambda ()
                    "Toggle hiding dotfiles whose name starts with '.'"
                    (interactive)
                    (let ((dired-omit-files (rx (seq string-start
                                                     (or "." ".."
                                                         (seq "." (+? graph)))
                                                     string-end))))
                      (call-interactively #'dired-omit-mode)))))
    
    (use-package dired-aux
      :after dired
      :custom
      ;; Renaming files in Dired reflects their name in VCSs
      (dired-vc-rename-file t)
      :config
      (setq dired-compress-files-alist
            (append dired-compress-files-alist
                    '(("\\.tar\\.7z\\'" . "tar cf - %i | 7z a -si %o")
                      ("\\.7z\\'" . "7z a %o %i"))))
      (add-to-list 'dired-compress-file-suffixes
                   '("\\.tar\\.7z\\'" "" "7z x -so %i | tar xf -"))
      ;; submit "M-s f" to consult-find
      (bind-keys :map dired-mode-map
                 ("M-s f" . nil)))
    
    (defun my/advice-dired-control-deletion (oldfun &rest r)
      "Enable file deleting functions to control deleting procedure
    whether files are going to be in trash box."
      (let ((delete-by-moving-to-trash
             (if (equal current-prefix-arg '(4))
                 nil t)))
        (apply oldfun (cdr r))))
    
    (defun my/dired-share ()
      "Share file with remote device via KDE Connect."
      (interactive)
      (let ((files (dired-get-marked-files nil nil)))
        (mapc (lambda (file)
                (shell-command
                 (mapconcat 'identity
                            (list "kdeconnect-cli" "-d" "a30587ededf4c2d2"
                                  "--share" (shell-quote-argument
                                             file)) " ")))
              files)))
    
    • dired-rsync
      "emacs-dired-rsync"
      
      (use-package dired-rsync
        :after dired
        :bind (:map dired-mode-map
                    ("C-c C-r" . dired-rsync))
        :custom
        (dired-rsync-options "-auz --info=progress2"))
      
    • dired-single   ARCHIVE
    • dired-hacks
      "emacs-dired-hacks"
      
      (require 'dired-open)
      
      (use-package dired-subtree
        :after dired
        :bind (:map dired-mode-map
                    ("TAB" . dired-subtree-cycle)))
      
      (use-package dired-narrow
        :after dired
        :bind
        (:map dired-mode-map
              ("z" . dired-narrow))
        (:map dired-narrow-map
              ("C-j" . exit-minibuffer)))
      
    • dired-hide-dotfiles   ARCHIVE
    • find-dired
      (use-package find-dired
        :bind (:prefix "M-s d"
                       :prefix-map my/find-dired-map
                       :prefix-docstring "Keymap for FInd Dired"
                       ("f" . find-dired)
                       ("F" . find-lisp-find-dired)
                       ("g" . find-grep-dired)
                       ("n" . find-name-dired)
                       ("d" . find-lisp-find-dired-subdirectories)
                       ("!" . find-dired-with-command))
        :custom
        (find-grep-options "-n -H --no-heading -q")
        (find-ls-option '("-print0 | xargs -0 ls -ldN" . "-ldN")))
      
  • EWW
    (funcall (if (= full-path 0) #'identity #'expand-file-name)
             (file-name-concat no-littering-var-directory "eww/org"))
    
    d <<eww-org-dir(full-path=1)>> - - - 3d
    
    (with-eval-after-load 'shr
      (setopt shr-width 10000)
      (setopt shr-use-fonts nil)
      (setopt shr-image-animate t)
      (setopt shr-use-colors nil)
      (setopt shr-max-image-proportion 0.4)
      ;; never use cookies
      (setopt shr-cookie-policy nil))
    

    The EWW bookmark functionality is an original implementation, separate from the emacs bookmark facility. Although adoption of the facility has been considered, a possible solution to count EWW bookmarks in the source of consult-buffer is showcased at here.

    (use-package eww
      :commands eww
      :after my-launch-app
      :bind (:map my/invoke-list-command-map
                  ("B" . eww-list-bookmarks)
                  :map my/launch-app-map
                  ("W" . eww-switch-to-buffer))
      :custom
      ;; set enough large column number to prevent from inserting line break
      (eww-header-line-format nil)
      (eww-browse-url-new-window-is-tab nil)
      (eww-auto-rename-buffer 'title)
      (eww-buffer-name-length 20)
      :config
      (bind-keys :map eww-mode-map
                 ("C" . eww-set-character-encoding)
                 ("C-j" . eww-follow-link)
                 ("T" . eww-goto-title-heading)
                 ("L" . my/eww-goto-heading)
                 ("O" . my/eww-view-in-org)
                 :map eww-bookmark-mode-map
                 ("C-j" . eww-bookmark-browse))
      (with-eval-after-load 'consult
        (defvar consult--source-eww
          (list :name     "Eww"
                :narrow   ?w
                :action   (lambda (bm)
                            (eww-browse-url (get-text-property 0 'url bm)))
                :items    (lambda ()
                            (eww-read-bookmarks)
                            (mapcar (lambda (bm)
                                      (propertize
                                       (format "%s (%s)"
                                               (plist-get bm :url)
                                               (plist-get bm :title))
                                       'url (plist-get bm :url)))
                                    eww-bookmarks))))
        (add-to-list 'consult-buffer-sources 'consult--source-eww 'append)))
    
    (defun my/eww-view-in-org ()
      "Convert current html page into one org file and show it.
    
    This is handy if you would like to check a page index comfortably."
      (interactive)
      (let ((source (plist-get eww-data :source))
            (src-html (make-temp-file "source-"))
            (dst-org (expand-file-name
                      (format "<<eww-org-dir()>>/%s.org" (org-id-uuid))))
            (coding-system-for-write 'utf-8-unix)
            (org-startup-folded t))
        (with-temp-buffer
          (insert source)
          (make-directory
           (file-name-directory (directory-file-name dst-org)) t)
          (write-region (point-min) (point-max) src-html nil)
          (call-process-shell-command (format "pandoc %s -f html -t org -o %s"
                                              (shell-quote-argument src-html)
                                              (shell-quote-argument dst-org)))
          (delete-file src-html))
        (find-file dst-org t)
        (goto-char (point-min))
        (org-ctrl-c-tab)))
    
    (defun eww-headings-dom ()
      "Return heading list as a dom from xml."
      (let ((source (plist-get eww-data :source))
            (dom nil))
        (with-temp-buffer
          (let ((source-file (make-temp-file "source-"))
                (coding-system-for-write 'utf-8-unix))
            (insert source)
            (write-region (point-min) (point-max) source-file nil)
            (erase-buffer)
            (call-process "extract_headings" source-file t)
            (delete-file source-file)
            (libxml-parse-xml-region (point-min) (point-max))))))
    
    (defun eww-goto-title-heading ()
      "Set point to a line which contaings the possible heading."
      (interactive)
      (when-let* ((headings-dom (eww-headings-dom))
                  (possible-heading (cl-reduce (lambda (node-a node-b)
                                                 (if (not (bound-and-true-p node-a))
                                                     (if (not (bound-and-true-p node-b))
                                                         nil
                                                       node-b)
                                                   (if (>= (string-to-number (dom-attr node-a 'proximity))
                                                           (string-to-number (dom-attr node-b 'proximity)))
                                                       node-a node-b)))
                                               (dom-children headings-dom)
                                               :initial-value nil))
                  (possible-text (dom-text possible-heading))
                  (match-pos (or (re-search-forward (format "^*?[[:blank:]]*%s[[:blank:]]*$" (regexp-quote possible-text)) nil t 1)
                                 (re-search-backward (format "^*?[[:blank:]]*%s[[:blank:]]*$" (regexp-quote possible-text)) nil t 1))))
        (beginning-of-line)
        (recenter-top-bottom 0)))
    
    (defun my/eww-goto-heading ()
      "Go to selected heading line."
      (interactive)
      (setq lexical-binding t)
      (when-let* ((headings-root (eww-headings-dom))
                  (cur-buf (current-buffer))
                  (heading (completing-read
                            "Heading : "
                            (mapcar (lambda (heading-node)
                                      (when-let* ((heading (dom-text heading-node))
                                                  (tag (symbol-name (dom-tag heading-node)))
                                                  (match-pos (string-match "h\\([1-6]\\{1\\}\\)" tag))
                                                  (indent (- (string-to-number (match-string 1 tag)) 1)))
                                        (format "%s%s"
                                                (apply 'concat (make-list indent "  "))
                                                heading)))
                                    (dom-children headings-root))))
                  (match (string-match "\\(?:  \\)*\\(.*\\)" heading))
                  (heading (match-string 1 heading))
                  (match-pos (or (re-search-forward (build-regex heading) nil t 1)
                                 (re-search-backward (build-regex heading) nil t 1))))
        (with-current-buffer cur-buf
          (switch-to-buffer cur-buf)
          (beginning-of-line)
          (recenter-top-bottom 0))))
    
    (defmacro build-regex (str)
      "Return a regexp representation for `STR'."
      `(format "^[[:blank:]SVG Image]*%s[[:blank:]]*$"
               ,str))
    
    • extractheadings
      import sys
      import lxml.html as html
      from lxml import etree
      import difflib
      
      if len(sys.argv) == 1:
          INPUT_STR = sys.stdin.read()
          ROOT = html.fromstring(INPUT_STR).getroottree()
      else:
          sys.exit(1)
      
      OUT_ROOT = etree.Element("headings")
      
      # extract title text
      title = ''
      title_text_arr = ROOT.xpath('//title[1]//text()')
      if len(title_text_arr):
          title = title_text_arr[0]
          OUT_ROOT.set("title", title.strip())
      
      htag_text_arr = ROOT.xpath('//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]')
      for i in range(len(htag_text_arr)):
          text = ''
          texts = htag_text_arr[i].xpath('.//text()')
          if len(texts) == 1:
              text = texts[0].strip()
          elif len(texts) > 1:
              text = ''.join(map(str.strip, texts))
          else:
              continue
          proximity = difflib.SequenceMatcher(None, title, text).ratio()
          heading = etree.Element(htag_text_arr[i].tag)
          heading.text = text
          heading.set('proximity', str(proximity))
          OUT_ROOT.append(heading)
      
      out_root_str = etree.tostring(OUT_ROOT, encoding='utf-8', method='xml', pretty_print=True)
      if type(out_root_str) is bytes:
          out_root_str = out_root_str.decode()
          print(out_root_str)
      
  • Info
    (use-package info
      :custom
      (Info-use-header-line nil)
      :config
      (bind-keys :map Info-mode-map ("a" . info-apropos)))
    
  • Magit

    Magit is git front end for emacs which is outstandingly powerful.

    "emacs-magit"
    
    (use-package magit
      :commands (magit magit-status magit-dispatch)
      :config
      (bind-keys :map magit-status-mode-map
                 ("/" . magit-file-checkout)
                 (";" . magit-list-repositories)))
    
    • Magit Essentials
      (use-package magit-repos
        :after magit
        :custom
        (magit-repository-directories '(("~/git" . 1))))
      
    • Magit Revision
      (use-package magit-diff
        :after magit
        :custom
        ;; %GG denotes signature key info botained by --show-signature option in git log command
        (magit-revision-headers-format
         (mapconcat #'identity
                    (list "Author:     %aN <%aE>"
                          "AuthorDate: %ad"
                          "Commit:     %cN <%cE>"
                          "CommitDate: %cd"
                          "%GG")
                    "\n")))
      
    • Magit Log
      (use-package magit-log
        :after magit
        :custom
        (magit-log-show-refname-after-summary t))
      
    • Magit Wip

      The following excerpt is important when trying finer control for git-wip-*-mode.

      • magit-wip-mode

        For historic reasons this mode is implemented on top of four other 'magit-wip-*' modes, which can also be used individually, if you want finer control over when the wip refs are updated; but that is discouraged.

      (use-package magit-wip
        :after magit
        :custom
        (magit-wip-merge-branch t)
        :config
        (magit-wip-after-save-mode 1))
      
    • forge
      "emacs-forge"
      
      (use-package forge
        :after magit)
      
  • Mu4e

    You might need to issue 'mu init' command before initial usage or after upgrading. The command would presumably looks like the following:

    mu init --maildir=~/data/share/mail --muhome=~/data/share/mu --my-address=jim@example.com --my-address=bob@example.com
    
    "mu"
    
    (use-package mu4e
      :defer t
      :hook (after-init . (lambda ()
                            (mu4e t)
                            (mu4e-update-mail-and-index t)))
      :delight
      (mu4e-main-mode    " MU")
      (mu4e-headers-mode " MU")
      (mu4e-view-mode    " MU")
      (mu4e-compose-mode " MU")
      :custom
      (mail-user-agent 'mu4e-user-agent)
      (mu4e-hide-index-messages t)
      (mu4e-context-policy 'pick-first)
      (mu4e-compose-context-policy 'ask)
      (mu4e-update-interval 300)
      (mu4e-split-view 'single-window)
      (mu4e-headers-date-format "%y%m%d")
      (mu4e-headers-time-format " %R")
      (mu4e-headers-fields
       '((:human-date . 6)
         (:flags      . 3)
         (:from       . 16)
         (:subject)))
      (mu4e-search-results-limit 2000)
      (mu4e-headers-advance-after-mark t)
      (mu4e-change-filenames-when-moving t)
      (mu4e-view-show-images t)
      (mu4e-html2text-command (lambda (msg)
                                (plist-put msg :body-html
                                           (with-temp-buffer
                                             (insert (or (mu4e-message-field msg :body-html) ""))
                                             (shell-command-on-region (point-min) (point-max) "nkf -w -Lu" (current-buffer) t)
                                             (or (buffer-string) "")))
                                (mu4e-shr2text msg)))
      :config
      (bind-keys :map mu4e-headers-mode-map
                 ("C-j" . mu4e-headers-view-message)
                 :map mu4e-view-mode-map
                 ("C-j" . push-button)
                 ("C-c C-a" . mu4e-view-attachment-action)
                 :map mu4e-compose-mode-map
                 ("C-x C-o" . org-mu4e-compose-org-mode))
      (add-to-list 'mu4e-view-actions
                   '("external browser" . mu4e-action-view-in-browser) t)
      (add-to-list 'mu4e-view-actions
                   '("XWidget View" . mu4e-action-view-with-xwidget) t)
      (when (fboundp 'imagemagick-register-types)
        (imagemagick-register-types)))
    
    • mu4e-alert   ARCHIVE
    • additional tweaks
      • file attachment in dired (source)

        gnus-dired-attach (C-c RET C-a)
        
        (use-package gnus-dired
          :after mu4e-compose
          :hook
          (dired-mode . turn-on-gnus-dired-mode)
          :custom
          (gnus-dired-mail-mode 'mu4e-user-agent))
        
      • use contact info from org-contacts

        (with-eval-after-load 'mu4e
          (add-to-list 'mu4e-headers-actions
                       '("Contact to add" . mu4e-action-add-org-contact) t)
          (add-to-list 'mu4e-view-actions
                       '("Contact to add" . mu4e-action-add-org-contact) t)
          (with-eval-after-load 'org-contacts
            (setq mu4e-org-contacts-file (car org-contacts-files))))
        
  • Elfeed

    Elfeed is a powerful RSS feed reader with tag capability that is one of core software in my input workflow.

    "emacs-elfeed"
    
    ("https://github.com/skeeto/elfeed/releases.atom" soft_update)
    
    (use-package elfeed
      :delight
      (elfeed-show-mode   " EF")
      (elfeed-search-mode " EF")
      :commands elfeed
      :init
      (defface elfeed-search-unchecked-title-face
        nil
        "Face used in search mode for unchecked entry titles."
        :group 'elfeed)
      (defface elfeed-search-checked-title-face
        nil
        "Face used in search mode for checked entry titles."
        :group 'elfeed)
      :custom-face
      (elfeed-search-title-face ((t (:foreground "#4D4D4D"))))
      (elfeed-search-unchecked-title-face ((t (:foreground "cornflowerblue"))))
      (elfeed-search-checked-title-face ((t (:foreground "darkblue"))))
      :custom
      (elfeed-use-curl t)
      (elfeed-search-date-format '("%Y%m%d" 8 :left))
      (elfeed-search-title-min-width 100)
      (elfeed-search-title-max-width 120)
      (elfeed-search-trailing-width 160)
      (elfeed-sort-order 'ascending)
      (elfeed-enclosure-default-dir "~/Downloads/")
      (elfeed-save-multiple-enclosures-without-asking t)
      :config
      (defalias 'elfeed-search-tag-all-unchecked
        (elfeed-expose #'elfeed-search-tag-all 'unchecked)
        "Add the `unchecked' tag to all selected entries.")
      (defalias 'elfeed-search-untag-all-unchecked
        (elfeed-expose #'elfeed-search-untag-all 'unchecked)
        "Remove the `unchecked' tag from all selected entries.")
      (defalias 'elfeed-search-tag-all-checked
        (elfeed-expose #'elfeed-search-tag-all 'checked)
        "Add the `checked' tag to all selected entries.")
      (defalias 'elfeed-search-untag-all-checked
        (elfeed-expose #'elfeed-search-untag-all 'checked)
        "Remove the `checked' tag from all selected entries.")
      (bind-keys :map elfeed-search-mode-map
                 ("C-j" . elfeed-search-show-entry)
                 ("M-RET" . elfeed-search-open-url)
                 ("C-c C-o" . elfeed-search-open-url)
                 ("f" . scroll-up-line)
                 ("e" . scroll-down-line)
                 ("q" . elfeed-kill-buffer)
                 ("Q" . quit-window)
                 ("x" . elfeed-search-update--force)
                 ("c" . (lambda () (interactive)
                          (elfeed-search-untag-all-unread)
                          (unless (use-region-p) (forward-line -1))
                          (elfeed-search-tag-all-unchecked)))
                 ("C" . elfeed-search-untag-all-unchecked)
                 ("r" . (lambda () (interactive)
                          (elfeed-search-untag-all-unread)
                          (unless (use-region-p) (forward-line -1))
                          (elfeed-search-untag-all-unchecked)
                          (unless (use-region-p) (forward-line -1))
                          (elfeed-search-tag-all-checked)
                          (unless (use-region-p) (forward-line -1))
                          (elfeed-search-tag-all (intern (format-time-string "%Y%m%d")))))
                 ("R" . elfeed-search-untag-all-checked)
                 ("d" . elfeed-search-untag-all-unread)
                 ("Y" . elfeed-search-download-media)
                 ("_" . elfeed-search-show-media-duration))
      (add-to-list 'elfeed-search-face-alist '(unchecked elfeed-search-unchecked-title-face))
      (add-to-list 'elfeed-search-face-alist '(checked elfeed-search-checked-title-face)))
    
    (defun elfeed-search-open-url ()
      "Visit the current entry in your browser using 'eww-browse-url'."
      (interactive)
      (elfeed-search-untag-all-unchecked)
      (unless (use-region-p) (forward-line -1))
      (elfeed-search-tag-all-checked)
      (unless (use-region-p) (forward-line -1))
      (elfeed-search-tag-all (intern (format-time-string "%Y%m%d")))
      (unless (use-region-p) (forward-line -1))
      (elfeed-search-browse-url))
    
    (defun elfeed-search-download-media ()
      "Downlaod video file."
      (interactive)
      (let ((entries (elfeed-search-selected)))
        (cl-loop for entry in entries
                 when (or (caar (elfeed-entry-enclosures entry))
                          (elfeed-entry-link entry))
                 do (let ((title (elfeed-entry-title entry)))
                      (yt-dlp-dispatch-dwim it title)
                      (dispatch-execute-macro)))
        (mapc #'elfeed-search-update-entry entries)
        (unless (or elfeed-search-remain-on-entry (use-region-p))
          (forward-line))))
    
    (defun elfeed-search-show-media-duration ()
      "Show duration of media attached to current entry."
      (interactive)
      (let* ((entry (elfeed-search-selected :single))
             (url (or (caar (elfeed-entry-enclosures entry))
                      (elfeed-entry-link entry))))
        (let ((dispatch-dwim--default-method
               (lambda (comm) (princ
                           (string-trim
                            (shell-command-to-string comm))))))
          (yt-dlp-dispatch-dwim url nil t)
          (dispatch-execute-macro))))
    
    • elfeed-score
      "emacs-elfeed-score"
      
      (use-package elfeed-score
        :after elfeed
        :custom
        (elfeed-score-score-format '("%d " 3 :right))
        (elfeed-score-rule-stats-file
         (no-littering-expand-var-file-name "elfeed/score/stats.el"))
        :config
        (elfeed-score-enable)
        (setq elfeed-search-sort-function #'my/elfeed-score-sort-descending)
        (setq elfeed-search-print-entry-function #'my/elfeed-score-search-print-entry)
        (keymap-set elfeed-search-mode-map "=" #'elfeed-score-map)
        :preface
        (defun my/elfeed-score-sort-descending (a b)
          "Return non-nil if A should sort after B."
          (let ((a-score (elfeed-score-scoring-get-score-from-entry a))
                (b-score (elfeed-score-scoring-get-score-from-entry b)))
            (if (< a-score b-score)
                t
              (let ((a-date (elfeed-entry-date a))
                    (b-date (elfeed-entry-date b)))
                (and (eq a-score b-score) (< a-date b-date))))))
        (defun my/elfeed-score-search-print-entry (entry)
          "Print ENTRY to the buffer with my style."
          (let* ((date (elfeed-search-format-date (elfeed-entry-date entry)))
                 (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
                 (title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
                 (feed (elfeed-entry-feed entry))
                 (feed-title
                  (when feed
                    (or (elfeed-meta feed :title) (elfeed-feed-title feed))))
                 (tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
                 (tags-str (mapconcat
                            (lambda (s) (propertize s 'face 'elfeed-search-tag-face))
                            tags ","))
                 (title-width (- (window-width) 10 elfeed-search-trailing-width))
                 (title-column (elfeed-format-column
                                title (elfeed-clamp
                                       elfeed-search-title-min-width
                                       title-width
                                       elfeed-search-title-max-width)
                                :left))
                 (score
                  (elfeed-score-format-score
                   (elfeed-score-scoring-get-score-from-entry entry))))
            (insert score)
            (when feed-title
              (insert (propertize (elfeed-format-column feed-title 6 :left)
                                  'face 'elfeed-search-feed-face) " "))
            (insert (propertize title-column 'face title-faces 'kbd-help title) " ")
            (insert (propertize date 'face 'elfeed-search-date-face) " ")
            (when tags
              (insert "(" tags-str ")")))))
      
    • elfeed-org

      elfeed-org allow me to express feeds for elfeed in org file.

      "emacs-elfeed-org"
      
      (use-package elfeed-org
        :after (elfeed org)
        :custom
        (rmh-elfeed-org-ignore-tag "ARCHIVE")
        (rmh-elfeed-org-auto-ignore-invalid-feeds nil)
        :config
        (when (require 'denote nil t)
          (setq rmh-elfeed-org-files
                (list (denote-get-path-by-id "20230605T173315")))
          (elfeed-org)))
      

      Packages managed by other than guix (i.e. straight.el or apt) are encouraged to register feed since updating information channel is desirable to exist.

      (advice-add #'rmh-elfeed-org-process
                  :after #'my/elfeed-org-add-feeds)
      
      (defun my/elfeed-org-add-feeds (rmh-elfeed-org-files rmh-elfeed-org-tree-id)
        "Add feeds in addition to feeds in `rmh-elfeed-org-files'.
      
      This function needs to be invoked after `rmh-elfeed-org-process' which clear all feeds."
        (setf elfeed-feeds
              (append elfeed-feeds
                      '(;; s-exps for feeds will be inserted here
                        <<elfeed-feeds>>))))
      
  • Terminal Emulators
    • shell
      (use-package shell
        :delight " SM"
        :commands (shell)
        :custom
        (shell-has-auto-cd t)
        :config
        (bind-keys :map shell-mode-map
                   ("C-j" . comint-send-input)))
      
    • term
      (use-package term
        :disabled t
        :delight
        (term-mode " TR")
        :config
        (bind-keys :map term-mode-map
                   ("C-j" . term-send-input)))
      
    • vterm
      "emacs-vterm"
      "emacs-multi-vterm"
      
      ("https://github.com/akermu/emacs-libvterm/releases.atom" soft_update)
      ("https://github.com/suonlight/multi-vterm/releases.atom" soft_update)
      
      # function/variable used in libvterm/emacs
      vterm_printf(){
        if [ -n "$TMUX" ]; then
          # Tell tmux to pass the escape sequences through
          # (Source: http://permalink.gmane.org/gmane.comp.terminal-emulators.tmux.user/1324)
          printf "\ePtmux;\e\e]%s\007\e\\" "$1"
        elif [ "${TERM%%-*}" = "screen" ]; then
          # GNU screen (screen, screen-256color, screen-256color-bce)
          printf "\eP\e]%s\007\e\\" "$1"
        else
          printf "\e]%s\e\\" "$1"
        fi
      }
      
      vterm_prompt_end(){
        vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
      }
      
      PROMPT_COMMAND='echo -ne "\033]0;${HOSTNAME}:${PWD}\007"'
      
      (use-package vterm
        :commands (vterm multi-vterm multi-vterm-next)
        :bind (:map vterm-mode-map
                    ("C-c M-n" . multi-vterm-next)
                    ("C-c M-p" . multi-vterm-prev))
        :delight " VT"
        :diminish ((vterm-copy-mode . "vc"))
        :custom
        (vterm-min-window-width 55)
        (vterm-buffer-name-string "vterm %s")
        :config
        (require 'multi-vterm)
        (bind-keys :map vterm-mode-map
                   ("C-h" . vterm-send-backspace)
                   ("M-h" . vterm-send-meta-backspace)
                   ("C-y" . vterm-yank)
                   ("M-y" . vterm-yank-pop)
                   ("M-l" . nil)
                   :map vterm-copy-mode-map
                   ("C-j" . vterm-copy-mode-done))
        (advice-add 'consult-yank-from-kill-ring :around #'advice/vterm-yank-pop))
      
      (defun my/multi-vterm (&optional arg)
        "Switch between existing vterm buffers.
      
      Spawn a new vterm buffer if there's no available vterm buffer.
      If command prefix ARG is simgle `C-u', spawn a vterm buffer forcibly regardless of condition."
        (interactive "P")
        (if (equal arg '(4))
            (multi-vterm)
          (multi-vterm-next)))
      
      (defun advice/vterm-yank-pop (orig-fun &rest args)
        (if (eq major-mode 'vterm-mode)
            (let ((inhibit-read-only t)
                  (yank-undo-function (lambda (_start _end) (vterm-undo))))
              (cl-letf (((symbol-function 'insert-for-yank)
                         (lambda (str) (vterm-send-string str t))))
                (apply orig-fun args)))
          (apply orig-fun args)))
      
    • Eshell
      (use-package eshell
        :after my-launch-app
        :commands (eshell)
        :bind (:map my/launch-app-map
                    ("e" . eshell))
        :hook
        (eshell-kill . eshell-command-alert)
        :custom
        (eshell-prompt-function
         #'(lambda ()
             (format "[%s]\n%s"
                     (eshell/basename (eshell/pwd))
                     (if (= (user-uid) 0) "# " "$ "))))
        (eshell-prompt-regexp "[#$] ")
        (eshell-visual-subcommands '(("git" "log" "diff" "show")))
        (eshell-scroll-to-bottom-on-input 'this)
        :config
        (require 'em-prompt)
        (bind-keys :map eshell-prompt-mode-map
                   ("C-h" . delete-backward-char)
                   ("M-h" . backward-kill-word))
        (setq eshell-path-env (getenv "PATH")))
      
      (defun eshell-command-alert (process status)
        "Send `alert' with severity based on STATUS when PROCESS finished."
        (let* ((cmd (process-command process))
               (buffer (process-buffer process))
               (msg (format "%s: %s" (mapconcat 'identity cmd " ")  status)))
          (if (string-prefix-p "finished" status)
              (alert msg :buffer buffer :severity  'normal)
            (alert msg :buffer buffer :severity 'urgent))))
      
      • Eshell Aliases

        Aliases for Eshell are shared with Bash.

        alias v view-file $1
        alias vo view-file-other-window $1
        alias <<print-aliases(lang="plain")>>
        
  • Org Mode
    "emacs-org"
    "tree-sitter-org"
    
    ("https://updates.orgmode.org/feed/releases" soft_update)
    

    Basic settings for built-in Org features. Similar to [BROKEN LINK: id:587bc395-6321-4f59-97e6-6f0b62518b20], all entries obey customize group hierarchy for Org mode.

    (use-package org
      :mode ("\\.org$" . org-mode)
      :init
      (defvar my/org-initialized nil
        "Indicator showing whether any org file had opened.")
      (defvar my/org-mode-init-hook nil
        "One-off hook run when first org file open.")
      (setq org-directory "~/org")
      :hook
      (org-mode . (lambda ()
                    (unless my/org-initialized
                      (setq my/org-initialized t)
                      (run-hooks 'my/org-mode-init-hook)
                      (provide 'my/org-mode-init))))
      :config
      (bind-keys :map org-mode-map
                 ("C-c F" . org-next-block)
                 ("C-c B" . org-previous-block)))
    
    (defvar my/org-todo-keyword--urgent "UG"
      "TODO keyword acronym standing for 'UrGent'")
    (put 'my/org-todo-keyword--urgent 'char ?u)
    (defvar my/org-todo-keyword--todo "TD"
      "TODO keyword acronym standing for 'ToDo'")
    (put 'my/org-todo-keyword--todo 'char ?t)
    (defvar my/org-todo-keyword--next "NX"
      "TODO keyword acronym standing for 'NeXt")
    (put 'my/org-todo-keyword--next 'char ?n)
    (defvar my/org-todo-keyword--in-action "IA"
      "TODO keyword acronym standing for 'In Action")
    (put 'my/org-todo-keyword--in-action 'char ?i)
    (defvar my/org-todo-keyword--someday "OH"
      "TODO keyword acronym standing for 'On Hold'")
    (put 'my/org-todo-keyword--someday 'char ?h)
    (defvar my/org-done-keyword--done "DN"
      "DONE keyword acronym standing for 'DoNe'")
    (put 'my/org-done-keyword--done 'char ?d)
    (defvar my/org-done-keyword--cancel "CX"
      "DONE keyword acronym standing for 'Cancel'")
    (put 'my/org-done-keyword--cancel 'char ?x)
    (defvar my/org-done-keyword--pending "LG"
      "DONE keyword acronym standing for 'Let Go'")
    (put 'my/org-done-keyword--pending 'char ?l)
    
    (defvar my/org-project-tag "project" "A tag for projects")
    
    • org mode keymap
      (with-eval-after-load 'org
        (let ((map org-mode-map))
          (keymap-set map "C-j" #'org-return)
          (keymap-set map "C-M-j" #'org-return-indent)
          (keymap-set map "M-j" #'org-meta-return)
          (keymap-set map "C-S-p" #'org-previous-item)
          (keymap-set map "C-S-n" #'org-next-item)
          (keymap-set map "C-S-u" #'org-up-element)
          (keymap-set map "C-M-d" #'org-down-element)
          (keymap-set map "C-<" #'org-previous-link)
          (keymap-set map "C->" #'org-next-link)
          (keymap-set map "C-c @" #'org-mark-element)
          (keymap-set map "C-c C-SPC" #'org-mark-subtree)
          (keymap-set map "C-c C-a" nil)
          (keymap-set map "C-," nil)
          (keymap-set map "M-h" nil)))
      
    • org core feature setup
      (with-eval-after-load "org-macs"
        (setopt org-sort-function 'org-sort-function-fallback))
      
      • Org Agenda
        (use-package org-agenda
          :after (org my-org-global-map)
          :bind (:map my/org-global-map
                      ("a" . org-agenda))
          :hook
          (org-agenda-mode . (lambda ()
                               ;; TODO: this can be rewritten with `display-buffer'
                               (delete-other-windows)
                               (org-agenda-to-appt t '((category "appt")))))
          :custom
          (org-agenda-files
           `(,(file-name-as-directory
               (file-name-concat org-directory "agenda"))))
          (org-agenda-inhibit-startup t)
          (org-agenda-use-tag-inheritance nil)
          :config
          (bind-keys :map org-agenda-mode-map
                     ("C-j" . org-agenda-switch-to)
                     ("M" . org-agenda-month-view)))
        
        (defun org-agenda-cmp-latest-clock-log (a b)
          "Compare two org entry A and B in terms of clock log.
        
        This function can be used as `org-agenda-cmp-user-defined' in `org-agenda-sorting-strategy'."
          (let* ((marker-a (get-text-property 1 'org-marker a))
                 (time-a (org-get-latest-clock-log-time marker-a))
                 (marker-b (get-text-property 1 'org-marker b))
                 (time-b (org-get-latest-clock-log-time marker-b)))
            (if (time-less-p time-a time-b) -1 +1)))
        
        (defun org-clock-sum-all ()
          "Sum the times for all agenda files."
          (interactive)
          (save-excursion
            (mapc (lambda (file)
                    (with-current-buffer (or (org-find-base-buffer-visiting file)
                                             (find-file-noselect file))
                      (org-clock-sum)
                      (org-clock-sum-today)))
                  (org-agenda-files))))
        
        (defun org-get-latest-clock-log-time (pom)
          "Get the latest clock log time stamp in org entry at POM as a time object.
        
        If entry at POM has no clock log time stamp, this function returns 0."
          (org-with-point-at pom
            (save-excursion
              (setq end-of-subtree (org-end-of-subtree))
              (setq latest-time 0)
              (org-back-to-heading t)
              (org-show-all)
              (while (re-search-forward org-drawer-regexp end-of-subtree t)
                (when (string= (match-string 1) (org-clock-drawer-name))
                  (while (progn
                           (forward-line 1)
                           (when (org-match-line (concat "^[ \t]*" org-clock-string "[ \t]*" org-tsr-regexp-both))
                             (setq match-ts
                                   (if (match-string 3)
                                       (match-string 3) (match-string 1)))
                             (when (time-less-p latest-time
                                                (apply 'encode-time (parse-time-string match-ts)))
                               (setq latest-time (apply 'encode-time (parse-time-string match-ts)))))
                           (not (org-match-line org-clock-drawer-end-re))))))))
          latest-time)
        
        • Org Agenda Custom Commands
          (with-eval-after-load 'org-agenda
            (setopt org-stuck-projects `(,(concat "+" my/org-project-tag)
                                         (,my/org-todo-keyword--todo ,my/org-todo-keyword--next ,my/org-todo-keyword--in-action)
                                         nil ""))
            (setq org-agenda-custom-commands
                  `(("a" "Week-agenda"
                     agenda ""
                     ((org-agenda-skip-function #'my/org-agenda-skip-on-hold-task)))
                    ("l" "Log entries in a week"
                     agenda ""
                     ((org-agenda-span (if (equal current-prefix-arg '(4))
                                           'day 'week))
                      (org-agenda-start-with-log-mode t)
                      (org-agenda-start-with-clockreport-mode t)
                      (org-agenda-include-inactive-timestamps nil)
                      (org-agenda-include-diary t)
                      (org-agenda-sorting-strategy
                       '(time-up
                         deadline-up
                         todo-state-up
                         priority-down))))
                    ("L" "Log entry timeline on today with default org-agenda-prefix-format"
                     agenda ""
                     ((org-agenda-prefix-format (eval (car (get 'org-agenda-prefix-format 'standard-value))))
                      (org-agenda-span (if (equal current-prefix-arg '(4))
                                           'day 'week))
                      (org-agenda-start-with-clockreport-mode t)
                      (org-agenda-start-with-log-mode t)
                      (org-agenda-include-inactive-timestamps nil)
                      (org-agenda-include-diary t)
                      (org-agenda-sorting-strategy
                       '(time-up
                         deadline-up
                         todo-state-up
                         priority-down))))
                    ;; KEEP IN MIND
                    ;; invoking `org-clock-sum-all' is required before showing effort table
                    ("e" . "Effort table")
                    ("ei" "today"
                     ((org-ql-search-block `(or (todo ,my/org-todo-keyword--urgent)
                                                (todo ,my/org-todo-keyword--in-action)
                                                (and (clocked :on today)
                                                     (or (todo) (done))
                                                     (not (habit))
                                                     (not (tags "web"))))
                                           ((org-ql-block-header "Today's task"))))
                     ((org-agenda-overriding-header "Today's Task")
                      (org-overriding-columns-format "%26ITEM(Task) %Effort(Effort){:} %CLOCKSUM_T(Today){:} %CLOCKSUM(Total)")
                      (org-agenda-view-columns-initially t)
                      (org-agenda-sorting-strategy '(todo-state-up priority-down deadline-up))))
                    ("eg" "this week"
                     ((org-ql-search-block `(or (todo ,my/org-todo-keyword--urgent)
                                                (todo ,my/org-todo-keyword--next)
                                                (todo ,my/org-todo-keyword--in-action))
                                           ((org-ql-block-header "This Week's task"))))
                     ((org-agenda-overriding-header "This Week's Task")
                      (org-overriding-columns-format "%26ITEM(Task) %Effort(Effort){:} %CLOCKSUM_T(Today){:} %CLOCKSUM(Total)")
                      (org-agenda-view-columns-initially t)
                      (org-agenda-sorting-strategy '(todo-state-up priority-down deadline-up))))
                    ("ed" "done task"
                     ((org-ql-search-block `(or (todo ,my/org-done-keyword--done)
                                                (todo ,my/org-done-keyword--cancel)
                                                (todo ,my/org-done-keyword--pending))
                                           ((org-ql-block-header "Done task"))))
                     ((org-agenda-overriding-header "Done Task")
                      (org-overriding-columns-format "%26ITEM(Task) %Effort(Effort){:} %CLOCKSUM(Total){:}")
                      (org-agenda-view-columns-initially t)
                      (org-agenda-sorting-strategy '(todo-state-up priority-down deadline-up))))
                    ("i" "Today's agenda"
                     ((todo "Today's agenda"
                            ((org-agenda-sorting-strategy '(priority-up))))
                      (org-ql-search-block `(or (todo ,my/org-todo-keyword--urgent)
                                                (todo ,my/org-todo-keyword--in-action))
                                           ((org-ql-block-header "Today's task")))
                      (org-ql-search-block `(and (planning :on today)
                                                 (not (todo ,my/org-todo-keyword--in-action
                                                            ,my/org-done-keyword--done
                                                            ,my/org-done-keyword--cancel
                                                            ,my/org-done-keyword--pending))
                                                 (not (tags "web"))
                                                 (not (habit))
                                                 (not (todo ,my/org-todo-keyword--someday)))
                                           ((org-ql-block-header "Scheduled/Deadlined on today")))
                      (org-ql-search-block `(and (habit)
                                                 (or (todo ,my/org-todo-keyword--todo)
                                                     (todo ,my/org-todo-keyword--next))
                                                 (scheduled :to today)
                                                 (not (tags "bad_habit"))
                                                 (not (tags-inherited "ARCHIVE")))
                                           ((org-ql-block-header "Habits to take")))
                      (org-ql-search-block `(and (ts-active :on today)
                                                 (not (or (todo ,my/org-todo-keyword--in-action)
                                                          (habit) (done)))
                                                 (not (todo ,my/org-todo-keyword--someday)))
                                           ((org-ql-block-header "Today's common event")))
                      (org-ql-search-block '(and (done)
                                                 (closed :on today))
                                           ((org-ql-block-header "Completed tasks on today")))
                      (org-ql-search-block '(and (habit)
                                                 (last-repeat 0))
                                           ((org-ql-block-header "Completed habits on today"))))
                     ((org-agenda-sorting-strategy
                       '(todo-state-up priority-down deadline-up))))
                    ("n" "This Week's agenda"
                     ((org-ql-search-block `(heading ,(format-time-string "%G-W%V"))
                                           ((org-agenda-files `(,(file-name-concat org-directory "archive" (format-time-string "archive_%Y.org"))))
                                            (org-ql-block-header "This week in datetree archive")))
                      (org-ql-search-block `(or (todo ,my/org-todo-keyword--next))
                                           ((org-ql-block-header "This week's tasks")))
                      (org-ql-search-block `(or (todo ,my/org-todo-keyword--urgent)
                                                (todo ,my/org-todo-keyword--in-action))
                                           ((org-ql-block-header "Today's tasks")))
                      (org-ql-search-block `(and (planning :from 0 :to 6)
                                                 (not (todo ,my/org-todo-keyword--in-action
                                                            ,my/org-done-keyword--done
                                                            ,my/org-done-keyword--cancel
                                                            ,my/org-done-keyword--pending))
                                                 (not (tags "web"))
                                                 (not (todo ,my/org-todo-keyword--someday))
                                                 (not (habit)))
                                           ((org-ql-block-header "Scheduled/Deadlined this week")))
                      (org-ql-search-block `(and (and (ts-active :from 0 :to 6)
                                                      (not (deadline))
                                                      (not (scheduled))
                                                      (not (closed)))
                                                 (not (or (todo ,my/org-todo-keyword--next
                                                                ,my/org-todo-keyword--in-action)
                                                          (done))))
                                           ((org-ql-block-header "This week's common event"))))
                     ((org-agenda-sorting-strategy
                       '(todo-state-up priority-down deadline-up))))
                    ("t" "All tasks"
                     ((org-ql-search-block `(todo ,my/org-todo-keyword--urgent)
                                           ((org-ql-block-header "Urgent task")))
                      (org-ql-search-block `(todo ,my/org-todo-keyword--in-action)
                                           ((org-ql-block-header "Today's task")))
                      (org-ql-search-block `(todo ,my/org-todo-keyword--next)
                                           ((org-ql-block-header "This week's task")))
                      (org-ql-search-block `(and (todo ,my/org-todo-keyword--todo)
                                                 (not (habit)))
                                           ((org-ql-block-header "Remaining task")))))
                    ("d" "Done tasks"
                     ((org-ql-search-block `(and (or (done)
                                                     (and (todo ,my/org-todo-keyword--someday)
                                                          (not (path ,(expand-file-name "agenda/on-holds.org" org-directory)))))
                                                 (not (tags "project")))
                                           ((org-ql-block-header "Done/Canceled/Pending task")))))
                    ("p" "Projects" tags ,(concat "+" my/org-project-tag))
                    ("h" "Habits in consistency graph"
                     agenda ""
                     ((org-agenda-span 'day)
                      (org-agenda-use-time-grid nil)
                      (org-agenda-prefix-format '((agenda . "")))
                      (org-habit-show-all-today t)
                      (org-agenda-sorting-strategy '(scheduled-up))
                      ;; display habits only
                      (org-agenda-skip-function
                       (lambda ()
                         (and (save-excursion
                                (not (org-is-habit-p)))
                              (progn (outline-next-heading) (point)))))))
                    ("H" "show all habits"
                     ((org-ql-search-block '(habit)
                                           ((org-ql-block-header "All Habits"))))
                     ((org-agenda-sorting-strategy '(scheduled-up)))))))
          
        • Org Agenda Daily/Weekly
          (with-eval-after-load 'org-agenda
            (setopt org-agenda-start-on-weekday 1)
            (setopt org-agenda-skip-deadline-if-done t)
            (setopt org-agenda-include-diary t)
            (setopt org-agenda-dim-blocked-tasks nil))
          
        • Org Agenda Line Format
          (with-eval-after-load 'org-agenda
            (setq org-agenda-prefix-format '((agenda   . "%?-12t% s")
                                             (timeline . "  %s")
                                             (todo     . " ")
                                             (tags     . " ")
                                             (search   . " "))))
          
      • Org Capture
        (use-package org-capture
          :after (org my-org-global-map)
          :bind (:map my/org-global-map
                      ("c" . org-capture))
          :hook
          (org-capture-mode . delete-other-windows)
          :custom
          (org-reverse-note-order nil)
          (org-capture-bookmark nil)
          (org-capture-templates
           `(("e" "Entry"
              entry (id "58e12e3e-da50-4089-9d22-565986637121")
              ,(concat "* [heading]\n%a\n"
                       "%(and (length> \"%i\" 0)
        (format \"#+begin_\%1$s\n\%2$s#+end_%1$s\n\"
        \"text\"
        (replace-regexp-in-string \"^*\" \",*\" \"%i\")))"))
             ("t" "Task"
              entry (id "58e12e3e-da50-4089-9d22-565986637121")
              ,(format "* %s %%?\n"
                       my/org-todo-keyword--todo))
             ("p" "Project"
              entry (id "58e12e3e-da50-4089-9d22-565986637121")
              "* %? [/] :project:\n"
              :jump-to-captured t)
             ("M" "Append memo to clocking task"
              item (clock)
              "- %i%?"))))
        
      • Org Startup
        (setopt org-startup-folded 'fold)
        (setopt org-startup-truncated t)
        (setopt org-startup-with-inline-images t)
        (setopt org-cycle-hide-drawer-startup t)
        
        ;; allow _{}/^{} format for sub/super script
        (setopt org-use-sub-superscripts '{})
        
      • Org TODO
        (setopt org-enforce-todo-dependencies t)
        (setopt org-todo-keywords
                `((sequence ,(format "%s(%c)" my/org-todo-keyword--todo
                                     (get 'my/org-todo-keyword--todo 'char))
                            ,(format "%s(%c)" my/org-todo-keyword--urgent
                                     (get 'my/org-todo-keyword--urgent 'char))
                            ,(format "%s(%c)" my/org-todo-keyword--next
                                     (get 'my/org-todo-keyword--next 'char))
                            ,(format "%s(%c)" my/org-todo-keyword--in-action
                                     (get 'my/org-todo-keyword--in-action 'char))
                            ,(format "%s(%c!/!)" my/org-todo-keyword--someday
                                     (get 'my/org-todo-keyword--someday 'char))
                            "|"
                            ,(format "%s(%c!/@)" my/org-done-keyword--done
                                     (get 'my/org-done-keyword--done 'char))
                            ,(format "%s(%c!/@)" my/org-done-keyword--cancel
                                     (get 'my/org-done-keyword--cancel 'char))
                            ,(format "%s(%c!/@)" my/org-done-keyword--pending
                                     (get 'my/org-done-keyword--pending 'char)))))
        (setopt org-todo-keyword-faces
                `((,my/org-todo-keyword--urgent    . "red1")
                  (,my/org-todo-keyword--todo      . "DeepPink1")
                  (,my/org-todo-keyword--next      . "green1")
                  (,my/org-todo-keyword--in-action . "DodgerBlue1")
                  (,my/org-todo-keyword--someday   . "SpringGreen")
                  (,my/org-done-keyword--done      . "gray30")
                  (,my/org-done-keyword--cancel    . "dark gray")
                  (,my/org-done-keyword--pending   . "sea green")))
        (setopt org-log-into-drawer t
                org-log-states-order-reversed t
                org-log-note-clock-out nil
                org-log-done 'time
                org-log-refile 'time
                org-log-reschedule nil
                org-log-redeadline 'time
                org-closed-keep-when-no-todo nil)
        
      • Org Structure
        (use-package org-indent
          :after org
          :bind (:map org-mode-map
                      ("C-c C-M-i" . org-indent-mode))
          :hook (org-mode . org-indent-mode)
          :custom
          (org-startup-indented t)
          (org-bookmark-names-plist nil)
          (org-insert-heading-respect-content t)
          (org-M-RET-may-split-line '((default . nil))))
        
        (use-package org-keys
          :after org
          :custom
          (org-use-speed-commands
           (lambda () (and (looking-at org-outline-regexp) (looking-back "^\**"))))
          :config
          (cl-labels ((os-set (key command)
                        (assoc-delete-all key org-speed-commands)
                        (add-to-list 'org-speed-commands
                                     (cons key command)
                                     t)))
            (add-to-list 'org-speed-commands '("Additional") t)
            (os-set "k" nil)                    ; prevent from inadvertent org tree loss
            (os-set "j" #'org-insert-heading)
            (os-set "P" #'org-set-property)
            (os-set "C" #'org-clone-subtree-with-time-shift)
            (os-set "s" #'org-schedule)
            (os-set "d" #'org-deadline)
            (os-set "N" #'org-toggle-narrow-to-subtree)
            (os-set "$" #'org-archive-subtree)
            (os-set "'" #'ignore)))
        
        • Org Edit Structure
          (use-package org-src
            :after org
            :hook (org-src-mode . (lambda ()
                                    (setq-local auto-save-visited-mode nil)
                                    (setq-local buffer-save-without-query t)
                                    (when (eq major-mode 'text-mode)
                                      (visual-line-mode 1))))
            :custom
            (org-special-ctrl-a/e t)
            (org-src-window-setup 'current-window)
            (org-edit-src-content-indentation 0)
            (org-edit-src-turn-on-auto-save nil)
            (org-edit-src-auto-save-idle-delay 30)
            (org-edit-src-persistent-message nil)
            (org-src-ask-before-returning-to-edit-buffer nil)
            (org-special-ctrl-k t)
            (org-ctrl-k-protect-subtree t)
            (org-src-lang-modes
             (mapc (lambda (override-src-lang)
                     (add-to-list 'org-src-lang-modes override-src-lang))
                   '(("bash"   . bash-ts)
                     ("C"      . c)
                     ("ruby"   . ruby)
                     ("python" . python-ts)
                     ("java"   . java)
                     ("rust"   . rust)
                     ("lua"    . lua)
                     ("css"    . css-ts)
                     ("json"   . json-ts)
                     ("dot"    . graphviz-dot)
                     ("yaml"   . yaml-ts))))
            :config
            (mapc (lambda (temp-cons)
                    (if-let ((place (assoc (car temp-cons) org-structure-template-alist)))
                        (setf (cdr (assoc (car temp-cons) org-structure-template-alist)) (cdr temp-cons))
                      (add-to-list 'org-structure-template-alist temp-cons t)))
                  '(("sh" . "src shell")
                    ("tx" . "src text")
                    ("el" . "src emacs-lisp")
                    ("py" . "src python")))
            (advice-add #'org-edit-src-save
                        :before
                        (lambda (&rest _)
                          (delete-trailing-whitespace))))
          
        • Org Plain lists
          (setopt org-list-demote-modify-bullet
                  '(("-" . "+") ("+" . "-") ("*" . "-")
                    ("1." . "1)") ("1)" . "1.") ("*" . "1."))
                  org-cycle-include-plain-lists 'integrate
                  org-list-use-circular-motion t
                  org-list-allow-alphabetical t)
          
        • Org Archive
          (use-package org-archive
            :defer t
            :custom
            (org-archive-default-command 'org-toggle-archive-tag))
          
          (setopt org-cycle-open-archived-trees t)
          
        • Org Inline Tasks
          (use-package org-inlinetask
            :commands (org-inlinetask-insert-task
                       org-inlinetask-promote
                       org-inlinetask-demote)
            :custom
            (org-inlinetask-default-state my/org-todo-keyword--todo)
            (org-inlinetask-min-level 10))
          
      • Org Tags
        (with-eval-after-load 'org
          (setopt org-use-tag-inheritance org-archive-tag
                  org-tags-column -57
                  org-tags-sort-function #'org-string-collate-greaterp))
        
      • Org Properties
        (setopt org-use-property-inheritance "TIMELIMIT.*"
                org-highest-priority ?A
                org-lowest-priority ?Z
                org-default-priority ?E
                org-global-properties
                '(("Effort_ALL". "0 0:10 0:20 0:30 1:00 1:30 2:00 3:00 4:00 6:00 8:00")))
        
      • Org Refile
        (use-package org-refile
          :after (org denote)
          :commands (org-refile)
          :custom
          (org-refile-targets
           `((org-agenda-files :tag . ,my/org-project-tag)
             (org-agenda-files :tag . "refile")
             (,(cl-remove-if-not (lambda (f)
                                   (member "refile"
                                           (denote-extract-keywords-from-path f)))
                                 (denote-directory-files))
              :tag . "refile"))))
        
      • Org Time
        (setopt org-time-stamp-custom-formats
                '("<%m-%d %a>" . "<%H:%M>"))
        
        • Org Progress
          • Org Habit
            (use-package org-habit
              :after my/org-mode-init
              :custom
              (org-habit-graph-column 32)
              (org-habit-preceding-days 21)
              (org-habit-following-days 14)
              (org-habit-show-habits-only-for-today t))
            
          • Org Clock
            (use-package org-clock
              :after (org my-org-global-map)
              :custom
              (org-clock-into-drawer "CLOCK")
              (org-clock-out-when-done t)
              (org-clock-out-remove-zero-time-clocks t)
              (org-clock-persist t)
              (org-clock-persist-query-resume nil)
              (org-clock-string-limit 20)
              (org-clock-continuously t)
              (org-clock-ask-before-exiting nil)
              :config
              (org-clock-persistence-insinuate)
              (bind-keys :map my/org-global-map
                         ("j" . org-clock-goto)
                         ("I" . org-clock-in)
                         ("O" . org-clock-out)
                         ("X" . org-clock-cancel))
              (with-eval-after-load 'org-keys
                (dolist (elt '(("Clock Commands")
                               ("I" . org-clock-in)
                               ("O" . org-clock-out)
                               ("x" . nil)
                               ("X" . org-clock-cancel)))
                  (add-to-list 'org-speed-commands elt t))))
            
      • Org Appearance
        (setopt org-hide-leading-stars t
                org-hide-emphasis-markers t
                org-pretty-entities t
                org-image-actual-width 100
                org-display-remote-inline-images 'cache)
        
      • Babel
        "plantuml"
        
        (use-package ob-core
          :after org
          :hook (org-babel-after-execute . org-display-inline-images)
          :init
          (cond
           ((string= system-type "gnu/linux")
            (setq org-plantuml-jar-path (expand-file-name "plantuml.jar" "~/.guix-profile/share/java")))
           ((string= system-type "darwin")
            (setq org-plantuml-jar-path "/usr/local/Cellar/plantuml/8041/plantuml.8041.jar")))
          :custom
          (org-babel-load-languages '((emacs-lisp . t)
                                      (lisp       . t)
                                      (C          . t)
                                      (java       . t)
                                      (R          . t)
                                      (shell      . t)
                                      (ruby       . t)
                                      (python     . t)
                                      (scheme     . t)
                                      (lua        . t)
                                      (ledger     . t)
                                      (gnuplot    . t)
                                      (dot        . t)
                                      (plantuml   . t)
                                      (lilypond   . t))))
        
        • Org Babel Tangle

          org-babel-tangle-default-file-mode does not seems to be in effect. I set org-babel-default-header-args:<lang> instead as a wordaround.

          (use-package ob-tangle
            :after ob-core
            :custom
            (org-babel-tangle-default-file-mode (identity #o444))
            (org-babel-tangle-use-relative-file-links nil))
          
      • Org ID
        (use-package org-id
          :after org
          :bind (:map org-mode-map
                      ("C-c i" . org-id-copy))
          :custom
          (org-id-track-globally t)
          (org-id-link-to-org-use-id 'create-if-interactive))
        
      • Org Crypt
        (use-package org-crypt
          :after my/org-mode-init
          :custom
          (org-crypt-key user-mail-address)
          :config
          (bind-keys :map org-mode-map
                     ("C-c M-: e" . org-encrypt-entry)
                     ("C-c M-: E" . org-encrypt-entries)
                     ("C-c M-: d" . org-decrypt-entry)
                     ("C-c M-: D" . org-decrypt-entries))
          (org-crypt-use-before-save-magic)
          (add-to-list 'org-tags-exclude-from-inheritance
                       org-crypt-tag-matcher))
        
      • Org Attach   ARCHIVE
      • Org Link
        (use-package ol
          :after (my-org-global-map my/org-mode-init)
          :bind (:map my/org-global-map
                      ("s" . org-store-link))
          :config
          (org-link-set-parameters "src" :follow #'org-babel-ref-resolve))
        
        • Org Follow Link
          (with-eval-after-load 'ol
            (setf (alist-get 'file org-link-frame-setup) 'find-file)
            (setopt org-link-elisp-confirm-function nil))
          
          (with-eval-after-load 'org-keys
            (setopt org-return-follows-link t))
          
      • Org Export
        • Org Export General
          (use-package ox
            :commands org-export-dispatch
            :custom
            (org-export-with-smart-quotes t)
            (org-export-with-emphasize t)
            (org-export-with-special-strings t)
            (org-export-with-fixed-width t)
            (org-export-with-timestamps t)
            (org-export-preserve-breaks nil)
            (org-export-with-sub-superscripts nil)
            (org-export-with-archived-trees 'headline)
            (org-export-with-author nil)
            (org-export-with-broken-links 'mark)
            (org-export-with-clocks nil)
            (org-export-with-creator nil)
            (org-export-with-drawers '(not "LOGBOOK"))
            (org-export-with-date nil)
            (org-export-with-entities t)
            (org-export-with-email nil)
            (org-export-with-footnotes t)
            (org-export-headline-levels 5)
            (org-export-with-inlinetasks t)
            (org-export-with-section-numbers nil)
            (org-export-with-planning nil)
            (org-export-with-priority nil)
            (org-export-with-properties nil)
            (org-export-with-statistics-cookies t)
            (org-export-with-tags nil)
            (org-export-with-tasks t)
            (org-export-with-latex t)
            (org-export-time-stamp-file nil)
            (org-export-with-title t)
            (org-export-with-toc nil)
            (org-export-with-todo-keywords nil)
            (org-export-with-tables t)
            (org-export-default-language "ja")
            (org-export-dispatch-use-expert-ui nil))
          
        • Org Export HTML
          (use-package ox-html
            :commands (org-html-export-as-html
                       org-html-export-to-html)
            :custom
            (org-html-preamble t)
            (org-html-postamble 'auto)
            (org-html-with-latex t)
            (org-html-container-element "div")
            (org-html-doctype "xhtml-strict"))
          
        • Org Export LaTeX
          (use-package ox-latex
            :commands (org-latex-export-as-latex
                       org-latex-export-to-latex
                       org-latex-export-to-pdf)
            :custom
            (org-latex-pdf-process '("platex %f"
                                     "platex %f"
                                     "bibtex %b"
                                     "platex %f"
                                     "platex %f"
                                     "dvipdfmx %b.dvi"))
            (org-latex-default-class "jsarticle")
            :config
            (add-to-list 'org-latex-classes
                         '("jsarticle"
                           "\\documentclass[dvipdfmx,12pt]{jsarticle}"
                           ("\\section{%s}" . "\\section*{%s}")
                           ("\\subsection{%s}" . "\\subsection*{%s}")
                           ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                           ("\\paragraph{%s}" . "\\paragraph*{%s}")
                           ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
            (add-to-list 'org-latex-classes
                         `("beamer"
                           "\\documentclass[presentation,dvipdfmx,18pt]{beamer}\n"
                           ("\\section{%s}" . "\\section*{%s}")
                           ("\\subsection{%s}" . "\\subsection*{%s}")
                           ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))
          
        • Org Export Texinfo
          (use-package ox-texinfo
            :after ox)
          
    • org 3rd-party packages
      • org-ai   ARCHIVE
      • org-modern
        "emacs-org-modern"
        
        (use-package org-modern
          :after org
          :commands org-modern-mode)
        
      • org-transclusion
        "emacs-org-transclusion"
        
        (use-package org-transclusion
          :after org
          :bind (:map my/org-global-map
                      ("% a" . org-transclusion-add)
                      ("% A" . org-transclusion-add-all)
                      ("% n" . org-transclusion-make-from-link)
                      ("% t" . org-transclusion-activate)))
        
      • org-edna

        Org Edna allows to org entries to be blockers and action triggers, which is very intresting feature especially for managing successive set of tasks or any correlated tasks.

        "emacs-org-edna"
        
        (use-package org-edna
          :after org
          :custom
          (org-edna-mode t))
        
      • org-drill

        org-drill helps me to learn english words and phrases. This video introduces how I use org-drill.

        "emacs-org-drill"
        
        ("https://gitlab.com/phillord/org-drill/-/tags?format=atom" soft_update)
        
        (use-package org-drill
          :after org
          :bind (:map org-mode-map
                      ("C-c D" . org-drill-tree))
          :custom
          (org-drill-spaced-repetition-algorithm 'sm5))
        
      • org-ql

        Org QL enables me to issue queries to search org entries with either SQL-like format or plain search strings.

        "emacs-org-ql"
        
        (use-package org-ql
          :after (org my-org-global-map)
          :bind (:map my/org-global-map
                      ("q q" . org-ql-search)
                      ("q v" . org-ql-view)
                      ("q f f" . org-ql-find)
                      ("q f F" . org-ql-find-in-org-directory)
                      ("q f a" . org-ql-find-in-agenda)
                      ("q f n" . my/org-ql-find-in-notes)
                      ("q /" . org-ql-sparse-tree))
          :preface
          (defun my/org-ql-find-in-notes ()
            "Call 'org-ql-find' on 'denote-all-files'."
            (interactive)
            (when (require 'denote nil t)
              (org-ql-find (denote-directory-files))))
          :custom
          (org-ql-search-directories-files-regexp "\\.org\\(?:\\.gpg\\)?$")
          (org-ql-search-directories-files-recursive t)
          :preface
          (defconst org-last-repeat-time-regexp
            "\\(?::LAST_REPEAT: *\\[\\([^]]+\\)]\\)"
            "Matches the LAST_REPEAT property together with a time stamp.")
          :config
          (require 'org-ql-search)
          (org-ql-defpred last-repeat (&key from to _on)
            "Return non-nil if current entry was repeated in given period.
        Without arguments, return non-nil if entry has LAST_REPEAT property."
            :normalizers ((`(,predicate-names ,(and num-days (pred numberp)))
                           (let* ((from-day (* -1 num-days))
                                  (rest (list :from from-day)))
                             (org-ql--normalize-from-to-on
                               `(last-repeat :from ,from))))
                          (`(,predicate-names . ,rest)
                           (org-ql--normalize-from-to-on
                             `(last-repeat :from ,from :to ,to))))
            :preambles ((`(,predicate-names . ,_)
                         ;;  Predicate still needs testing.
                         (list :regexp org-last-repeat-time-regexp :query query)))
            :body
            (org-ql--predicate-ts :from from :to to :regexp org-last-repeat-time-regexp :match-group 1))
          (org-ql-defpred (tags-expanded expanded-tags tags-x xtags) (&rest tags)
            "Return non-nil if current heading has one or more of TAGS.
        If TAGS contains a group tag, all tags in the group is used to match.
        Both inherited and local tags is tested."
            :normalizers ((`(,predicate-names . ,tags)
                           `(tags-expanded ,@tags)))
            :body (apply #'org-ql--predicate-tags
                         (seq-uniq (--mapcat (org-tags-expand it t)
                                             tags))))
          (org-ql-defpred (category-inherited) (&rest categories)
            "Return non-nil if current heading has CATEGORY.
        Ancestors are looked up If current heading has no CATEGORY."
            :body (when-let ((category (or (org-get-category (point))
                                           (org-entry-get (point) "CATEGORY" t))))
                    (cl-typecase categories
                      (null t)
                      (otherwise (member category categories)))))
          (with-eval-after-load 'org-expiry
            (org-ql-defpred expired ()
              "Return non-nil if the entry is expired."
              :body (org-expiry-expired-p))))
        
        (use-package org-ql-view
          :after (org-ql org-agenda)
          :config
          (push `("Tasks on Web/Book"
                  :buffers-files org-agenda-files
                  :query (and (or (tags "web")
                                  (tags "book"))
                              (not (or (done) (todo ,my/org-todo-keyword--someday))))
                  :super-groups ((:auto-tags))
                  :sort (priority date))
                org-ql-views)
          (push `("Habits"
                  :buffers-files org-agenda-files
                  :query (habit)
                  :sort (todo scheduled))
                org-ql-views))
        
      • org-contacts
        "emacs-org-contacts"
        
        (use-package org-contacts
          :after org
          :commands (org-contacts-anniversaries)
          :config
          (when (require 'denote nil t)
            (setq org-contacts-files
                  (list (denote-get-path-by-id "20230605T173003")))))
        
      • org-ml   ARCHIVE
      • org-roam   ARCHIVE
      • org-mime
        "emacs-org-mime"
        
        (use-package org-mime
          :after org
          :commands (org-mime-org-subtree-htmlize)
          :bind (:map org-mode-map
                      ("C-c M-M" . org-mime-org-subtree-htmlize))
          :custom
          (org-mime-use-property-inheritance t)
          (org-mime-export-ascii t))
        
      • org-web-tools
        "emacs-org-web-tools"
        
        (use-package org-web-tools
          :bind (("C-c C-;" . org-web-tools-insert-link-for-url)))
        
      • org-volume   ARCHIVE
      • org-clock-convenience
        ("https://github.com/dfeich/org-clock-convenience/releases.atom" soft_update)
        
        (use-package org-clock-convenience
          :ensure t
          :after org-agenda
          :bind
          (:map org-agenda-mode-map
                ("@"   . org-clock-convenience-fill-gap)
                ("C-@" . org-clock-convenience-fill-gap-both)))
        
      • org-reveal
        "emacs-org-reveal"
        
      • org-expiry
        "emacs-org-contrib"
        
        (use-package org-expiry
          :after my/org-mode-init
          :custom
          (org-expiry-inactive-timestamps t)
          (org-expiry-handler-function 'org-expiry-archive-subtree)
          :config
          (with-eval-after-load 'org-capture
            (add-hook 'org-capture-prepare-finalize-hook
                      #'org-expiry-insert-created))
          (org-expiry-insinuate))
        
      • ob-async   ARCHIVE
      • ob-lilypond   ARCHIVE
      • ox-hugo
        "emacs-ox-hugo"
        
        (use-package ox-hugo
          :after ox)
        
      • org-web-track

        org-web-track-update-all-and-notify seems not to suit midnight-hook.

        (use-package org-web-track
          :ensure t
          :after (my/org-mode-init my-org-global-map)
          :bind (:map my/org-global-map
                      ("T q" . my/org-web-track-ql-view)
                      ("T c" . org-web-track-columns)
                      ("T a" . org-web-track-agenda-columns)
                      ("T n" . org-web-track-setup-entry)
                      ("T u" . org-web-track-update-entry)
                      ("T U" . org-web-track-update-files)
                      ("T r" . org-web-track-report))
          :preface
          (defun my/org-web-track-ql-view ()
            "Open Org Agenda view with tracking items which hold TRACK_URL property."
            (interactive)
            (require 'org-web-track nil t)
            (with-current-buffer (get-buffer-create "Org Web Track")
              (org-agenda-mode)
              (setq-local org-overriding-columns-format (org-web-track-columns-format)
                          org-agenda-view-columns-initially t)
              (setq org-columns-modify-value-for-display-function
                    'org-web-track-display-values)
              (when (require 'org-ql nil t)
                (org-ql-search org-web-track-files
                               `(property ,org-web-track-url)
                               :super-groups '((:tag "book") (:tag "food")
                                               (:tag "tool") (:tag "supply"))
                               :buffer (current-buffer)))))
          (defun my/org-web-track-update-all-and-notify ()
            "Update all web tracking items in `org-web-track-files'
         and send an email if any of them has changed its value."
            (let* ((org-log-into-drawer t)
                   (message-kill-buffer-on-exit t)
                   (mail-msg (mapconcat
                              (lambda (chg)
                                (org-with-point-at chg
                                  (let ((org-trust-scanner-tags t))
                                    (format "%s\n\t%s\n"
                                            (substring-no-properties
                                             (org-get-heading t t t t))
                                            (org-web-track-current-changes nil "%p => %c" " | ")))))
                              (org-web-track-update-files))))
              (unless (string-blank-p mail-msg)
                ;; you need to set SMTP info to smtpmail-xxx variables
                (message-mail user-mail-address "Web Tracking Notification")
                (message-goto-body)
                (insert mail-msg)
                (message-send-and-exit))))
          :custom
          (org-web-track-selectors-alist
           `((,(regexp-quote "www.lenovo.com/jp/ja/p/laptops/thinkpad")
              (lambda (dom)
                (when-let ((price-node (car (dom-search dom (lambda (node) (string= "productpromotionprice"
                                                                                (dom-attr node 'name)))))))
                  (assoc-default 'content (cadr price-node)))))
             (,(rx "www.amazon.co.jp" (+ anychar) "-ebook")
              "pup 'span[id=\"priceblock_ourprice\"],span[id=\"kindle-price\"],span[class=\"a-text-price\"],span[class=\"a-offscreen\"] json{}' \| jq -re '.[0].text'")
             (,(regexp-quote "www.amazon.co.jp/product-reviews")
              ([.a-size-base.a-color-secondary] ; num of stars
               [.a-size-medium.a-color-base])) ; rating
             (,(regexp-quote "www.amazon.co.jp")
              ([.a-price.aok-align-center > .a-offscreen]
               [div:availability span])) ; availability
             (,(regexp-quote "www.yodobashi.com/product")
              [:js_scl_unitPrice])
             (,(regexp-quote "kakaku.com/item")
              [.priceTxt])
             (,(regexp-quote "store.steampowered.com")
              (lambda (dom)
                (when-let ((products (dom-search dom (lambda (node) (string-prefix-p "game_area_purchase_game"
                                                                                     (dom-attr node 'class))))))
                  (dom-text (or (dom-by-class (car products) "game_purchase_price")
                                (dom-by-class (car products) "discount_final_price"))))))
             (,(regexp-quote "www.googleapis.com/youtube/v3/channels")
              (lambda (json)
                (assoc-default 'subscriberCount
                               (assoc-default 'statistics (aref (cdr (assoc 'items json))
                                                                0)))))
             (,(regexp-quote "www.googleapis.com/youtube/v3/videos")
              (lambda (json)
                (when (require 'dash nil t)
                  (let ((stat (->> json
                                   (assoc-default 'items)
                                   (mapcar #'identity)
                                   (nth 0)
                                   (assoc-default 'statistics))))
                    (list (assoc-default 'likeCount stat)
                          (assoc-default 'commentCount stat))))))
             (,(regexp-quote "openexchangerates.org/api/latest")
              (lambda (json)
                (number-to-string
                 (assoc-default 'JPY
                                (assoc-default 'rates json))))
              (lambda (rate) (format "%.3f" (string-to-number rate))))
             (,(regexp-quote "map.kaldi.co.jp/kaldi")
              "pup 'div[class=\"shop_bnr news\"] ul li text{}' | xargs echo -n")
             (,(regexp-quote "www.seijoishii.com")
              [.price > .total])))
          (org-agenda-bulk-custom-functions
           '((?U org-web-track-agenda-update)))
          (org-web-track-item-column-width 16)
          (org-web-track-update-column-width 30)
          (org-web-track-files
           (lambda ()
             (when (require 'denote nil t)
               (let ((denote-directory (expand-file-name "~/org/notes"))
                     (org-agenda-files '("~/org/agenda/")))
                 (append (org-agenda-files)
                         (cl-remove-if-not (lambda (f)
                                             (member "track"
                                                     (denote-extract-keywords-from-path f)))
                                           (denote-directory-files))))))))
        
        • batch tracking
          export DISPLAY=:0 && \
            emacs -Q --batch \
                  -l $XDG_CONFIG_HOME/emacs/early-init.el \
                  -l $XDG_CONFIG_HOME/emacs/lisp/my-org-web-track.el \
                  -l $XDG_CONFIG_HOME/emacs/batch/org-web-track-mail.el \
                  -f my/org-web-track-mail
          
          (provide 'my/org-mode-init)
          (define-keymap :prefix 'my/org-global-map)
          (provide 'my-org-global-map)
          
          (setopt message-send-mail-function 'smtpmail-send-it
                  smtpmail-smtp-server "<<smtp-host>>"
                  smtpmail-smtp-service 465
                  smtpmail-smtp-user user-mail-address
                  smtpmail-stream-type 'ssl
                  smtpmail-mail-address user-mail-address
                  message-send-mail-function 'smtpmail-send-it
                  network-security-level 'low)
          
          (defun my/org-web-track-mail ()
            "Update `org-web-track' items along with sending email and immediate exit."
            (my/org-web-track-update-all-and-notify)
            (org-save-all-org-buffers)
            (save-buffers-kill-emacs t))
          
      • org-tidy

        org-tidy attentively conceals all org drawers. I use it in [BROKEN LINK: id:2bd0c5d2-faed-43d9-b054-3e1ad9b51a76].

        (use-package org-tidy
          :ensure t
          :after org
          :bind (:map org-mode-map
                      ("C-c t" . org-tidy-mode)))
        
      • org-super-links   ARCHIVE
  • Completion
    • vertico
      "emacs-vertico"
      
      ("https://github.com/minad/vertico/releases.atom" soft_update)
      
      (use-package vertico
        :bind (:map vertico-map
                    ("C-j" . exit-minibuffer)
                    ("RET" . vertico-directory-enter)
                    ("C-h" . vertico-directory-delete-char)
                    ("M-h" . vertico-directory-delete-word)
                    ("M-q" . vertico-quick-insert)
                    ("C-q" . vertico-quick-exit))
        :hook
        ((minibuffer-setup . vertico-repeat-save)
         ;; enable to write "~/" or "/" whatever path being input in minibuffer
         (rfn-eshadow-update-overlay . vertico-directory-tidy))
        :custom
        (vertico-cycle t)
        (vertico-scroll-margin 0)
        (vertico-count 11)
        (vertico-resize 'grow-only)
        :config
        (bind-keys ("M-R" . vertico-repeat))
        (vertico-mode 1))
      
      (use-package vertico-multiform
        :after vertico
        :bind (:map vertico-map
                    ("M-G" . vertico-multiform-grid))
        :config
        (vertico-multiform-mode 1))
      
    • consult
      "emacs-consult"
      
      ("https://github.com/minad/consult/releases.atom" soft_update)
      
      (use-package consult
        :after my-org-global-map
        :bind (("C-;" . consult-buffer)
               ("C-c m" . consult-mode-command)
               ("C-c r" . consult-recent-file)
               ("C-c h" . consult-outline)
               ("C-x M-:" . consult-complex-command)
               ("M-y" . consult-yank-pop)
               ("C-M-y" . consult-yank-from-kill-ring)
               ("M-g M-g" . consult-goto-line)
               ("M-g m" . consult-mark)
               ("M-g M" . consult-global-mark)
               ("M-g i" . consult-imenu)
               ("M-g I" . consult-imenu-multi)
               ("C-x r j" . consult-register)
               ("M-s e" . consult-isearch-history)
               ("M-s \\" . consult-locate)
               ("M-s f" . consult-find)
               ("M-s F" . consult-fd)
               ("M-s g" . consult-grep)
               ("M-s G" . consult-git-grep)
               ("M-s r" . consult-ripgrep)
               ("M-s l" . consult-line)
               ("H-s" . consult-line)
               ("M-s L" . consult-line-multi)
               ("M-s m" . consult-multi-occur)
               ("M-s k" . consult-keep-lines)
               ("M-s u" . consult-focus-lines)
               ("M-s i" . consult-info)
               ("C-x M-#" . consult-theme)
               :map my/org-global-map
               ("h" . consult-org-agenda)
               :map isearch-mode-map
               ("C-M-n" . consult-isearch-history)
               ("M-l" . consult-line)
               ("M-L" . consult-line-multi)
               :map minibuffer-local-map
               ("C-M-n" . consult-history)
               :map text-mode-map
               ("C-M-i" . completion-at-point)
               :map org-mode-map
               ("C-M-i" . completion-at-point))
        :custom
        (consult-narrow-key "<")
        (consult-widen-key ">")
        (consult-line-start-from-top t)
        (consult-project-root-function #'project-root-function)
        (consult-locate-args (format "plocate -d %s --ignore-case --existing --regexp"
                                     (expand-file-name "<<plocate-db()>>")))
        (consult-find-args "find . ")
        (consult-fd-args '((if (executable-find "fdfind" 'remote)
                               "fdfind" "fd")
                           "--hidden --full-path --color=never"))
        :init
        ;; use consult for completion unless Corfu is active
        (setq completion-in-region-function
              (lambda (&rest args)
                (apply (if vertico-mode
                           #'consult-completion-in-region
                         #'completion--in-region)
                       args)))
        :config
        (with-eval-after-load 'org
          (bind-keys :map org-mode-map
                     ("C-c H" . consult-org-heading))))
      
      (defun project-root-function ()
        (when-let (project (project-current))
          (car (project-roots project))))
      
      • consult-dir
        "emacs-consult-dir"
        
        (use-package consult-dir
          :after consult
          :bind ("H-d" . consult-dir))
        
      • affe
        (use-package affe
          :ensure t
          :bind (("M-s a f" . affe-find)
                 ("M-s a g" . affe-grep)))
        
      • consult-ghq
        (use-package consult-ghq
          :ensure t
          :after consult
          :bind (("M-s q f" . consult-ghq-find)
                 ("M-s q g" . consult-ghq-grep))
          :custom
          (consult-ghq-find-function 'dired))
        
    • embark
      "emacs-embark"
      
      ("https://github.com/oantolin/embark/releases.atom" soft_update)
      
      (use-package embark
        :bind (("C-." . embark-act)
               ("M-." . embark-dwim)
               ("<help> B" . embark-bindings)
               :map embark-file-map
               ("b" . browse-url))
        :init
        (setq prefix-help-command #'embark-prefix-help-command) ;
        :custom
        (embark-mixed-indicator-delay 3)
        :hook
        (embark-collect-mode . consult-preview-at-point-mode)
        :config
        (require 'embark-consult)
        (require 'embark-org)
        (require 'avy-embark-collect)
        (push 'embark--allow-edit
              (alist-get 'shell-command-on-region embark-target-injection-hooks)))
      
    • marginalia
      "emacs-marginalia"
      
      ("https://github.com/minad/marginalia/releases.atom" soft_update)
      
      (use-package marginalia
        :after vertico
        :config
        (marginalia-mode 1))
      
    • orderless
      "emacs-orderless"
      
      ("https://github.com/oantolin/orderless/releases.atom" soft_update)
      
      (use-package orderless
        :custom (completion-styles '(orderless)))
      
      • support for migemo in orderless
        (defun orderless-migemo (component)
          (let ((pattern (migemo-get-pattern component)))
            (condition-case nil
                (progn (string-match-p pattern "") pattern)
              (invalid-regexp nil))))
        
        (orderless-define-completion-style orderless-default-style
          (orderless-matching-styles '(orderless-literal
                                       orderless-regexp)))
        
        (orderless-define-completion-style orderless-migemo-style
          (orderless-matching-styles '(orderless-literal
                                       orderless-regexp
                                       orderless-migemo)))
        
        (setq orderless-matching-styles '(orderless-literal orderless-regexp orderless-migemo))
        
        ;; Uncomment this clause if you prefer to apply rules for completion categories selectively
        ;; (setq completion-category-overrides
        ;;       '((command (styles orderless-default-style))
        ;;         (file (styles orderless-migemo-style))
        ;;         (project-file (styles orderless-migemo-style))
        ;;         (buffer (styles orderless-migemo-style))
        ;;         (symbol (styles orderless-default-style))
        ;;         (multi-category (styles orderless-migemo-style))
        ;;         (org-heading (styles orderless-migemo-style))
        ;;         (consult-location (styles orderless-migemo-style))
        ;;         (org-roam-node (styles orderless-migemo-style))
        ;;         (unicode-name (styles orderless-migemo-style))
        ;;         (variable (styles orderless-default-style))))
        
    • corfu

      Though I prefer vertico more for completion interface, consult manual says Vertico can't be used with Eglot or lsp-mode. So I decided to use Corfu only in prog-mode.

      "emacs-corfu"
      
      (use-package corfu
        :hook ((prog-mode shell-mode eshell-mode) . corfu-mode)
        :custom
        (corfu-cycle t)
        (corfu-auto t)
        (corfu-preselect-first t))
      
    • cape
      "emacs-cape"
      
      (use-package cape
        :bind (:map ctl-x-map
                    :prefix "C-M-i"
                    :prefix-map my/cape-map
                    :prefix-docstring "Keymap for cape commands."
                    ("C-M-i" . completion-at-point)
                    ("d" . cape-dabbrev)
                    ("h" . cape-history)
                    ("f" . cape-file)
                    ("k" . cape-keyword)
                    ("s" . cape-symbol)
                    ("a" . cape-abbrev)
                    ("l" . cape-line)
                    ("w" . cape-dict)
                    ("\\" . cape-tex)
                    ("_" . cape-tex)
                    ("^" . cape-tex)
                    ("&" . cape-sgml)
                    ("r" . cape-rfc1345))
        :hook
        (text-mode . (lambda ()
                       (setq-local completion-at-point-functions
                                   (append completion-at-point-functions
                                           '(cape-dabbrev
                                             cape-file
                                             cape-symbol
                                             cape-dict)))))
        (prog-mode . (lambda ()
                       (setq-local completion-at-point-functions
                                   (append completion-at-point-functions
                                           '(cape-dabbrev
                                             cape-file
                                             cape-symbol
                                             cape-keyword
                                             cape-history))))))
      
  • text-mode and its derivatives
    • nxml-mode
      (use-package nxml-mode
        :delight (nxml-mode " XM")
        :mode (("\.xml$"   . nxml-mode)
               ("\.xsl$"   . nxml-mode)
               ("\.xhtml$" . nxml-mode)
               ("\.page$"  . nxml-mode)
               ("\.plist$" . nxml-mode))
        :hook (nxml-mode . my/prog-mode-hook-function)
        :custom
        (nxml-child-indent 2)
        (nxml-attribute-indent 2)
        (nxml-slash-auto-complete-flag t)
        :config
        (setq-local tab-width 2)
        ;; pattern in hideshow mode for nxml mode
        (add-to-list 'hs-special-modes-alist
                     (list 'nxml-mode
                           "<!--\\|<[^/>]*[^/]>"
                           "-->\\|</[^/>]*[^/]>"
                           "<!--"
                           'nxml-forward-element
                           nil)))
      
    • toml-mode

      language grammar for toml

      (toml "https://github.com/tree-sitter/tree-sitter-toml")
      
      (toml-mode . toml-ts-mode)
      
      (use-package toml
        :mode "\\.toml$")
      
    • yaml-mode
      (yaml "https://github.com/ikatyang/tree-sitter-yaml")
      
      (yaml-mode . yaml-ts-mode)
      
      (yaml-ts-mode . combobulate-mode)
      
      (use-package yaml
        :mode "\\.ya?ml$")
      
    • json-mode
      "tree-sitter-json"
      
      (json-mode . json-ts-mode)
      
      (use-package json
        :mode "\\.json$")
      
    • markdown-mode
      "markdown"
      "tree-sitter-markdown"
      "emacs-markdown-mode"
      
      (use-package markdown-mode
        :mode "\\.md$")
      
    • tex-mode
      (use-package tex-mode
        :mode ("\\.tex$" . latex-mode)
        :hook ((tex-mode . (lambda () (setq ispell-parser 'tex))))
        :config
        (my/add-dash-docsets-hook 'latex-mode-hook '("LaTeX")))
      
  • prog-mode and its derivatives
    (add-hook 'prog-mode-hook
              #'my/prog-mode-hook-function)
    
    (defun my/prog-mode-hook-function ()
      (hl-line-mode -1)
      (hs-minor-mode -1)
      (display-fill-column-indicator-mode 1)
      (prettify-symbols-mode 1))
    
    • sh-mode
      "tree-sitter-bash"
      
      (use-package sh-script
        :delight " SH"
        :commands sh-mode
        :mode ("\\.sh$" . sh-mode)
        :hook ((sh-mode)
               . (lambda ()
                   (my/add-dash-docsets-hook 'sh-mode-hook '("Bash"))
                   (setq-local devdocs-current-docs '("bash"))))
        :custom
        (sh-basic-offset 2)
        (sh-indent-after-if '+)
        (sh-indent-for-case-label 0)
        (sh-indent-for-case-alt '+))
      
    • bash-ts-mode
      (use-package bash-ts-mode
        :interpreter "bash"
        :hook ((bash-ts-mode)
               . (lambda ()
                   (setq-local devdocs-current-docs '("bash")))))
      
    • emacs-lisp-mode

      I'm in the trial of pp-eval-last-sexp instead of eval-last-sexp adviced from this blog post

      (elisp "https://github.com/Wilfred/tree-sitter-elisp")
      
      (use-package elisp-mode
        :mode ("\\.eld?$" . emacs-lisp-mode)
        :commands emacs-lisp-mode
        :hook (emacs-lisp-mode . (lambda ()
                                   (setq-local devdocs-current-docs '("elisp"))
                                   (push '("lambda" . ?λ) prettify-symbols-alist)
                                   (setq-local tab-width 8)
                                   (eldoc-mode 1)))
        :config
        (require 'eldoc)
        (substitute-key-definition 'eval-last-sexp
                                   'pp-eval-last-sexp
                                   global-map)
        (my/add-dash-docsets-hook 'emacs-lisp-mode-hook
                                  '("Emacs Lisp")))
      
      (defun my/elisp-mode-eval-buffer ()
        (interactive)
        (message "Evaluated buffer")
        (eval-buffer))
      
      (keymap-set emacs-lisp-mode-map "C-c C-c" #'my/elisp-mode-eval-buffer)
      (keymap-set lisp-interaction-mode-map "C-c C-c" #'my/elisp-mode-eval-buffer)
      
    • lisp-mode
      (use-package lisp-mode
        :mode "\\.lisp$"
        :config
        (my/add-dash-docsets-hook 'lisp-mode-hook
                                  '("Common Lisp")))
      
      • sly

        Sly, a fork of Slime.

        "emacs-sly"
        
        (use-package sly
          :custom
          (inferior-lisp-program "<<which(filename="sbcl")>>")
          (sly-auto-start 'ask)
          :config
          (with-eval-after-load 'ob-lisp
            (setopt org-babel-lisp-eval-fn 'sly-eval)))
        
    • scheme-mode
      "tree-sitter-scheme"
      
      (use-package scheme
        :mode (("\\.guix-channel" . scheme-mode)
               ("\\.guix-authorizations" . scheme-mode)))
      
    • cc-mode
      (use-package cc-mode
        :mode (("\\.c\\'"   . c-mode)
               ("\\.cpp\\'" . c++-mode))
        :hook (((c-mode c-ts-mode)
                . (lambda ()
                    (setq-local devdocs-current-docs '("c"))))
               ((c++-mode c++-ts-mode)
                . (lambda ()
                    (setq-local devdocs-current-docs '("cpp")))))
        :custom
        (c-tab-always-indent t)
        (c-auto-align-backslashes nil)
        (c-echo-syntactic-information-p t)
        (c-default-style "my/c-style")
        :config
        (my/add-dash-docsets-hook 'c-mode-common-hook
                                  '("C"))
        (c-toggle-auto-newline 1)
        (c-add-style "my/c-style"
                     '((c-basic-offset . 4)
                       (c-comment-only-line-offset . 0)
                       (c-hanging-braces-alist
                        . ((brace-if-brace before after)
                           (substatement-open before after)))
                       (c-hanging-colons-alist
                        . ((case-label after)))
                       (c-cleanup-list
                        . (brace-else-brace
                           brace-elseif-brace
                           empty-defun-braces
                           defun-close-semi
                           list-close-comma
                           scope-operator))
                       (c-offsets-alist
                        . ((arglist-intro . +)
                           (arglist-cont-nonempty . c-lineup-arglist)))))
        (c-add-style "my/objc-style"
                     '((c-basic-offset . 2)
                       (c-comment-only-line-offset . 0)
                       (c-hanging-braces-alist
                        . ((brace-if-brace before after)
                           (substatement-open before after)))
                       (c-hanging-colons-alist
                        . ((case-label after)))
                       (c-cleanup-list
                        . (brace-else-brace
                           brace-elseif-brace
                           empty-defun-braces
                           defun-close-semi
                           list-close-comma
                           scope-operator))
                       (c-offsets-alist
                        . ((arglist-intro . +)
                           (arglist-cont-nonempty . c-lineup-arglist))))))
      
    • rust-mode
      "emacs-rust-mode"
      
      (use-package rust-mode
        :mode "\\.rs$"
        :hook ((rust-mode rust-ts-mode)
               . (lambda ()
                   (setq-local devdocs-current-docs '("rust")))))
      
    • rustic   ARCHIVE
    • python-mode
      "tree-sitter-python"
      
      (python-mode . python-ts-mode)
      
      (python-ts-mode . combobulate-mode)
      
      (use-package python
        :mode "\\.py\\'"
        :interpreter "python"
        :hook
        ((python-mode inferior-python-mode python-ts-mode) .
         (lambda ()
           (setq-local flycheck-checker 'python-pylint)
           (setq-local devdocs-current-docs '("python~3.9"))))
        :config
        (my/add-dash-docsets-hook 'python-mode-hook
                                  '("Python 3" "NumPy" "Matplotlib"))
        (my/add-dash-docsets-hook 'inferior-python-mode-hook
                                  '("Python 3")))
      
      (defvar python-mode-initialized nil)
      
      (defun my-python-mode-hook ()
        (setq-local flycheck-checker 'python-pylint)
        (jedi:setup)
        (jedi-mode 1)
        (unless python-mode-initialized
          (setq python-mode-initialized t)
          (info-lookup-add-help
           :mode 'python-mode
           :regexp "[a-zA-Z_0-9.]+"
           :doc-spec
           '(("(python)Python Module Index" )
             ("(python)Index"
              (lambda
                (item)
                (cond
                 ((string-match
                   "\\([A-Za-z0-9_]+\\)() (in module \\([A-Za-z0-9_.]+\\))" item)
                  (format "%s.%s" (match-string 2 item)
                          (match-string 1 item))))))))))
      
      • emacs-project
        "emacs-company-jedi"
        
        (use-package company-jedi
          :after python
          :hook (python-mode . my/python-mode-hook)
          :custom
          (jedi:tooltip-method nil)
          :config
          (defun my/python-mode-hook ()
            (add-to-list 'company-backends 'company-jedi)))
        
    • ruby-mode
      (ruby-mode . ruby-ts-mode)
      
      (use-package ruby
        :mode (("\\.rb$"   . ruby-mode)
               ("Capfile$" . ruby-mode)
               ("Gemfile$" . ruby-mode))
        :interpreter (("ruby"    . ruby-mode)
                      ("rbx"     . ruby-mode)
                      ("jruby"   . ruby-mode))
        :hook ((ruby-mode ruby-ts-mode)
               . (lambda ()
                   (setq-local flycheck-checker 'ruby-rubocop)
                   (setq-local devdocs-current-docs '("ruby~3.2"))))
        :config
        (setq ruby-indent-level 2)
        (setq ruby-insert-encoding-magic-comment nil)
        (add-to-list 'hs-special-modes-alist
                     `(ruby-mode
                       ,(rx (or "def" "class" "module" "do" "if" "{" "[")) ; Block start
                       ,(rx (or "}" "]" "end"))                       ; Block end
                       ,(rx (or "#" "=begin"))                        ; Comment start
                       ruby-forward-sexp nil))
        (my/add-dash-docsets-hook 'ruby-mode-hook '("Ruby")))
      
      • inf-ruby
        "emacs-inf-ruby"
        
        (use-package inf-ruby
          :after ruby
          :config
          (setq inf-ruby-default-implementation "pry")
          (add-to-list 'inf-ruby-implementations '("pry" . "pry"))
          (setq inf-ruby-eval-binding "Pry.toplevel_binding")
          (setq inf-ruby-first-prompt-pattern "^\\[[0-9]+\\] pry\\((.*)\\)> *")
          (setq inf-ruby-prompt-pattern "^\\[[0-9]+\\] pry\\((.*)\\)[>*\"'] *"))
        
    • java-ts-mode
      (java-mode . java-ts-mode)
      
      (use-package java-ts-mode
        :mode "\\.java$"
        :hook (java-ts-mode
               . (lambda ()
                   (setq-local devdocs-current-docs '("openjdk~21")))))
      
    • lua-mode
      "emacs-lua-mode"
      
      (use-package lua-mode
        :mode "\\.lua$"
        :hook ((lua-mode lua-ts-mode)
               . (lambda ()
                   (setq-local flycheck-checker 'lua-rubocop)
                   (setq-local devdocs-current-docs '("lua~3.2")))))
      
    • perl-mode
      (use-package perl-mode
        :delight " PL")
      
    • web-mode
      "emacs-web-mode"
      "tree-sitter-html"
      "tree-sitter-css"
      
      (use-package web-mode
        :delight " WB"
        :mode (("\\.phtml$"     . web-mode)
               ("\\.tpl\\.php$" . web-mode)
               ("\\.jsp$"       . web-mode)
               ("\\.as[cp]x$"   . web-mode)
               ("\\.erb$"       . web-mode)
               ("\\.html?$"     . web-mode))
        :custom
        (web-mode-attr-indent-offset        2)
        (web-mode-attr-value-indent-offset  2)
        (web-mode-code-indent-offset        2)
        (web-mode-css-offset                2)
        (web-mode-markup-indent-offset      2)
        (web-mode-sql-indent-offset         2)
        (web-mode-block-padding             2)
        (web-mode-script-padding            2)
        :config
        (my/add-dash-docsets-hook 'web-mode-hook '("HTML")))
      
    • css-mode
      (use-package css-mode
        :mode "\\.css$"
        :hook ((css-mode css-ts-mode)
               . (lambda ()
                   (my/add-dash-docsets-hook 'css-mode-hook '("CSS"))
                   (setq-local devdocs-current-docs '("css"))))
        :custom
        (css-indent-offset 2))
      
    • js2-mode
      "emacs-js2-mode"
      
      (js2-mode . js-ts-mode)
      (css-mode . css-ts-mode)
      
      (use-package js2-mode
        :delight " J2"
        :mode (("\\.js\\(on\\)?\\'" . js2-mode)
               ("\\.eslintrc\\'"    . js2-mode))
        :hook (js2-mode . (lambda ()
                            (setq-local flycheck-checker 'javascript-eslint)
                            (setq-local flycheck-disabled-checkers '(javascript-jshint javascript-jscs))))
        :custom
        (flycheck-javascript-eslint-executable "eslint")
        (my/add-dash-docsets-hook 'js2-mode-hook '("JavaScript")))
      
      (use-package js
        :disabled t
        :delight " JS"
        :custom
        (js-indent-level 2))
      
    • make
      (cmake "https://github.com/uyha/tree-sitter-cmake")
      (make "https://github.com/alemuller/tree-sitter-make")
      
      (cmake-mode . cmake-ts-mode)
      
      (use-package cmake
        :mode (("\\.mk$" . cmake-mode)
               ("^Makefile$" . cmake-mode)))
      
    • sql-mode
      (use-package sql
        :delight " SQ"
        :commands (sql-mode)
        :mode (("\\.sql$" . sql-mode))
        :bind (:map sql-interactive-mode-map
                    ("C-j" . comint-send-input))
        :hook (sql-mode . (lambda ()
                            (setq-local flycheck-checker 'sql-sqlint))))
      
    • plantuml-mode
      "emacs-plantuml-mode"
      
      (use-package plantuml
        :mode "\\.plu$"
        :custom
        (plantuml-jar-path "~/.guix-extra-profiles/emacs/emacs/share/java/plantuml.jar")
        (plantuml-exec-mode 'jar)
        :config
        (my/add-dash-docsets-hook 'plantuml-mode-hook '("PlantUML")))
      
    • conf
      (use-package conf-mode
        :delight " CF")
      
    • generic-x
      (use-package generic-x
        :mode (("\\.conf$"     . apache-conf-generic-mode)
               ("\\.htaccess$" . apache-conf-generic-mode)))
      
  • special-mode derivatives
    • special mode
      (keymap-set special-mode-map "C-j" #'push-button)
      
    • help
      (with-eval-after-load 'help
        (keymap-set help-map "C-?" #'help-for-help)
        (keymap-set help-map "c" #'describe-face)
        (keymap-set help-map "K" #'describe-keymap))
      
    • woman
      (autoload #'woman "woman" nil t)
      
  • Programming
    • sMerge

      sMerge can be used to solve merging problems. The code below kicks off sMerge if there is a collision sign.

      (autoload 'smerge-mode "smerge-mode" nil t)
      
      (defun my/try-smerge ()
        (save-excursion
          (goto-char (point-min))
          (when (re-search-forward "^<<<<<<< " nil t)
            (smerge-mode 1))))
      (add-hook 'find-file-hook 'my/try-smerge t)
      
    • Next Error
      (setopt next-error-message-highlight t)
      
    • Hideshow
      (use-package hideshow
        :diminish "hs"
        :bind (("C-c TAB" . hs-toggle-hiding)))
      
    • imenu
      (use-package imenu
        :custom
        (imenu-auto-rescan t))
      
    • compile
      (use-package compile
        :bind (:map compilation-mode-map
                    ("C-j" . compile-goto-error)))
      
    • grep
      (use-package grep
        :custom
        (grep-program "rg")
        :config
        (bind-keys :map grep-mode-map
                   ("C-j" . compile-goto-error)))
      
      (defun my/grep-ripgrep-all ()
        "Grep many kind of files like pdf, epub and more."
        (interactive)
        (let ((grep-find-command '("rga --with-filename --no-heading --line-number --color never -e '' ./* ./**/*" . 66)))
          (call-interactively 'grep-find)))
      
      (keymap-global-set "M-s R" 'my/grep-ripgrep-all)
      
    • executable

      The post says it is useful to set executable bit if the saved file seems to be script.

      (with-eval-after-load 'executable
        (add-hook 'after-save-hook
                  'executable-make-buffer-file-executable-if-script-p))
      
    • Paren Showing
      (use-package paren
        :hook (prog-mode . show-paren-local-mode)
        :custom
        (show-paren-style 'mixed)
        (show-paren-delay 0.2)
        (show-paren-mode nil)
        (show-paren-context-when-offscreen t))
      
    • project
      (use-package project
        :custom
        (project-kill-buffers-display-buffer-list t)
        :config
        (add-to-list 'project-switch-commands
                     '(project-dired "Dired root"))
        (with-eval-after-load 'magit
          (bind-keys :map project-prefix-map
                     ("M" . magit-project-status))
          (add-to-list 'project-switch-commands
                       '(magit-project-status "Magit"))))
      
    • log-view
      (use-package log-view
        :delight
        (log-view-mode " LV")
        :mode ("\\.log$" . log-view-mode)
        :hook ((log-view-mode . my/prog-mode-hook-function )
               (log-view-mode . auto-revert-tail-mode)))
      
    • vc (version control)
    • vc-hooks
      (use-package vc-hooks
        :custom
        (vc-follow-symlinks t))
      
  • Tree Sitter
    "tree-sitter"
    
    (use-package treesit
      :preface
      (defun my/setup-install-grammars ()
        "Install Tree-sitter grammars if they are absent."
        (interactive)
        (dolist (grammar
                 '(<<treesit-language-source>>))
          (add-to-list 'treesit-language-source-alist grammar)
          (unless (treesit-language-available-p (car grammar))
            (treesit-install-language-grammar (car grammar)))))
      :config
      (my/setup-install-grammars))
    
    • combobulate
      (use-package combobulate
        :disabled t
        :after treesit
        :preface
        (setq combobulate-key-prefix "C-c o")
        :hook (<<combobulate-hook>>)
        :load-path ("~/.emacs.d/site-elisp/combobulate/"))
      
    • major mode remapping
      (setq major-mode-remap-alist
            '(<<major-mode-remap-rule>>))
      
  • Elisp Package Development
    • package build
      "emacs-package-build"
      
      (let ((default-directory package-user-dir))
        (setopt package-build-recipes-dir
                (expand-file-name "recipes")
                package-build-archive-dir
                (expand-file-name "packages")
                package-build-working-dir
                (expand-file-name "working")))
      
    • flycheck package
      "emacs-flycheck-package"
      
      (eval-after-load 'flycheck
        '(flycheck-package-setup))
      
    • package lint
      "emacs-package-lint"
      
    • makem.sh
      (require 'makem)
      
  • Blogging

    This subtree holds resources for my blog.

    • org-static-blog   ARCHIVE
    • templates for blogging
      (blog-post-filename (format-time-string "%Y%m%d-%H%M.org"))
      
      (blog-post-skeleton "#+title:" n
                          "#+date:" n
                          "#+description:" n
                          "#+filetags:" n n
                          "#+SETUPFILE: content/header")
      (blog-post-macro-preview "{{{preview-begin}}}" n
                               "{{{preview-end}}}")
      (blog-post-macro-accordion (p "Summary: " summary t)
                                 (format "{{{accordion-begin(%s)}}}" summary) n
                                 "{{{accordion-end}}}")
      
    • ox-haunt
      "emacs-ox-haunt"
      
      (use-package ox-haunt
        :after ox)
      
  • Journaling
    • journal entry template
      (journal "- diary" n>
               "#+begin_src text" n> "#+end_src" n
               "- clocktable" n>
               "#+begin: clocktable-by-tag :maxlevel 3 :wstart 1 "
               ":block " (p "year") "-" (p "month") "-" (p "day") n>
               "#+end:" n
               "- agenda" n>
               "#+begin_example" n
               (p "agenda") n>
               "#+end_example")
      
    • week subtree template
      (nw & "- mission [/]" n
          "- clocktable" n>
          "#+begin: clocktable-by-tag :maxlevel 3 "
          ":block " (p "year") "-W" (p "isoweek") " :wstart 1" n>
          "#+end:" n
          "- meta-projects" n>
          "#+begin: clocktable :maxlevel 3 :scope agenda "
          ":block " (p "year") "-W" (p "isoweek")
          " :wstart 1 :narrow 40! :tcolumns 3 :formula %" n>
          "#+end:")
      
  • sundry 3rd-party packages

    Packages below are out of categories above. Packages which have ARCHIVE tag is no longer in use. Keywords which package gives in its description are set to tag.

    • git-modes
      "emacs-git-modes"
      
      (use-package git-modes)
      
    • git-annex
      "emacs-git-annex"
      
      ("https://github.com/jwiegley/git-annex-el/releases.atom" soft_update)
      
      (use-package git-annex
        :after dired
        :custom-face
        (git-annex-dired-annexed-available ((t (:foreground "green yellow"))))
        (git-annex-dired-annexed-unavailable ((t (:foreground "red3")))))
      
    • magit-annex   ARCHIVE
    • orgit
      "emacs-orgit"
      
      (use-package orgit
        :after my/org-mode-init
        :custom
        (orgit-remote "github"))
      
    • suggest
      "emacs-suggest"
      
      (use-package suggest
        :commands suggest
        :custom
        (suggest-insert-example-on-start nil))
      
    • mastodon
      "emacs-mastodon"
      
      (use-package mastodon
        :after my-launch-app
        :bind (:map my/launch-app-map
                    ("@" . mastodon))
        :hook (mastodon-mode . visual-line-mode)
        :custom
        (mastodon-active-user "p_snow")
        (mastodon-instance-url "https://emacs.ch")
        (mastodon-client--token-file
         (no-littering-expand-var-file-name "mastodon.plstore"))
        :config
        (require 'mastodon-views))
      
    • bookmark+   ARCHIVE
    • chatgpt-shell
      "emacs-chatgpt-shell"
      
      (use-package chatgpt-shell
        :after engine-mode
        :preface
        (defun my/chatgpt-shell-dwim ()
          "docstring"
          (interactive)
          (if (use-region-p)
              (chatgpt-shell-prompt-compose)
            (chatgpt-shell)))
        :bind (:map engine-mode-prefixed-map
                    ("p c" . my/chatgpt-shell-dwim)
                    ("p C" . chatgpt-shell-prompt)
                    ("p e" . chatgpt-shell-explain-code)
                    ("p d" . chatgpt-shell-describe-code)
                    ("p p" . chatgpt-shell-proofread-region)
                    ("p g" . chatgpt-shell-write-git-commit)
                    ("p u" . chatgpt-shell-generate-unit-test)
                    ("p i" . (lambda (&optional lang)
                               "Ask ChatGPT to interpret into Japanese or some other language if LANG is non-nil."
                               (interactive "P")
                               (when (require 'chatgpt-shell nil t)
                                 (chatgpt-shell-send-region-with-header
                                  (format "Please help me interpret the following sentence into %s:"
                                          (if (equal lang '(4))
                                              (completing-read "Langauage: " '("Japanese" "English" "German"))
                                            "Japanese"))))))
                    :map chatgpt-shell-mode-map
                    ("C-M-g" . chatgpt-shell-clear-buffer))
        :custom
        (chatgpt-shell-openai-key 'my/apikey)
        (chatgpt-shell-system-prompt 3)
        (chatgpt-shell-model-temperature 0.6))
      
      (use-package ob-chatgpt-shell
        :after (chatgpt-shell ob-core)
        :config
        (ob-chatgpt-shell-setup)
        (add-to-list 'org-structure-template-alist
                     '("ch" . "src chatgpt-shell")
                     t))
      
      (use-package dall-e-shell
        :commands dall-e-shell
        :custom
        (dall-e-shell-openai-key 'my/apikey))
      
    • async
      "emacs-async"
      
      (use-package dired-async
        :after dired
        :config
        (dired-async-mode 1))
      
      (use-package async-bytecomp
        :custom
        (async-bytecomp-package-mode t))
      
    • visible-mark   marking faces color
      (use-package visible-mark
        :ensure t
        :config
        (global-visible-mark-mode 1))
      
    • ement   comm
      "emacs-ement"
      
    • ligature   extensions data
      "emacs-ligature"
      
      (use-package ligature
        :hook (prog-mode . ligature-mode)
        :config
        ;; ligatures below are supported by `Iosevka' font
        (ligature-set-ligatures 'prog-mode
                                '("-<<" "-<" "-<-" "<--" "<---" "<<-" "<-" "->" "->>" "-->" "--->" "->-" ">-" ">>-"
                                  "=<<" "=<" "=<=" "<==" "<===" "<<=" "<=" "=>" "=>>" "==>" "===>" "=>=" ">=" ">>="
                                  "<->" "<-->" "<--->" "<---->" "<=>" "<==>" "<===>" "<====>" "::" ":::" "__"
                                  "<~~" "</" "</>" "/>" "~~>" "==" "!=" "/=" "~=" "<>" "===" "!==" "!===" "=/=" "=!="
                                  "<:" ":=" "*=" "*+" "<*" "<*>" "*>" "<|" "<|>" "|>" "<." "<.>" ".>" "+*" "=*" "=:" ":>"
                                  "(*" "*)" "/*" "*/" "[|" "|]" "{|" "|}" "++" "+++" "\\/" "/\\" "|-" "-|" "<!--" "<!---")))
      
    • meow   modal_editing convenience ARCHIVE
    • enlive   selector query css
      "emacs-enlive"
      
    • gptel   convenience

      gptel is my go-to AI frontend in Emacs as of [2024-07-19 Fri]. It supports a large number of services (LLM models).

      (use-package gptel
        :ensure t
        :after engine-mode
        :bind (:map engine-mode-prefixed-map
                    ("i i" . gptel-send)
                    ("i <RET>" . gptel)
                    ("i m" . gptel-menu))
        :preface
        (defun my/apikey (&optional host key)
          "Return API key for HOST or OpenAI."
          (let ((host-fixed (or host "openai.com"))
                (key-fixed (or key "apikey")))
            (or (cdr (assoc key-fixed (auth-source-pass-parse-entry host-fixed)))
                (let ((sec (plist-get (car (auth-source-search :host host-fixed)) :secret)))
                  (if (functionp sec)
                      (funcall sec) sec)))))
        :custom
        (gptel-api-key 'my/apikey)
        (gptel-max-tokens 500)
        (gptel-model "gpt-4o-mini")
        (gptel-default-mode 'org-mode)
        :config
        (cl-pushnew '(proofreading . "Please help me proofread the following text with English.")
                    gptel-directives)
        (cl-pushnew '(japanese . "Please help me interpret the following sentence into Japanese.")
                    gptel-directives)
        (cl-pushnew '(english . "Please help me interpret the following sentence into English.")
                    gptel-directives)
        (gptel-make-gemini "Gemini"
          :key (apply-partially 'my/apikey "www.google.com/<<full-name>>" "gemini apikey")
          :stream t)
        (gptel-make-openai "Perplexity"
          :host "api.perplexity.ai"
          :key (apply-partially 'my/apikey "perplexity.ai")
          :endpoint "/chat/completions"
          :stream t
          :models '("llama-3-sonar-small-32k-chat"
                    "llama-3-sonar-small-32k-online"
                    "llama-3-sonar-large-32k-chat"
                    "llama-3-sonar-large-32k-online"
                    "llama-3-8b-instruct"
                    "llama-3-70b-instruct"
                    "mixtral-8x7b-instruct")))
      
    • engine-mode
      "emacs-engine-mode"
      
      (use-package engine-mode
        :preface
        (defvar engine/minibuffer-history nil
          "Minibuffer history for `engine-mode'.")
        (defun engine--history-search-term (engine-name)
          "Prompt the user for a search term for ENGINE-NAME.
      Default to the symbol at point."
          (let ((current-word (or (thing-at-point 'symbol 'no-properties) "")))
            (read-string (engine--search-prompt engine-name current-word)
                         nil 'engine/minibuffer-history current-word)))
        :config
        (advice-add 'engine--prompted-search-term :override
                    'engine--history-search-term)
        (engine/set-keymap-prefix (kbd "H-q"))
        (engine-mode 1)
        (defengine google
          "https://www.google.com/search?ie=utf-8&oe=utf-8&q=%s"
          :keybinding "g")
        (defengine duckduckgo
          "https://duckduckgo.com/?q=%s"
          :keybinding "d")
        (defengine youtube
          "https://www.youtube.com/results?aq=f&oq=&search_query=%s"
          :keybinding "y")
        (defengine github
          "https://github.com/search?ref=simplesearch&q=%s"
          :keybinding "h")
        (defengine amazon
          "https://www.amazon.co.jp/gp/search/?field-keywords=%s"
          :keybinding "a")
        (defengine melpa
          "https://melpa.org/#/?q=%s"
          :keybinding "m")
        (defengine eijiro
          "https://eow.alc.co.jp/search?q=%s"
          :keybinding "e")
        (defengine weblio
          "https://www.weblio.jp/content/%s"
          :keybinding "l")
        (defengine wikipedia-ja
          "https://ja.wikipedia.org/wiki/%s"
          :keybinding "w")
        (defengine wikipedia
          "https://en.wikipedia.org/wiki/%s"
          :keybinding "W"))
      
    • tmr   timer convenience
      "emacs-tmr"
      
      (use-package tmr
        :after my-launch-app
        :bind (:map my/launch-app-map
                    ("t" . tmr)))
      
    • beframe
      "emacs-beframe"
      
      (use-package beframe
        :ensure t
        :bind (("C-x B" . beframe-switch-buffer)
               ("C-x C-b" . beframe-buffer-menu))
        :custom
        (beframe-global-buffers '("*Messages*" "*Backtrace*"))
        (beframe-create-frame-scratch-buffer nil)
        :config
        (with-eval-after-load 'consult
          (defface beframe-buffer
            '((t :inherit font-lock-string-face))
            "Face for `consult' framed buffers.")
          (defvar beframe--consult-source
            `( :name     "Frame-specific buffers (current frame)"
               :narrow   ?F
               :category buffer
               :face     beframe-buffer
               :history  beframe-history
               :items    ,#'beframe--buffer-names
               :action   ,#'switch-to-buffer
               :state    ,#'consult--buffer-state))
          (add-to-list 'consult-buffer-sources 'beframe--consult-source))
        (beframe-mode 1))
      
    • free-keys   convenience
      "emacs-free-keys"
      
      (use-package free-keys
        :after my-launch-app
        :bind (:map my/launch-app-map
                    ("f" . free-keys))
        :config
        (setq free-keys-modifiers (append free-keys-modifiers
                                          '("H"))))
      
    • consult-recoll   docs convenience
      (use-package consult-recoll
        :ensure t
        :after consult
        :bind ("M-s c" . consult-recoll)
        :preface
        (defun my/consult-recoll--html (file)
          (let ((mime (mailcap-extension-to-mime (file-name-extension file))))
            (cond
             ((string= mime (regexp-quote "application/epub+zip"))
              (my/open-epub-as-html file))
             (t (eww-open-file file)))))
        :custom
        (consult-recoll-open-fns
         '(("text/html" . my/consult-recoll--html)
           ("application/pdf" . my/open-pdf-as-html))))
      
    • detached   processes convenience

      How to use detached.el

      • in vterm
        1. create a session with S-RET(detached-vterm-send-input)
        2. detach from the session with C-c C-d(detached-detach-key)
        3. attach to the session with C-RET(detached-vterm-attach)
      • with shell-command
        • invoke/call detached-shell-command
      "dtach"
      "emacs-detached"
      
      (use-package detached
        :init
        (detached-init)
        (defvar my/detached-bury-buffer nil)
        :bind
        (([remap async-shell-command] . detached-shell-command)
         ([remap compile] . detached-compile)
         ([remap recompile] . detached-compile-recompile))
        :custom
        (detached-shell-command-initial-input nil)
        (detached-terminal-data-command system-type)
        (detached-terminal-data-command system-type)
        :config
        (add-to-list 'display-buffer-alist
                     `((lambda (buffer-or-name arg)
                         (when (and (string-match-p
                                     (regexp-opt (list detached--shell-command-buffer))
                                     (cond
                                      ((stringp buffer-or-name) buffer-or-name)
                                      ((bufferp buffer-or-name) (buffer-name buffer-or-name))))
                                    my/detached-bury-buffer)
                           (setq my/detached-bury-buffer nil)
                           t))
                       (display-buffer-reuse-window)
                       (body-function
                        . (lambda (window) (interactive)
                            (bury-buffer))))))
      
      (defun my/org-link--open-shell-detached (path _)
        "Open a \"shell\" type.
      
      PATH is the command to execute with `detached-shell-command'."
        (if (or (and (org-string-nw-p org-link-shell-skip-confirm-regexp)
                     (string-match-p org-link-shell-skip-confirm-regexp path))
                (not org-link-shell-confirm-function)
                (funcall org-link-shell-confirm-function
                         (format "Execute %s in shell? "
                                 (org-add-props path nil 'face 'org-warning))))
            (progn
              (message "Executing %s" path)
              (detached-shell-command path))
          (user-error "Abort")))
      
      (with-eval-after-load 'ol
        (org-link-set-parameters "shell" :follow #'my/org-link--open-shell-detached))
      
      (use-package detached-list
        :after my-launch-app
        :bind (:map my/invoke-list-command-map
                    ("d" . detached-list-sessions))
        :config
        (bind-keys :map detached-list-mode-map
                   ("C-m" . detached-list-open-session)
                   ("C-j" . detached-list-open-session)))
      
    • emacs-guix   tools
      "emacs-guix"
      
      (use-package guix
        :commands guix
        :config
        (with-eval-after-load 'my-launch-app
          (bind-keys :map my/launch-app-map
                     ("x" . guix))))
      
    • pcre2el

      pcre2el is not only the converter between PCRE, emacs lisp style regexp and RX representation. It support re-builder as a conversion environment. Great!

      "emacs-pcre2el"
      
      (use-package pcre2el)
      
    • iedit   simultaneous region refactoring occurrence

      IEdit is not only replacement for M-%/C-M-% but it highlights symbol occurences to change. This advise would be useful for anyone who needs dedicated command to replace symbols in defun.

      "emacs-iedit"
      
      (use-package iedit
        :bind ("C-\"" . iedit-mode)
        :custom
        (iedit-toggle-key-default (kbd "C-\""))
        (iedit-auto-narrow t))
      
    • ledger-mode / flycheck-ledger   tools languages convenience
      "emacs-ledger-mode"
      "emacs-flycheck-ledger"
      
      (use-package ledger-mode
        :bind (:map ledger-mode-map
                    ("C-c C-y" . ledger-copy-transaction-at-point))
        :custom
        (ledger-reconcile-default-commodity "\\")
        :config
        (require 'flycheck-ledger)
        (with-eval-after-load 'ob-core
          (require 'ob-ledger)))
      
    • mentor   processes comm bittorrent

      mentor is a [BROKEN LINK: id:bdc987ad-419b-4f36-922d-de75207d85c0] client for emacs.

      Typical file downloading workflow:

      1. start mentor: M-x mentor
      2. add magnet link (URL or torrent file path): M-x mentor-download-load-magnet-link-or-url (l)
      3. view downloaded file in dired: M-x mentor-dired-jump (v)
      4. shutdown mentor: M-x mentor-shutdown (Q)
      "emacs-mentor"
      
      (use-package mentor
        :commands mentor
        :custom
        (mentor-rtorrent-download-directory "<<torrent-dir()>>")
        (mentor-rtorrent-keep-session t)
        (mentor-rtorrent-external-rpc "<<rtorrent-sock()>>"))
      
    • geiser   scheme languages geiser

      guix#The Perfect Setup

      "guile"
      "emacs-geiser"
      "emacs-geiser-guile"
      
      (use-package geiser-guile
        :commands (scheme-mode)
        :config
        (add-to-list 'geiser-guile-load-path
                     (file-name-concat "<<ghq-root()>>" "git.savannah.gnu.org/git/guix"))
        (with-eval-after-load 'yasnippet
          (add-to-list 'yas-snippet-dirs
                       (file-name-concat "<<ghq-root()>>" "git.savannah.gnu.org/git/guix/etc/snippets/yas")
                       t))
        (load-file (file-name-concat "<<ghq-root()>>" "git.savannah.gnu.org/git/guix/etc/copyright.el")))
      
    • consult-dash   docs dash consult
      "emacs-dash-docs"
      
      (use-package dash-docs
        :custom
        (dash-docs-docsets-path
         (file-name-concat "<<share-dir()>>" "dash/docsets")))
      
      (add-to-list 'load-path
                   (expand-file-name "elpa/consult-dash-20220621.226" user-emacs-directory))
      
      (use-package consult-dash
        :after dash-docs
        :bind ("M-s M-d" . consult-dash))
      
      (defmacro my/add-dash-docsets-hook (hook docsets)
        `(add-hook ,hook
                   (lambda ()
                     (when (functionp 'consult-dash)
                       (setq-local consult-dash-docsets ,docsets)))))
      
    • denote

      Denote leverages note taking in org mode by providing file naming scheme and backlink capability.

      "emacs-denote"
      
      (use-package denote
        :after my/org-mode-init
        :bind (("C-c n n" . denote)
               ("C-c n o" . denote-open-or-create))
        :custom
        (denote-directory (expand-file-name "~/org/notes"))
        :config
        (require 'denote-org-extras)
        (with-eval-after-load 'org-capture
          (setq denote-org-capture-specifiers "%l\n%i\n%?")
          (add-to-list 'org-capture-templates
                       '("n" "New note (with denote.el)" plain
                         (file denote-last-path)
                         #'denote-org-capture
                         :no-save t
                         :immediate-finish nil
                         :kill-buffer t
                         :jump-to-captured t))))
      
    • denote-menu
      "emacs-denote-menu"
      
      (use-package denote-menu
        :after denote
        :custom
        (denote-menu-date-column-width 12)
        (denote-menu-title-column-width 32)
        (denote-menu-show-file-type nil))
      
    • tree-sitter   ARCHIVE
    • xr   ARCHIVE
    • Whole Line Or Region   wp convenience
      ("https://github.com/purcell/whole-line-or-region/releases.atom" soft_update)
      
      (use-package whole-line-or-region
        :ensure t
        :config
        (whole-line-or-region-global-mode 1))
      
    • projectile   ARCHIVE ignore
    • counsel-projectile   ARCHIVE ignore
    • ag.el   ARCHIVE ignore
    • ripgrep.el   ARCHIVE ignore
    • wgrep   grep extensions edit
      "emacs-wgrep"
      
      (use-package wgrep
        :custom
        (wgrep-auto-save-buffer t)
        (wgrep-enable-key "e"))
      
    • dumb-jump   programming

      dumb-jump is "jump to definition" package without tagging.

      "emacs-dumb-jump"
      
      (use-package dumb-jump
        :after xref
        :config
        (add-to-list 'xref-backend-functions
                     #'dumb-jump-xref-activate)
        (bind-keys ("M-g ." . dumb-jump-go)
                   ("M-g ," . dumb-jump-back)
                   ("M-g l" . dumb-jump-quick-look)))
      
    • visual-regexp   ARCHIVE
    • avy   point location

      This post tell me how we can concoct efficient editing workflow with avy.

      "emacs-avy"
      
      (use-package avy
        :preface
        (defun avy-action-embark (pt)
          (unwind-protect
              (save-excursion
                (goto-char pt)
                (embark-act))
            (select-window
             (cdr (ring-ref avy-ring 0))))
          t)
        :config
        (setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark)
        (bind-keys ("M-g g" . avy-goto-line)
                   ("H-g" . avy-goto-line)))
      
      • avy-migemo
        (use-package avy-migemo
          :ensure t
          :after migemo
          :bind (("M-g M-a" . avy-migemo-goto-char-timer)
                 ("M-g a" . avy-migemo-goto-char-timer)
                 ("H-a" . avy-migemo-goto-char-timer)
                 :map isearch-mode-map
                 ("M-a" . avy-migemo-isearch)))
        
    • paredit   lisp

      Handy commands

      • press '(' to insert '()' pair
      • C-<left/right> to barf and slurp s-expressions
      • call paredit-splice-sexp to strip embracing parenthes
      • press M-<up> or M-r to delete upper sexp except pointing one
      • DEL, C-d, C-k act like originals but care for parenthes
      • press DEL to remedy unbalanced parenthes
      • press C-q ( to insert ( char manually
      "emacs-paredit"
      
      (use-package paredit
        :bind (:map paredit-mode-map
                    ("<backspace>" . paredit-backward-delete)
                    ("C-M-g" .  paredit-splice-sexp)
                    ("M-s" . nil)
                    ("C-M-;" . paredit-comment-dwim)
                    ("M-;" . nil))
        :hook (((emacs-lisp-mode lisp-mode scheme-mode) . enable-paredit-mode)))
      
      • workaround Thanks to this blog post, a couple of commands in paredit.el are fixed with transient-mark-mode

        (defvar my/paredit-delete-region-functions
          '(paredit-forward-delete
            paredit-backward-delete)
          "List of `paredit-mode' functions that should support tmm region deletion.")
        
        (defvar my/paredit-kill-region-functions
          '(paredit-forward-kill-word
            paredit-backward-kill-word)
          "List of `paredit-mode' functions that should support tmm region killing.")
        
        (defun my/paredit-fix-transient-mark-mode (orig-fn &rest args)
          "Allow deleting/killing a region if expression is balanced."
          (if (and transient-mark-mode
                   mark-active)
              (cond ((memq this-command my/paredit-delete-region-functions)
                     (paredit-delete-region (region-beginning) (region-end)))
                    ((memq this-command my/paredit-kill-region-functions)
                     (paredit-kill-region (region-beginning) (region-end)))
                    (t (apply orig-fn args)))
            (apply orig-fn args)))
        
        (dolist (fun (append my/paredit-delete-region-functions
                             my/paredit-kill-region-functions))
          (advice-add fun :around #'my/paredit-fix-transient-mark-mode))
        
    • lsp-mode   ARCHIVE
    • company-mode   ARCHIVE
    • flycheck   tools languages convenience
      "emacs-flycheck"
      
      (use-package flycheck
        :diminish
        :hook (prog-mode . flycheck-mode-on-safe)
        :custom
        (global-flycheck-mode t)
        (flycheck-check-syntax-automatically
         '(save new-line idle-change))
        (flycheck-display-errors-delay 10.0)
        (flycheck-checker-error-threshold 1000)
        (flycheck-textlint-config ".textlintrc")
        :config
        (add-hook 'flycheck-mode-hook
                  (lambda ()
                    (if (my/ascii-string-p (buffer-string))
                        (setq flycheck-textlint-config ".textlintrc")
                      (setq flycheck-textlint-config ".config/textlint/textlintrc_ja"))))
        (add-to-list 'display-buffer-alist
                     `(,flycheck-error-message-buffer
                       . (display-buffer-reuse-window
                          display-buffer-in-previous-window
                          display-buffer-pop-up-window
                          . ((window-height . 5))))))
      
    • twittering-mode   web  ARCHIVE
    • comment-dwim-2   ARCHIVE
    • undo-tree   undo tree redo history files convenience ARCHIVE
    • quickrun   ARCHIVE
    • expand-region
      "emacs-expand-region"
      
      (use-package expand-region
        :bind (("C-,"   . er/expand-region)
               ("C-M-," . er/contract-region))
        :config
        (push 'er/mark-outside-pairs er/try-expand-list))
      
    • helpful

      helpful-mode displays more sources compared to help-mode, including callers, debugging information, and source code.

      "emacs-helpful"
      
      (use-package helpful
        :after help
        :bind (:map help-map
                    ("C-u a" . helpful-callable)
                    ("C-u f" . helpful-function)
                    ("C-u s" . helpful-symbol)
                    ("C-u x" . helpful-command)
                    ("C-u v" . helpful-variable)
                    ("C-u k" . helpful-key)
                    ("C-u m" . helpful-macro)))
      
    • define-word   dictionary convenience
      ("https://github.com/abo-abo/define-word/releases.atom" soft_update)
      
      (use-package define-word
        :ensure t
        :defer t
        :bind (:map engine-mode-prefixed-map
                    ("n" . my/define-word))
        :preface
        (defvar my/define-word-minibuffer-history nil)
        (defun my/define-word (word)
          "Invoke `define-word' with dedicated minibuffer history."
          (interactive (list (determine-search-word "Word to define: " 'my/define-word-minibuffer-history)))
          (define-word word define-word-default-service))
        :custom
        (define-word-displayfn-alist
         '((wordnik . my/define-word--display-in-buffer)
           (openthesaurus . my/define-word--display-in-buffer)
           (webster . my/define-word--display-in-buffer)))
        (define-word-default-service 'wordnik)
        :config
        (setf (cdr (assoc 'wordnik define-word-services))
              '("http://wordnik.com/words/%s" my/define-word--parse-wordnik-all))
        (push '("<b>\\(.*?\\)</b>" bold)
              define-word--tag-faces)
        ;; fix issue #31 temporally
        (defun my/define-word--fix-31 (define-word-orig &rest args)
          "Fix `define-word' backends that require a user agent (like wordnik)."
          (let ((url-request-extra-headers
                 '(("User-Agent" .
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_5_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"))))
            (apply define-word-orig args)))
        (advice-add #'define-word :around #'my/define-word--fix-31))
      
      (defun my/define-word--display-in-buffer (definition)
        "docstring"
        (let* ((buf-name "*DEFINE WORD*")
               (buffer (get-buffer-create buf-name))
               (display-buffer-alist
                `((,buf-name
                   (display-buffer-same-window)))))
          (with-current-buffer buffer
            (read-only-mode -1)
            (erase-buffer)
            (insert definition)
            (goto-char (point-min))
            (save-excursion (xml-parse-string))
            (read-only-mode 1))
          (display-buffer buffer)))
      
      (defun my/define-word--parse-wordnik-example ()
        (save-excursion
          (let (beg
                results
                (define-word-limit 10))
            (re-search-forward "<h2>Examples" nil t)
            (save-excursion
              (setq beg (re-search-forward "<div class=\"relatedWords-module\">" nil t)))
            (while (re-search-forward "<p class=\"text\">\\(.*\\)</p>" beg t)
              (push "---" results)
              (push (match-string 1) results))
            (when (setq results (nreverse results))
              (define-word--convert-html-tag-to-face (define-word--join-results results))))))
      
      (defvar my/define-word-part-map-alist '(("adjective" "adj.")
                                              ("adverb" "adv.")
                                              ("intransitive verb" "v.i.")
                                              ("transitive verb" "v.t.")))
      
      
      (defun my/define-word--parse-wordnik ()
        (let ((str (define-word--parse-wordnik)))
          (with-temp-buffer
            (insert str)
            (mapc (lambda (abbrev-map)
                    (goto-char (point-min))
                    (while (re-search-forward (format "\\(%s\\)" (car abbrev-map)) nil t)
                      (let ((match (match-string 1)))
                        (replace-match
                         (propertize (cadr abbrev-map) 'face (text-properties-at (point)))))))
                  my/define-word-part-map-alist)
            (buffer-string))))
      
      (defun my/define-word--parse-wordnik-related-word ()
        "docstring"
        (save-excursion
          (save-match-data
            (seq-reduce
             (lambda (accum group)
               (let (results
                     (define-word-limit 20))
                 (if (re-search-forward (format "<h3>%s" group) nil t)
                     (progn (save-excursion
                              (setq beg (re-search-forward "<div class=\"related-group-header clearfix\">" nil t)))
                            (push (concat group ":") results)
                            (while (re-search-forward "<span data-definition-for=\"\\([^\"]*\\)\"" beg t)
                              (push (concat "- " (match-string 1)) results)
                              (re-search-forward "</span>"))
                            (concat (when accum (concat accum "\n\n"))
                                    (when (setq results (nreverse results))
                                      (define-word--convert-html-tag-to-face (define-word--join-results results)))))
                   accum)))
             '("antonym" "equivalents" "hypernyms" "hyponyms" "same context")
             nil))))
      
      (defun my/define-word--parse-wordnik-all ()
        ""
        (let* ((def (funcall #'my/define-word--parse-wordnik))
               (rel (funcall #'my/define-word--parse-wordnik-related-word))
               (exp (funcall #'my/define-word--parse-wordnik-example)))
          (concat ;; "Definitions:\n" (funcall #'define-word--parse-wordnik) "\n\n"
           (when def (format "Definitions:\n%s\n\n" def))
           (when rel (format "%s\n\n" rel))
           (when exp (format "Examples:\n%s\n" exp)))))
      
    • synosaurus
      "emacs-synosaurus"
      
      (use-package synosaurus
        :bind (:map engine-mode-prefixed-map
                    ("s" . synosaurus-lookup))
        :custom
        (synosaurus-choose-method 'default))
      
    • exec-path-from-shell   unix environment

      A GNU Emacs library to ensure environment variables inside Emacs look the same as in the user's shell.

      "emacs-exec-path-from-shell"
      
      (use-package exec-path-from-shell
        :config
        (when (memq window-system '(mac))
          (exec-path-from-shell-initialize)))
      
    • migemo.el

      migemo allows me to search incrementally Japanese words using roma-ji expression.

      ("https://github.com/emacs-jp/migemo/releases.atom" soft_update)
      
      (use-package migemo
        :ensure t
        :ensure-system-package cmigemo
        :custom
        (migemo-command "cmigemo")
        (migemo-options '("-q" "--emacs"))
        (migemo-directory
         (file-name-concat "<<migemo-dict-path()>>" "utf-8"))
        (migemo-user-dictionary nil)
        (migemo-coding-system 'utf-8-unix)
        (migemo-regex-dictionary nil)
        :config
        (migemo-init))
      
      • ivy-migemo   ARCHIVE ignore
    • jaword

      Jaword.elはbackward-wordやforward-wordを日本語に対応させる機能を提供します。これらのコマンドはデフォルトではひらがな・カタカナ・漢字の境界を単語の区切りとするのに対し、jaword.elを使用するとより正確に日本語の単語単位での移動が可能です。

      ("https://github.com/zk-phi/jaword/releases.atom" soft_update)
      
      (use-package jaword
        :ensure t
        :custom
        (jaword-enable-subword t)
        :hook ((skk-mode
                . (lambda ()
                    (jaword-mode 1)))
               (input-method-activate
                . (lambda ()
                    (when (string= current-input-method "japanese-skk")
                      (jaword-mode 1))))
               (input-method-deactivate
                . (lambda ()
                    (when (string= current-input-method "japanese-skk")
                      (jaword-mode -1))))))
      
    • hungry-delete   ARCHIVE
    • aggressive-indent-mode   tools maint lisp indent
      "emacs-aggressive-indent"
      
      (use-package aggressive-indent
        :diminish (aggressive-indent-mode . "ai")
        :hook ((emacs-lisp-mode scheme-mode) . aggressive-indent-mode))
      
    • adaptive-wrap

      adaptive-wrap and visual-line-mode are active in a bundle in a bunch of major modes.

      "emacs-adaptive-wrap"
      
      (use-package adaptive-wrap)
      
    • restart-emacs   convenience ARCHIVE
    • pangu-spacing   ARCHIVE
    • which-key   ARCHIVE
    • hydra   ARCHIVE
    • pass   store password password keychain

      Major mode for manupulating password store file.

      "emacs-pass"
      
      (use-package pass
        :commands pass
        :bind (:map pass-mode-map
                    ("C-j" . pass-view)))
      
    • password-store   tools store password password pass
      "emacs-password-store"
      
      (use-package password-store
        :custom
        (password-store-time-before-clipboard-restore 25))
      
    • nov.el   multimedia hypermedia epub
      "emacs-nov-el"
      
      (use-package nov
        :mode ("\\.epub$" . nov-mode)
        :hook (nov-mode . nov-abbreviate-file-name)
        :custom
        (nov-variable-pitch nil)
        (nov-text-width t)
        :config
        (bind-keys :map nov-mode-map
                   ("C-j" . nov-browse-url)))
      
      (defun nov-abbreviate-file-name ()
        "Shorten `nov-file-name' using `directory-abbrev-alist'."
        (setq nov-file-name (abbreviate-file-name nov-file-name)))
      
    • yasnippet   emulation convenience
      "emacs-yasnippet"
      "emacs-yasnippet-snippets"
      
      (use-package yasnippet
        :disabled t
        :bind (:prefix "C-c y"
                       :prefix-map my/yas-map
                       :prefix-docstring "Keymap for YASnippet family."
                       ("v" . yas-visit-snippet-file)
                       ("n" . yas-new-snippet)
                       ("i" . yas-insert-snippet)
                       ("TAB" . yas-insert-snippet))
        :hook
        ((prog-mode text-mode) . yas-minor-mode)
        (after-init . yas-reload-all)
        :custom
        (yas-triggers-in-field t)
        (yas-wrap-around-region t)
        :config
        (require 'yasnippet-snippets))
      
    • auto-yasnippet
      "emacs-auto-yasnippet"
      
      (use-package auto-yasnippet
        :disabled t
        :after yasnippet
        :config
        (bind-keys :map my/yas-map
                   ("a"   . aya-create)
                   ("A"   . aya-expand)
                   ("C-s" . aya-persist-snippet)))
      
    • yankpad   convenience abbrev ARCHIVE
    • pdf-tools   multimedia files ARCHIVE
    • rainbow-mode   faces
      "emacs-rainbow-mode"
      
      (use-package rainbow-mode
        :diminish
        (rainbow-mode . "rb")
        :hook
        ((css-mode scss-mode php-mode html-mode) . rainbow-mode)
        :custom
        (rainbow-html-colors t)
        (rainbow-x-colors t)
        (rainbow-latex-colors t)
        (rainbow-ansi-colors t))
      
    • alert   notification message emacs
      "emacs-alert"
      
      (use-package alert
        :config
        (set-face-background 'alert-saved-fringe-face nil)
        (alert-add-rule :status '(buried)
                        :mode   'mu4e-alert
                        :style  'fringe)
        (alert-add-rule :status '(buried)
                        :mode   'dired-mode
                        :style  'fringe)
        (alert-add-rule :status '(buried)
                        :mode   'org-mode
                        :style  'libnotify))
      
    • ts   lisp calendar
      "emacs-ts"
      
      (use-package ts)
      
    • app-launcher
      "emacs-app-launcher"
      
      (use-package app-launcher
        :after my-launch-app
        :bind (:map my/launch-app-map
                    ("a" . app-launcher-run-app)))
      
    • tldr   tools docs

      I ran into a problem with downloading TL;DR document. I forcibly download, uncompress and locate that to `tldr-directory-path'.

      "emacs-tldr"
      
      (use-package tldr
        :commands tldr
        :custom
        (tldr-use-word-at-point t))
      
    • burly   convenience

      Burly.el is capable of saving and opening Emacs frames and windows by describing them as URL which can be stored in bookmark. You can use Burly as a lightweight workspace tool.

      "emacs-burly"
      
      (require 'burly)
      
    • tempel   wp tools languages abbrev
      "emacs-tempel"
      ;; "emacs-tempel-collection"
      
      (use-package tempel
        :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
               ("M-_" . tempel-insert))
        :hook
        ((conf-mode prog-mode text-mode minibuffer-mode) . tempel-setup-capf)
        (mu4e-compose-mode . tempel-abbrev-mode)
        :preface
        (defun tempel-setup-capf ()
          (setq-local completion-at-point-functions
                      (cons #'tempel-expand
                            completion-at-point-functions)))
        :custom
        (tempel-path (list (no-littering-expand-etc-file-name "tempel/templates/*.eld")
                           (no-littering-expand-etc-file-name "tempel/templates/*/*.eld")))
        :config
        (bind-keys :map tempel-map
                   ("TAB" . tempel-next)
                   ("M-TAB" . tempel-previous)
                   ("C->" . tempel-next)
                   ("C-<" . tempel-previous))
        (with-eval-after-load 'cape
          (bind-keys :map my/cape-map
                     ("t" . tempel-expand))))
      
      (use-package tempel-collection
        :ensure t
        :after tempel)
      
    • literate calc mode   tools languages calc
      "emacs-literate-calc-mode"
      
      (use-package literate-calc-mode
        :bind ("C-c M-=" . literate-calc-mode))
      
    • git-link
      "emacs-git-link"
      
      (use-package git-link)
      
    • jinx
      "emacs-jinx"
      
      (use-package jinx
        :bind (("M-$" . jinx-correct)
               ("C-M-$" . jinx-languages))
        :config
        (bind-keys :map jinx-mode-map
                   ("M-n" . jinx-next)
                   ("M-p" . jinx-previous)))
      
    • simple-httpd
      "emacs-simple-httpd"
      
      (use-package simple-httpd
        :commands (httpd-start httpd-serve-directory))
      
    • logos
      "emacs-logos"
      
      (use-package logos
        :config
        (require 'fingertip)
        :preface
        (defun logos-toggle-read-only ()
          "Toggle read only state in `logos-focus-mode'"
          (interactive)
          (let ((current-ro-state logos-buffer-read-only))
            (logos-focus-mode -1)
            (setq logos-buffer-read-only (not current-ro-state))
            (logos-focus-mode 1)
            (unless logos-buffer-read-only
              (fingertip-mode -1))
            (message "Buffer is %s"
                     (if logos-buffer-read-only
                         "read-only" "editable"))))
        :bind (("H-;" . logos-focus-mode)
               ("H-n" . logos-narrow-dwim)
               :map logos-focus-mode-map
               ("H-e" . logos-toggle-read-only))
        :hook (((help-mode helpful-mode Info-mode eww-mode nov-mode mu4e-view-mode
                           woman-mode mastodon-mode Custom-mode devdocs-mode)
                . logos-focus-mode)
               (logos-focus-mode
                . (lambda () (if logos-focus-mode
                             (fingertip-mode 1)
                           (setq logos-buffer-read-only t)
                           (fingertip-mode -1)))))
        :custom
        (logos-scroll-lock nil)
        (logos-variable-pitch nil)
        (logos-hide-cursor nil)
        (logos-hide-mode-line t)
        (logos-hide-buffer-boundaries t)
        (logos-buffer-read-only t)
        (logos-olivetti nil)
        (logos-hide-fringe t))
      
    • editorconfig
      "emacs-editorconfig"
      
      (use-package editorconfig)
      
    • elisp-demos
      "emacs-elisp-demos"
      
      (use-package elisp-demos
        :bind (:map help-map
                    ("O" . elisp-demos-find-demo))
        :config
        (require 'helpful)
        (bind-keys :map helpful-mode-map
                   ("O" . elisp-demos-for-helpful)))
      
    • yeetube
      "emacs-yeetube"
      
      (use-package yeetube
        :after engine-mode
        :bind (:map engine-mode-prefixed-map
                    ("Y" . yeetube-search))
        :custom
        (yeetube-download-directory "<<media-dir()>>")
        (yeetube-default-sort-column "Date")
        (yeetube-display-thumbnails nil)
        :config
        (keymap-set yeetube-mode-map "Y"
                    (lambda () (interactive)
                      "Dwonload a video at point using `yt-dlp-dispatch-dwim'"
                      (yt-dlp-dispatch-dwim (yeetube-get-url))))
        (keymap-set yeetube-mode-map "P"
                    (lambda () (interactive)
                      "Play a video at point using `yt-dlp-dispatch-dwim'"
                      (yt-dlp-dispatch-dwim (yeetube-get-url) nil nil t)
                      (dispatch-execute-macro))))
      
    • consult-notes   wp convenience ARCHIVE
    • gnuplot
      "gnuplot"
      
      "emacs-gnuplot"
      
      (use-package gnuplot
        :defer t
        :hook
        (gnuplot-mode . (lambda ()
                          (setq-local devdocs-current-docs '("gnuplot")))))
      
    • treesit-auto
      (use-package treesit-auto
        :ensure t
        :config
        (treesit-auto-add-to-auto-mode-alist 'all))
      
    • devdocs
      "emacs-devdocs"
      
      (use-package devdocs
        :bind (:map help-map
                    ("M-d" . devdocs-lookup)))
      
    • unfill
      "emacs-unfill"
      
      (use-package unfill
        :bind ("M-Q" . unfill-toggle))
      
    • htmlize
      "emacs-htmlize"
      
      (funcall (if (= full-path 0) #'identity #'expand-file-name)
               (file-name-concat no-littering-var-directory "htmlize"))
      
      <<htmlize-dir()>>
      
      d <<htmlize-dir(full-path=1)>> - - - 5d
      

      EWW is a nice place for intensive reading. When there are articles or documents in a format other than HTML, I convert them to HTML and browse them in EWW with my/view-htmlized.

      (use-package htmlize
        :bind ("C-!" . my/view-htmlized)
        :custom
        (Htmlize-force-inline-images t)
        :init
        (with-eval-after-load 'org-keys
          (add-to-list 'org-speed-commands
                       '("!" . my/view-htmlized)
                       t))
        :config
        (defun my/view-htmlized ()
          (interactive)
          (pcase-let* ((`(,fname . ,write-proc)
                        (if (derived-mode-p 'org-mode)
                            (cons (format "%s.html"
                                          (secure-hash 'md5 (org-id-get-create)))
                                  (lambda (file)
                                    (let ((org-export-show-temporary-export-buffer nil)
                                          (org-export-preserve-breaks t)
                                          (org-export-use-babel nil))
                                      (org-html-export-as-html nil (if (org-before-first-heading-p) nil t) nil)
                                      (with-current-buffer "*Org HTML Export*"
                                        (write-file file)))))
                          (cons (format "%s.html"
                                        (secure-hash 'md5
                                                     (apply 'buffer-substring-no-properties
                                                            (if (region-active-p)
                                                                (list (region-beginning) (region-end))
                                                              (list (point-min) (point-max))))))
                                (lambda (file)
                                  (let ((region-text (apply 'htmlize-region-for-paste
                                                            (if (region-active-p)
                                                                (list (region-beginning) (region-end))
                                                              (list (point-min) (point-max))))))
                                    (with-temp-buffer
                                      (insert region-text)
                                      (write-file file nil)))))))
                       (file-path (file-name-concat "<<htmlize-dir()>>" fname)))
            (when (or (not (file-exists-p file-path))
                      (derived-mode-p 'org-mode))
              (apply write-proc (list file-path)))
            (browse-url-of-file file-path))))
      
    • everywhere   frames convenience
      "emacs-everywhere"
      "xdotool"
      
      (use-package emacs-everywhere
        :hook (emacs-everywhere-mode
               . (lambda () (when emacs-everywhere-mode
                          (set-frame-parameter nil 'fullscreen 'fullboth)))))
      
      emacsclient -s server-<<hash-string(seed="server", len=6)>> --eval "(emacs-everywhere)"
      
    • Eat
      "emacs-eat"
      
      (use-package eat
        :commands (eat eat-eshell-mode)
        :hook (eshell-load . eat-eshell-mode))
      
    • japanese-holidays   calendar

      japanese-holidays

      (use-package japanese-holidays
        :ensure t
        :preface
        (defun my/japanese-holiday-show (&rest _args)
          "Show holiday information in mini buffer if date on which the cursor is any holidays."
          (let* ((date (calendar-cursor-to-date t))
                 (calendar-date-display-form '((format "%s年 %s月 %s日(%s)" year month day dayname)))
                 (date-string (calendar-date-string date))
                 (holiday-list (calendar-check-holidays date)))
            (when holiday-list
              (message "%s: %s" date-string (mapconcat #'identity holiday-list "; ")))))
        :custom-face
        (japanese-holiday-saturday ((t (:foreground "blue" :background nil))))
        :custom
        (japanese-holiday-weekend '(0 6))
        (japanese-holiday-weekend-marker
         '(holiday nil nil nil nil nil japanese-holiday-saturday))
        :config
        (setf calendar-holidays
              (append japanese-holidays holiday-local-holidays holiday-other-holidays))
        (with-eval-after-load 'calendar
          (add-hook 'calendar-today-visible-hook
                    #'japanese-holiday-mark-weekend)
          (add-hook 'calendar-today-invisible-hook
                    #'japanese-holiday-mark-weekend)
          (add-hook 'calendar-move-hook
                    #'my/japanese-holiday-show)))
      

further extension

  • utility macros/functions
    • lambda key

      lambda-key is nice idea to remedy lambda bindings with define-key.

      (defun lambda-key (keymap key def)
        "Wrap`define-key' to provide documentation."
        (set 'sym (make-symbol (documentation def)))
        (fset sym def)
        (keymap-set keymap key sym))
      
      (lambda-key "(lambda-key " (p "keymap") " \"" (p "key") "\"" n>
                  "(lambda () \"" (p "desc") "\"" n> "(interactive)" n>
                  p "))")
      
    • computational
      (defun my/combinations (list)
        "Return all combinations for all elements in LIST."
        (if (null list)
            (list nil)
          (let ((rest-combs (my/combinations (cdr list))))
            (append (mapcar (lambda (combination) (cons (car list) combination))
                            rest-combs)
                    rest-combs))))
      
    • copy line number in kill ring
      (defun my/copy-line-number ()
        "Copy the line number of current point into the kill ring."
        (interactive)
        (let ((line-number (number-to-string (line-number-at-pos))))
          (message "Line number: %s" line-number)
          (kill-new line-number)))
      (keymap-global-set "C-M-S-w" 'my/copy-line-number)
      
    • expand shorthand time string
      (defun my/expand-shorthand-time (time-string)
        "Expand shorthand expressions in TIME-STRING."
        (let ((case-fold-search nil))
          (rx-let ((follow-digit (char)
                                 (: (group (+ digit)) (* space) char
                                    (group (or eol (not alpha))))))
            (seq-reduce (lambda (result element)
                          (replace-regexp-in-string (car element) (cadr element) result))
                        `((,(rx (follow-digit "y")) "\\1year\\2")
                          (,(rx (follow-digit "M")) "\\1month\\2")
                          (,(rx (follow-digit "w")) "\\1week\\2")
                          (,(rx (follow-digit "d")) "\\1day\\2")
                          (,(rx (follow-digit "h")) "\\1hour\\2")
                          (,(rx (follow-digit "m")) "\\1minute\\2")
                          (,(rx (follow-digit "s")) "\\1second\\2"))
                        time-string))))
      

      unit test

      (ert-deftest test/expand-shorthand-time ()
        "Tests the output string of `my/expand-shorthand-time'."
        (should (string= (my/expand-shorthand-time "1m")
                         "1minute"))
        (should (string= (my/expand-shorthand-time "1m 20s")
                         "1minute 20second"))
        (should (string= (my/expand-shorthand-time " 2h10m ")
                         " 2hour10minute ")))
      
    • org link at point map
      (defun org-link-at-point-map (function)
        "Call FUNCTION with a list whose elements are the URL and title
      obtained from the org-link at the current point."
        (when-let* ((context (org-element-lineage
                              (org-element-context)
                              '(link)
                              t))
                    (type (org-element-property :type context))
                    (path (org-element-property :path context))
                    (desc (when-let ((begin (org-element-property :contents-begin context))
                                     (end (org-element-property :contents-end context)))
                            (buffer-substring begin end))))
          (cond
           ((string-match-p "https?" type)
            (funcall function (list (org-link-unescape (concat type ":" path)) desc)))
           ((string-match-p "elfeed" type)
            (save-excursion
              (org-open-at-point)
              (when (eq major-mode 'elfeed-show-mode)
                (when-let ((url (or (caar (elfeed-entry-enclosures elfeed-show-entry))
                                    (elfeed-entry-link elfeed-show-entry)))
                           (title (elfeed-entry-title elfeed-show-entry)))
                  (funcall function (list url title)))
                (quit-window)))))))
      
    • play sound
      "mpg321"
      "vorbis-tools"
      
      (defun my/play-sound (sound-file &optional background-p)
        "Play SOUND-FILE.
      
      The job is executed in the background if BACKGROUND-P is non-nil."
        (call-process-shell-command
         (concat
          (mapconcat #'shell-quote-argument
                     (list (cond
                            ((string-suffix-p ".mp3" sound-file)
                             "mpg321")
                            ((string-suffix-p ".oga" sound-file)
                             "ogg123")
                            (t
                             "mpv --no-config"))
                           sound-file)
                     " ")
          (when background-p "&"))))
      
    • determine search word macro
      (defmacro determine-search-word (&optional prompt history)
        "Return word at point unless region is active using PROMPT and HISTORY."
        `(downcase
          (if (use-region-p)
              (buffer-substring-no-properties (region-beginning) (region-end))
            (read-string ,(or prompt "Search word: ") (downcase (or (word-at-point t) "")) "" ,history))))
      
  • patches and workarounds
  • repeat maps
    • dired subdir repeat map
      (with-eval-after-load 'dired
        (defvar-keymap my/dired-subdir-map
          :doc "Keymap to repeat commands on dired-subdir."
          :repeat (:enter (dired-next-subdir
                           dired-prev-subdir
                           dired-goto-subdir)
                          :exit (dired-kill-subdir
                                 dired-open-subdir))
          "n" #'dired-next-subdir
          "p" #'dired-prev-subdir
          "k" #'dired-kill-subdir
          "g" #'dired-goto-subdir
          "TAB" #'dired-hide-subdir
          "RET" #'dired-open-subdir))
      
    • visual cycling repeat map
      (use-package emacs
        :no-require t
        :bind-keymap
        ("H-c" . my/cycle-visual-repeat-map)
        :preface
        (defvar-keymap my/cycle-visual-repeat-map
          :doc "Keymap to repeat toggle/cycling appearance.  Used in `repeat-mode'."
          :repeat t
          "#" #'display-line-numbers-mode
          "SPC" #'cycle-spacing
          "V" #'visual-line-mode
          "F" #'toggle-frame-fullscreen
          "P" #'variable-pitch-mode
          "L" #'line-number-mode
          "C" #'column-number-mode
          "R" #'rainbow-mode
          "TAB" #'adaptive-wrap-prefix-mode
          "+" #'text-scale-adjust
          "-" #'text-scale-adjust
          "0" (lambda () "Set text scale to default level." (interactive)
                (text-scale-set 0))))
      
    • frame repeat map
      (defvar-keymap my/frame-repeat-map
        :doc "Keymap to repeat frame commands.  Used in `repeat-mode'."
        :repeat (:enter (other-frame))
        "o" #'other-frame
        "0" #'delete-frame
        "1" #'delete-other-frames
        "2" #'make-frame-command
        "5" #'other-frame-prefix
        "B" #'bookmark-jump-other-frame
        "p" #'project-other-frame-command
        "D" #'dired-other-frame
        "m" #'compose-mail-other-frame)
      
    • delete repeat map

      Suport successive deletion with C-h/M-h

      (defun my/delete-char-dwim ()
        (interactive)
        (if (eq major-mode 'org-mode)
            (org-delete-char 1)
          (delete-char 1)))
      (defun my/delete-backward-char-dwim ()
        (interactive)
        (if (eq major-mode 'org-mode)
            (org-delete-backward-char 1)
          (delete-backward-char 1)))
      (defun my/kill-word-dwim ()
        (interactive)
        (if jaword-mode
            (jaword-kill 1)
          (kill-word 1)))
      (defun my/backward-kill-word-dwim ()
        (interactive)
        (if jaword-mode
            (jaword-kill -1)
          (backward-kill-word 1)))
      
      (defvar-keymap my/delete-repeat-map
        :doc "Keymap to repeat deletion commands.  Used in `repeat-mode'."
        :repeat (:enter (delete-char
                         org-delete-char
                         kill-word
                         jaword-kill
                         my/delete-char-dwim
                         my/delete-backward-char-dwim
                         my/kill-word-dwim
                         my/backward-kill-word-dwim))
        "C-d" #'my/delete-char-dwim
        "C-h" #'my/delete-backward-char-dwim
        "M-d" #'my/kill-word-dwim
        "M-h" #'my/backward-kill-word-dwim)
      
  • tailored commands/functions
    • update elfeed feeds automatically

      elfeed-update will run at morning everyday

      emacsclient -s server-<<hash-string(seed="server", len=6)>> --eval "(my/elfeed-update)"
      
      (defun my/elfeed-update ()
        "Update Elfeed feeds and compress the database."
        (interactive)
        (elfeed)
        (elfeed-db-compact)
        (elfeed-update)
        (when (eq major-mode 'elfeed-search-mode)
          (elfeed-kill-buffer)))
      
    • frame maximization

      Every frame will be spawn in maximized and initial frame will be full screen at Emacs start-up time.

      (use-package frame
        :if (member (window-system) '(x ns w32))
        :init
        (add-to-list 'after-make-frame-functions
                     (lambda (frame) (set-frame-parameter frame 'fullscreen 'maximized)))
        :hook
        (emacs-startup . (lambda () (set-frame-parameter nil 'fullscreen 'fullboth))))
      
    • prune old .eln files at shut down   ARCHIVE
    • operate the number at point

      EmacsWiki: Increment Number

      (defmacro my/operate-number (operation)
        `(save-excursion
           (save-restriction
             (when (use-region-p)
               (narrow-to-region (region-beginning) (region-end)))
             (skip-chars-backward "0-9")
             (skip-chars-forward "0")
             (or (looking-at "[0-9]+")
                 (error "No number at point"))
             (replace-match (number-to-string
                             (apply ,operation (list (string-to-number (match-string 0)))))))))
      
      (defun my/increment-number-at-point ()
        "Increment the number at point.
      
      If there is a region which embrace a number, that number will be
      incremented instead."
        (interactive)
        (my/operate-number #'1+))
      
      (defun my/decrement-number-at-point ()
        "Decrement the number at point.
      
      If there is a region which embrace a number, that number will be
      decremented instead."
        (interactive)
        (my/operate-number #'1-))
      
      (keymap-global-set "C-x <up>" #'my/increment-number-at-point)
      (keymap-global-set "C-x <down>" #'my/decrement-number-at-point)
      (defvar-keymap my/operate-number-repeat-map
        :doc "Keymap to repeat operating a number."
        :repeat (:enter (my/increment-number-at-point
                         my/decrement-number-at-point))
        "<up>" #'my/increment-number-at-point
        "<down>" #'my/decrement-number-at-point)
      
    • edit local file as root (sudo)

      my/sudo-edit-local-file is inspired by this thisblog post.

      (defun my/sudo-edit-local-file ()
        "Reopen the current buffer file with root privileges."
        (interactive)
        (when (file-exists-p buffer-file-name)
          (find-alternate-file
           (concat "/sudo:root@localhost:"
                   buffer-file-name))))
      
      (keymap-global-set "C-x C-#" #'my/sudo-edit-local-file)
      
    • power management

      Shut down emacs automatically when remaining power of laptop PC lower than battery-load-low to avoid accidental data loss.

      (require 'battery)
      
      (run-with-timer 60 60 #'kill-emacs-auto)
      
      (defun kill-emacs-auto ()
        "Kill Emacs if remaining machine power lower than `battery-load-low'."
        (when-let* ((data (and battery-status-function (funcall battery-status-function)))
                    (percentage (car (read-from-string (cdr (assq ?p data)))))
                    (power (cdr (assq ?L data))))
          (when (and (numberp percentage)
                     (< percentage battery-load-low)
                     (stringp power)
                     (string= power "BAT"))
            (save-buffers-kill-emacs t))))
      
    • lazy viewing   ARCHIVE
    • lookup functions   ARCHIVE
    • tldr
      (defun my/tldr (command)
        "Show the output of tldr for COMMAND in a dedicated buffer."
        (interactive (list (read-string "Coomand: ")))
        (let* ((buf-name "*TL;DR*")
               (buffer (get-buffer-create buf-name)))
          (with-current-buffer buffer
            (read-only-mode -1)
            (erase-buffer)
            (insert (shell-command-to-string (format "tldr %s"
                                                     (shell-quote-argument command))))
            (ansi-color-apply-on-region (point-min) (point-max))
            (goto-char (point-min))
            (read-only-mode 1))
          (display-buffer buffer '((display-buffer-pop-up-window)
                                   . ((window-height . 8))))
          (switch-to-buffer-other-window buffer)))
      
    • download media file   ARCHIVE
    • Text-to-Speech using AI (tts.el)

      tts.el provides functions to say out given text using the OpenAI Text to Speech service through its API. The vips project is referenced when implementation. The vips project was helpful during the implementation process.

      ;;; tts.el --- Text to speech -*- lexical-binding: t -*-
      
      ;; Author: p-snow
      ;; Version: 0.0.1
      ;; Package-Requires: ((emacs "29.1") (request "0.3.0"))
      ;; Homepage: https://github.com/p-snow/tts
      ;; Keywords: media
      
      ;; This file is not part of GNU Emacs
      
      ;; This program is free software: you can redistribute it and/or modify
      ;; it under the terms of the GNU General Public License as published by
      ;; the Free Software Foundation, either version 3 of the License, or
      ;; (at your option) any later version.
      
      ;; This program is distributed in the hope that it will be useful,
      ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
      ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      ;; GNU General Public License for more details.
      
      ;; You should have received a copy of the GNU General Public License
      ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
      
      ;;; Commentary:
      
      ;; commentary
      
      ;;; Code:
      
      (require 'request)
      
      (defun tts-openai-request (api-key input action)
        "Return audio file content from OpenAI API and save it to OUTPUT-FILE or play it.
      INPUT is the text string sent to the API.
      RESPONSE-FORMAT is the format of the audio file, defaults to 'mp3'.
      ACTION is either 'save or 'play, determining what to do with the response."
        (let* ((auth-value (format "Bearer %s" api-key))
               (model "tts-1")
               (voice "nova")
               (speed nil)
               (response-format "opus")
               (url "https://api.openai.com/v1/audio/speech")
               (data (json-encode `(("model" . ,model)
                                    ("input" . ,input)
                                    ("voice" . ,voice)
                                    ,@(when response-format `(("response_format" . ,response-format)))
                                    ,@(when speed `(("speed" . ,speed)))))))
          (request
            url
            :type "POST"
            :data data
            :headers `(("Authorization" . ,auth-value) ("Content-Type" . "application/json"))
            :parser 'buffer-string
            :success (cl-function
                      (lambda (&key data &allow-other-keys)
                        (let ((coding-system-for-write 'binary)
                              (speech-file (make-temp-file "tts-" nil ".opus")))
                          (with-temp-file speech-file
                            (insert data))
                          (when action (funcall action speech-file)))))
            :error (cl-function
                    (lambda (&key error-thrown &allow-other-keys)
                      (message "Error: %S" error-thrown))))))
      
      (defun tts-play (speech-file)
        ""
        (play-sound-file speech-file))
      
      (provide 'tts)
      ;;; tts.el ends here
      
      (require 'tts)
      
      (keymap-global-set "C-x C-\"" #'my/tts-say-it)
      (with-eval-after-load 'embark
        (keymap-set embark-region-map "\"" #'my/tts-say-it))
      
      (defun my/tts-say-it (text)
        ""
        (interactive (list (cond
                            ((region-active-p)
                             (buffer-substring-no-properties (region-beginning) (region-end)))
                            (sentence-at-point))))
        (tts-openai-request (my/apikey)
                            text
                            #'my/tts-play))
      
      (defun my/tts-play (speech-file)
        ""
        (start-process-shell-command "tts" nil (format "mpv --keep-open=no --no-pause --force-window=no %1$s; rm -f %1$s"
                                                       (shell-quote-argument speech-file))))
      
      • jsay   ARCHIVE
      • esay   ARCHIVE
    • wrap up launch app commands in single keymap
      (keymap-global-set "H-j"
                         (define-keymap
                           :prefix 'my/launch-app-map
                           "v" #'my/multi-vterm
                           "d" #'dired-jump
                           "w" #'eww
                           "m" #'mu4e
                           "p" #'proced
                           "P" #'list-processes
                           "?" #'woman
                           "l" (define-keymap
                                 :prefix 'my/invoke-list-command-map)))
      
      (provide 'my-launch-app)
      
    • show the day in calendar
      (defun my/calendar-show-items ()
        "Show items on the day pointed in calendar view."
        (interactive)
        (let* ((calendar-date-display-form calendar-iso-date-display-form)
               (date (calendar-date-string (calendar-cursor-to-date)))
               (d (parse-time-string date))
               (year (decoded-time-year d))
               (files (file-expand-wildcards (file-name-concat org-directory "archive" (format "archive_%d.org" year))))
               (buffer-name-prefix "==")
               (buffer-name (concat buffer-name-prefix date))
               (display-buffer-alist
                `((,buffer-name-prefix
                   (display-buffer-reuse-mode-window
                    display-buffer-pop-up-window)
                   (window-height . 7)))))
          (org-ql-search files `(and (or (parent (heading ,date))
                                         (ts-a :on ,date))
                                     (not (tags "web" "drill")))
                         :buffer buffer-name)
          (other-window 1)))
      
      (with-eval-after-load 'calendar
        (bind-keys :map calendar-mode-map
                   ("v" . my/calendar-show-items)))
      
    • replace regexp with re-builder and query-replace-regexp trick

      Great idea using re-builder when query-replace-regexp borrowed from this post.

      (defvar my/re-builder-positions nil
        "Store point and region bounds before calling re-builder")
      
      (advice-add 're-builder
                  :before
                  (defun my/re-builder-save-state (&rest _)
                    "Save into `my/re-builder-positions' the point and region
      positions before calling `re-builder'."
                    (setq my/re-builder-positions
                          (cons (point)
                                (when (region-active-p)
                                  (list (region-beginning)
                                        (region-end)))))))
      
      (defun reb-replace-regexp (&optional delimited)
        "Run `query-replace-regexp' with the contents of re-builder. With
      non-nil optional argument DELIMITED, only replace matches
      surrounded by word boundaries."
        (interactive "P")
        (reb-update-regexp)
        (let* ((re (reb-cook-regexp (reb-read-regexp)))
               (replacement (query-replace-read-to
                             re
                             (concat "Query replace"
                                     (if current-prefix-arg
                                         (if (eq current-prefix-arg '-) " backward" " word")
                                       "")
                                     " regexp"
                                     (if (with-selected-window reb-target-window
                                           (region-active-p)) " in region" ""))
                             t))
               (pnt (car my/re-builder-positions))
               (beg (cadr my/re-builder-positions))
               (end (caddr my/re-builder-positions)))
          (with-selected-window reb-target-window
            (goto-char pnt) ; replace with (goto-char (match-beginning 0)) if you want
                                              ; to control where in the buffer the replacement starts
                                              ; with re-builder
            (setq my/re-builder-positions nil)
            (reb-quit)
            (query-replace-regexp re replacement delimited beg end))))
      
      (keymap-set reb-mode-map "RET" #'reb-replace-regexp)
      (keymap-set reb-lisp-mode-map "RET" #'reb-replace-regexp)
      (keymap-global-set "C-%" #'re-builder)
      
    • mark and open a file in one go in Dired
      (defun my/dired-mark-and-open-file ()
        "Mark file and open it in one go."
        (interactive)
        (save-excursion
          (dired-mark 1))
        (dired-open-file))
      
      (with-eval-after-load 'dired
        (keymap-set dired-mode-map "M-RET"
                    #'my/dired-mark-and-open-file))
      
    • open space

      Like C-o (M-x open-line), it is useful open single space ahead of point.

      (defun my/open-space ()
        "Insert single space ahead of point."
        (interactive)
        (insert " ")
        (backward-char 1))
      (keymap-global-set "M-o" 'my/open-space)
      
    • miscellaneous functions/commands
      (defun my/insert-time-stamp ()
        "Insert time stamp with preferred format."
        (interactive)
        (insert
         (completing-read "Format: "
                          (mapcar #'format-time-string
                                  '("%Y%m%d" "%Y-%m-%d" "%Y-%m-%d %H:%M:%S")))))
      
      (defun my/drag-line-backward ()
        "Drag up current line for one."
        (interactive)
        (transpose-lines 1)
        (previous-line 2))
      (keymap-global-set "M-S-<up>" 'my/drag-line-backward)
      
      (defun my/drag-line-forward ()
        "Drag down current line for one."
        (interactive)
        (next-line 1)
        (transpose-lines 1)
        (previous-line 1))
      (keymap-global-set "M-S-<down>" 'my/drag-line-forward)
      
      (defun my/comment-box (b e)
        "Draw a box comment around the region but arrange for the region
        to extend to at least the fill column. Place the point after the
        comment box."
        (interactive "r")
        (let ((e (copy-marker e t)))
          (goto-char b)
          (end-of-line)
          (insert-char ?  (- fill-column (current-column)))
          (comment-box b e 1)
          (goto-char e)
          (set-marker e nil)))
      
      (defun my/ascii-string-p (string)
        "Return non-nil if `STRING' seems to be made of ASCII."
        (if (length> string 0)
            (string-match-p
             (format "\\`[%s]+\\'" "[:ascii:]’“”–")
             string)
          t))
      
      (defun my/file-name-legitimate (filename)
        "Substitute legitimate file name for `FILENAME' so as not to have any special charactors."
        (seq-reduce (lambda (takeover replace)
                      (replace-regexp-in-string
                       (car replace) (cdr replace) takeover))
                    '(("/" . "/")
                      ("\"" . "❞")
                      ("'" . "❜"))
                    filename))
      
      (defun my/kansuji-to-number (kansuuji-string)
        "Translate Japanese Kansuuji(漢数字) KANSUUJI-STRING to number."
        (let* ((kanji-digits '(?一 ?二 ?三 ?四 ?五 ?六 ?七 ?八 ?九))
               (kanji-units '((?十 . 10) (?百 . 100) (?千 . 1000)))
               (rs (cl-reduce (lambda (kansuji val)
                                (let ((digit (cl-position kansuji kanji-digits))
                                      (unit (cdr (assoc kansuji kanji-units))))
                                  (if (> (cdr val) 0)
                                      (cons (+ (car val) (* (cdr val)
                                                            (1+ (or digit 0))))
                                            (or unit 0))
                                    (cond
                                     (digit (cons (+ (1+ digit) (car val)) 0))
                                     (unit (cons (car val) unit))))))
                              kansuuji-string
                              :from-end t :initial-value (cons 0 0))))
          (+ (car rs) (cdr rs))))
      
  • literate configuration (this file)

    Configuration files are generated by tangling org source blocks whenever this file is saved defined in .dir-locals.el. Following code suppresses confirmation at file saving.

    (defun my/compile-default-command ()
      "Compile `compile-command'."
      (let ((default-directory (project-root (project-current t)))
            (compilation-buffer-name-function
             (or project-compilation-buffer-name-function
                 compilation-buffer-name-function)))
        (funcall #'compile compile-command))
      (quit-window))
    
    (add-to-list 'safe-local-eval-forms
                 '(add-hook 'after-save-hook
                            #'my/compile-default-command nil t))
    
    • Common Babel Config

      I'd share this babel config between literate configuration and main Emacs session.

      (with-eval-after-load 'ob-core
        (setq org-babel-noweb-error-all-langs t)
        (setopt org-babel-python-command (executable-find "python3"))
        (setopt org-babel-ruby-command (executable-find "ruby"))
        (setopt org-confirm-babel-evaluate nil)
        (setopt org-babel-load-languages '((shell      . t)
                                           (emacs-lisp . t)
                                           (python     . t)
                                           (ruby       . t)))
        (setf org-babel-default-header-args
              (append org-babel-default-header-args
                      '((:mkdirp . t)
                        (:comments . "link")))))
      
      (with-eval-after-load 'ob-shell
        (add-to-list 'org-babel-default-header-args:shell
                     '(:shebang . "#!/bin/sh"))
        (add-to-list 'org-babel-default-header-args:shell
                     `(:tangle-mode . ,(identity #o755)))
        (add-to-list 'org-babel-default-header-args:bash
                     `(:tangle-mode . ,(identity #o755)))
        (add-to-list 'org-babel-default-header-args:bash
                     '(:shebang . "#!/usr/bin/env bash\nset -euo pipefail\n")))
      
      (with-eval-after-load 'ob-emacs-lisp
        (add-to-list 'org-babel-default-header-args:emacs-lisp
                     `(:tangle-mode . ,(identity #o444))))
      
      (with-eval-after-load 'ob-python
        (mapc (lambda (header-arg)
                (add-to-list 'org-babel-default-header-args:python
                             header-arg))
              `((:tangle-mode . ,(identity #o755))
                (:shebang . "#!/usr/bin/env python3"))))
      
      (with-eval-after-load 'ob-ruby
        (mapc (lambda (header-arg)
                (add-to-list 'org-babel-default-header-args:ruby
                             header-arg))
              `((:tangle-mode . ,(identity #o755))
                (:shebang . "#!/usr/bin/env ruby"))))
      
  • custom RX definitions

    My RX function 'parened' evaluates 'parened-rx' with reb-target-buffer's syntax table since working buffer with RX in re-builder is reb-lisp-mode which does not hold same syntax table with target-buffer.

    (defun parened-rx (paren-open-char element)
      `(seq ,paren-open-char
            (group ,element)
            ,(matching-paren paren-open-char)))
    
    (rx-define parened (paren-open-char element)
      (eval (with-syntax-table (cond
                                ((derived-mode-p 'reb-lisp-mode)
                                 (with-current-buffer reb-target-buffer (syntax-table)))
                                ((derived-mode-p 'minibuffer-mode)
                                 (with-current-buffer (other-buffer nil t) (syntax-table)))
                                (t (syntax-table)))
              (parened-rx paren-open-char 'element))))
    
  • dispatch-dwim commands
    (require 'dispatch-dwim)
    
    ;;; Misc
    
    (defalias 'dispatch-execute-macro (kmacro "RET"))
    
    (defun dispatch-dwim-select ()
      "Execute dispatch-*-dwim command."
      (declare (interactive-only command-execute))
      (interactive)
      (let ((read-extended-command-predicate
             (lambda (sym buf)
               (string-match-p (rx "dispatch-dwim" eos)
                               (symbol-name sym)))))
        (execute-extended-command current-prefix-arg)))
    (keymap-global-set "H-D" 'dispatch-dwim-select)
    
    • mpv command
      (transient-define-prefix mpv-dispatch-dwim (file &optional no-resume start end)
        ""
        :man-page "mpv"
        [("-R" "noresume" "--no-resume-playback"
          :init-value (lambda (obj) (oset obj value (if no-resume "--no-resume-playback" nil))))
         ("-s" "start" "--start=" :prompt "start: "
          :init-value (lambda (obj) (oset obj value start)))
         ("-e" "end" "--end=" :prompt "end: "
          :init-value (lambda (obj) (oset obj value end)))]
        [(dispatch-dwim-execute
          :command-format
          (format "mpv %%a %s"
                  (dispatch-dwim-shell-argument (plist-get (oref transient-current-prefix scope) 'file))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "File to play: "))))
        (transient-setup 'mpv-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
      (with-eval-after-load 'embark
        (mapc (lambda (map)
                (lambda-key map "P"
                            (lambda (file)
                              "play media FILE"
                              (mpv-dispatch-dwim file t))))
              (list embark-file-map embark-url-map))
        (with-eval-after-load 'org
          (defun my/flink-path (flink)
            (let* ((exp-link (org-link-expand-abbrev flink)))
              (string-trim-right
               (string-trim-left exp-link "file:")
               "::.+$")))
          (mapc (lambda (map)
                  (lambda-key map "P"
                              (lambda (link)
                                "play media file written in org LINK"
                                (mpv-dispatch-dwim (my/flink-path link) t))))
                (list embark-org-link-map))
          (mapc
           (lambda (map)
             (lambda-key map "P"
                         (lambda (list)
                           "play media in LIST"
                           (mpv-dispatch-dwim (mapcar (lambda (item)
                                                        (when (string-match org-link-any-re (car item))
                                                          (my/flink-path (match-string-no-properties 2 (car item)))))
                                                      (cdr (org-list-to-lisp list)))
                                              t))))
           (list embark-org-plain-list-map))))
      
    • ffmpeg-opus command
      (transient-define-prefix ffmpeg-opus-dispatch-dwim (file)
        ""
        [("-b" "bitrate" "-b:a " :prompt "bitrate: " :class transient-option
          :init-value (lambda (obj) (oset obj value "64k")))
         ("-t" "tempo" "-filter:a atempo=" :prompt "tempo (floating number): ")]
        [(dispatch-dwim-execute
          :command-format
          (format "parallel ffmpeg -i '{}' -c:a libopus -ac 2 %%a '{.}'.opus ::: %s"
                  (dispatch-dwim-shell-argument (plist-get (oref transient-current-prefix scope) 'file))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "File to convert to opus: "))))
        (transient-setup 'ffmpeg-opus-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
    • ffmpeg-vp9 command
      (transient-define-prefix ffmpeg-vp9-dispatch-dwim (file)
        ""
        [("-B" "audio bitrate" "-b:a " :prompt "Audio bitrate: " :class transient-option
          :init-value (lambda (obj) (oset obj value "128k")))
         ("-c" "crf" "-crf " :prompt "CRF value: " :class transient-option
          :init-value (lambda (obj) (oset obj value "28")))
         ("-ss" "seek start" "-ss " :prompt "Time to start seeking: " :class transient-option)
         ("-to" "seek stop" "-to " :prompt "Time to stop seeking: " :class transient-option)]
        [(dispatch-dwim-execute
          :command-format
          (apply #'format
                 "ffmpeg -i %5$s %1$s %2$s /dev/null && ffmpeg %%S -i %5$s %1$s %3$s %4$s %6$s_vp9.mkv"
                 "-c:v libvpx-vp9 -cpu-used 4 -threads 3 -row-mt 1 -speed 1 -b:v 0 %c"
                 "-pass 1 -an -f null"
                 "-pass 2 -map_metadata 0"
                 "-c:a libopus %b -ac 2"
                 (let* ((f (plist-get (oref transient-current-prefix scope) 'file))
                        (valid-f (dispatch-dwim-shell-argument f)))
                   (list valid-f
                         (file-name-base valid-f))))
          :command-format-spec ((?b . (dispatch-shell-command--key-vals '("-B")))
                                (?c . (dispatch-shell-command--key-vals '("-c")))
                                (?S . (dispatch-shell-command--key-vals '("-ss" "-to")))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "Input file: "))))
        (transient-setup 'ffmpeg-vp9-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
    • handbrake command
      (transient-define-prefix handbrake-dispatch-dwim (file)
        ""
        [("-o" "Destination file name" "--output "
          :class transient-option :prompt "Output file (.mp4/.mkv/.webm): "
          :init-value (lambda (obj)
                        (oset obj value
                              (if (and (stringp file)
                                       (or (file-exists-p file)
                                           (file-directory-p file)))
                                  (format "%s-hbout.mkv"
                                          (file-name-base file))
                                "hbout.mkv"))))
         ("-m" "Add chapter markers" "--markers" :class transient-switch
          :init-value (lambda (obj) (oset obj value "--markers")))
         ("-t" "Titles" "--title "
          :class transient-option :prompt "Titles (0 to scan): ")
         ("-F" "Main feature title" "--main-feature" :class transient-switch
          :init-value (lambda (obj) (oset obj value "--main-feature")))
         ("-S" "Subtitle" "" :class transient-switches
          :argument-format "--%s"
          :argument-regexp "\\(--\\(all-subtitles\\|first-subtitles\\)\\)"
          :choices ("all-subtitles" "first-subtitles")
          :init-value (lambda (obj) (oset obj value "--all-subtitles")))]
        ["Video"
         ("e" "Encoder" ""
          :class transient-switches
          :argument-format "--encoder %s"
          :argument-regexp "\\(--encoder \\(VP8\\|VP9\\|x264\\|x265\\|svt_av1\\)\\)"
          :choices ("VP8" "VP9" "x264" "x265" "svt_av1")
          :init-value (lambda (obj) (oset obj value "--encoder VP9")))
         ("p" "Preset" ""
          :class transient-switches
          :argument-format "--encoder-preset %s"
          :argument-regexp "\\(--encoder \\(.+\\)\\)"
          :choices ("veryfast" "faster" "fast" "medium" "slow" "slower" "veryslow")
          :init-value (lambda (obj) (oset obj value "--encoder-preset fast")))
         ("-q" "Quality" "--quality "
          :class transient-option :prompt "Quality value (float): "
          :init-value (lambda (obj) (oset obj value "18.0")))
         ("-b" "Bitrate" "--vb "
          :class transient-option :prompt "Bitrate (in kbps): ")]
        ["Audio"
         ("T" "Track" ""
          :class transient-switches
          :argument-format "--%s"
          :argument-regexp "\\(--\\(all-audio\\|first-audio\\)\\)"
          :choices ("all-audio" "first-audio")
          :init-value (lambda (obj) (oset obj value "--all-audio")))
         ("E" "Encoder" ""
          :class transient-switches
          :argument-format "--aencoder %s"
          :argument-regexp "\\(--aencoder \\(opus\\|copy\\)\\)"
          :choices ("opus" "copy")
          :init-value (lambda (obj) (oset obj value "--aencoder copy")))
         ("-B" "Bitrate" "--ab "
          :class transient-option :prompt "Bitrate (in kbps): ")]
        [(dispatch-dwim-execute
          :command-format
          (format "HandBrakeCLI --input %s %%a"
                  (dispatch-dwim-shell-argument (plist-get (oref transient-current-prefix scope) 'file))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "Input device or directory: "))))
        (transient-setup 'handbrake-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
    • dvdbackup command
      (transient-define-prefix dvdbackup-dispatch-dwim ()
        ""
        :man-page "dvdbackup"
        [("-i" "Input device" "--input="
          :class transient-option
          :prompt "Device file: "
          :reader transient-read-existing-file
          :init-value (lambda (obj) (oset obj value "/dev/sr0")))
         ("-o" "Output directory" "--output="
          :class transient-option
          :prompt "Output directory: "
          :reader transient-read-file
          :init-value (lambda (obj) (oset obj value "dvd-out")))
         ("a" "Action" ""
          :class transient-switches
          :argument-format "--%s"
          :argument-regexp "--\\(mirror\\|feature\\|info\\)"
          :choices ("mirror" "feature" "info")
          :init-value (lambda (obj) (oset obj value "--mirror")))]
        [(dispatch-dwim-execute
          :command-format
          (format "dvdbackup %%a"
                  (plist-get (oref transient-current-prefix scope) 'file)))]
        (interactive)
        (transient-setup 'dvdbackup-dispatch-dwim))
      
    • rtcwake complex command
      (transient-define-prefix sleep-dispatch-dwim (&optional number)
        ""
        :man-page "sleep"
        [("n" "Number[SUFFIX]" "" :prompt "amount of time [s/m/h/d]: "
          :class transient-option :always-read t :allow-empty nil
          :init-value (lambda (obj) (oset obj value (or number "0"))))]
        [(dispatch-dwim-execute
          :command-format "sleep %a")]
        (interactive)
        (transient-setup 'sleep-dispatch-dwim))
      
      (transient-define-prefix option-dispatch-dwim (&rest options)
        ""
        [("o" "option" ""
          :class transient-option
          :init-value (lambda (obj)
                        (oset obj choices options)
                        (oset obj value (car (oref obj choices)))))]
        [(dispatch-dwim-execute)]
        (interactive)
        (transient-setup 'option-dispatch-dwim))
      
      (transient-define-prefix rtcwake-dispatch-dwim (&optional number)
        ""
        :man-page "rtcwake"
        [("-t" "wake-up time" "--date " :prompt "Wake-up time: " :class transient-option
          :allow-empty nil :reader my/transient-read-time :always-read t
          :init-value (lambda (obj) (oset obj value (my/transient-read-time nil "+6h30m" nil))))]
        [("{" "preceding sleep" dispatch-dwim-fraction-command
          :prefix sleep-dispatch-dwim :prefix-arg ("10m") :priority 20
          :default "sleep 10m")
         ("[" "following command" dispatch-dwim-fraction-command
          :prefix option-dispatch-dwim :prefix-arg ("midnight.sh")
          :priority 30 :default "midnight.sh")
         ("]" "following command" dispatch-dwim-fraction-command
          :prefix option-dispatch-dwim :prefix-arg ("morning.sh")
          :priority 90 :default "morning.sh")
         (dispatch-dwim-execute
          :command-format "pwsudo rtcwake -l -m mem -v %d"
          :command-format-spec ((?d . (dispatch-shell-command--key-vals '("-t")))))])
      
      (defun my/transient-read-time (prompt default-time history)
        "Read time using date command."
        (or (seq-some (lambda (d)
                        (let* ((time-str (string-trim
                                          (shell-command-to-string
                                           (format "date +'%%Y-%%m-%%d %%H:%%M' -d '%s'"
                                                   (my/expand-shorthand-time d)))))
                               (time-ts (or (and (string-prefix-p "date: invalid date"
                                                                  time-str)
                                                 (message "Invalid time"))
                                            (encode-time (parse-time-string time-str)))))
                          ;; Ask the user if the specified time is designated as either past or beyond 12 hours
                          (if (and (or (time-less-p time-ts (current-time))
                                       (time-less-p (time-add nil (seconds-to-time (* 12 60 60)))
                                                    time-ts))
                                   (not (y-or-n-p (format "Is %s right?: " time-str))))
                              (car (symbol-value history))
                            (format "'%s'" time-str))))
                      (delq nil (list (and prompt (read-string prompt)) default-time)))
            ""))
      
    • pandoc command
      (transient-define-prefix pandoc-dispatch-dwim (file)
        ""
        :man-page "pandoc"
        [("-t" "Ootput format" "--to="
          :class transient-option
          :always-read t
          :choices ("org" "pdf" "epub" "rst" "texinfo" "odt" "docx" "markdown" "asciidoc" "plain")
          :init-value (lambda (obj) (oset obj value (car (oref obj choices)))))]
        [(dispatch-dwim-execute
          :command-format
          (format "parallel pandoc %%t %%o '{}' ::: %s"
                  (dispatch-dwim-shell-argument (plist-get (oref transient-current-prefix scope) 'file)))
          :command-format-spec ((?t . (dispatch-shell-command--key-vals '("-t")))
                                (?o . (seq-some (lambda (obj)
                                                  (when (and (string= (oref obj key) "-t")
                                                             (string-match "--to=\\(.+\\)" (transient-infix-value obj)))
                                                    (let ((out-ext (pcase (match-string 1 (transient-infix-value obj))
                                                                     ("markdown" "md")
                                                                     ("texinfo" "texi")
                                                                     ("asciidoc" "adoc")
                                                                     ("plain" "txt")
                                                                     (x x))))
                                                      (concat (format "--output='{.}'.%s" out-ext)
                                                              (when (string= out-ext "pdf")
                                                                " --pdf-engine weasyprint")))))
                                                (transient-suffixes (oref transient-current-prefix command))))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "File to convert: "))))
        (transient-setup 'pandoc-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
    • yt-dlp command
      (transient-define-prefix yt-dlp-dispatch-dwim (url &optional output duration-p play-p)
        ""
        :man-page "yt-dlp"
        [("-P" "download path" "--paths "
          :class transient-option
          :reader transient-read-existing-directory
          :init-value (lambda (obj) (oset obj value "<<media-dir()>>")))
         ("-a" "audio" "-x --embed-thumbnail")
         ("-d" "duration" "--get-duration --no-warnings"
          :init-value (lambda (obj) (oset obj value (and duration-p (oref obj argument)))))
         ("-p" "play" "--exec 'mpv --no-pause {}\; rm {}'"
          :init-value (lambda (obj) (oset obj value (and play-p (oref obj argument)))))
         ("-f" "format" "--format "
          :class transient-option
          :init-value (lambda (obj) (oset obj value "'bestvideo*+bestaudio/best'")))
         ("-S" "format sort" "--format-sort "
          :class transient-option
          :init-value (lambda (obj) (oset obj value "'filesize:1.5G'")))
         ("-o" "output" "--output "
          :class transient-option :reader dispatch-dwim-read-yt-dlp-output
          :prompt "Output file name without extension: "
          :init-value
          (lambda (obj) (oset obj value
                          (and output (shell-quote-argument
                                       (format "%s.%%\(ext\)s"
                                               (my/file-name-legitimate output)))))))]
        [(dispatch-dwim-execute
          ;; embed url to :command-format may cause unexpected glitch
          :command-format
          "yt-dlp --embed-chapters --live-from-start %a %u"
          :command-format-spec ((?u . (dispatch-dwim-shell-argument
                                       (plist-get (oref transient-current-prefix scope) 'url)))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-url))
                               (read-string "URL: "))))
        (setq my/detached-bury-buffer t)
        (transient-setup 'yt-dlp-dispatch-dwim nil nil
                         :scope (plist-put nil 'url url)))
      
      (defun dispatch-dwim-read-yt-dlp-output (prompt _default _history)
        ""
        (shell-quote-argument
         (format "%s.%%\(ext\)s"
                 (my/file-name-legitimate (read-string prompt)))))
      
      (with-eval-after-load 'embark
        (mapc (lambda (map)
                (lambda-key map "Y"
                            (lambda (thing)
                              "download media file"
                              (let ((url (pcase thing
                                           ((and (pred (lambda (s) (string-match org-link-any-re s))) ol)
                                            (or (match-string-no-properties 0 ol)
                                                (match-string-no-properties 2 ol)))
                                           ((and (pred (lambda (s) (string-match ffap-url-at-point s))) u)
                                            (match-string-no-properties 0 u)))))
                                (yt-dlp-dispatch-dwim url)
                                (dispatch-execute-macro)))))
              (list embark-kill-ring-map embark-url-map
                    embark-org-link-map)))
      
    • du command
      (transient-define-prefix du-dispatch-dwim (file)
        "Dispatch du command."
        :man-page "du"
        [("-s" "summarize" "--summarize"
          :init-value (lambda (obj) (oset obj value "--summarize")))
         ("-c" "add total" "--total"
          :init-value (lambda (obj) (oset obj value "--total")))
         ("-D" "deref symlinks" "--dereference-args"
          :init-value (lambda (obj) (oset obj value "--dereference-args")))
         ("-h" "human readable" "--human-readable"
          :init-value (lambda (obj) (oset obj value "--human-readable")))]
        [(dispatch-dwim-execute
          :command-format
          (format "ionice -c2 -n7 nice -n19 du %%a %s"
                  (dispatch-dwim-shell-argument (plist-get (oref transient-current-prefix scope) 'file))))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-file-name))
                               (read-file-name "File: "))))
        (transient-setup 'du-dispatch-dwim nil nil
                         :scope (plist-put nil 'file file)))
      
    • ghq-get command
      (transient-define-prefix ghq-get-dispatch-dwim (url)
        "Dispatch ghq get command."
        [("-s" "shallow clone" "--shallow")]
        [(dispatch-dwim-execute
          :command-format
          (format "ghq get %%a '%s'"
                  (plist-get (oref transient-current-prefix scope) 'url)))]
        (interactive (list (or (unless (equal current-prefix-arg '(4))
                                 (dispatch-dwim-determine-url))
                               (read-string "URL: "))))
        (transient-setup 'ghq-get-dispatch-dwim nil nil
                         :scope (plist-put nil 'url url)))
      
  • TempEL templates for major modes
    (defun my/region-string-trans-buffer ()
      "Return region string in some buffer where region is active."
      (cl-some (lambda (buf)
                 (with-current-buffer buf
                   (when (region-active-p)
                     (buffer-substring-no-properties (region-beginning)
                                                     (region-end)))))
               (list (current-buffer) (other-buffer nil t))))
    
    • fundamental-mode templates
      (timestamp (my/insert-time-stamp))
      
    • text-mode templates
      (gma "update on " (my/insert-time-stamp))
      
    • org-mode templates
      (call & "#+CALL: " (p "name" name) "(" (s var) "=\"" (s value) "\")")
      (name & "#+NAME: " (p "name" name))
      (/ "[/]" (org-update-statistics-cookies nil))
      (tsm p " :: " (with-temp-buffer (org-insert-time-stamp (current-time) t t)))
      
      <<org-template>>
      
    • minibuffer-mode templates
      <<minibuffer-mode-template>>
      
      (version (p "command") " --version")
      (curly-bracket-japanese-regexp
       (rx (parened ?{ (+ (or " "
                               (category japanese-hiragana-two-byte)))))))
      
    • emacs-lisp-mode templates
      <<emacs-lisp-mode-template>>
      
      (require "(require '" (p "feature") ")")
      (lambda "(lambda () " n>
        (p "docstring: " doc-str t)
        (when (length> doc-str 0)
          (format "\"%s\"\n" doc-str))
        > p ")")
      (interactive "(interactive"
                   (p "code: " code t)
                   (when (length> code 0)
                     (format " \"%s\"" code))
                   ")")
      (bind-keys "(bind-keys " (p (format ":map "))
                 "(\"" p "\" . " p "))")
      
    • sh-base-mode templates
      (rf "${" p "}")
      (function "function " (p "name") " {" n> p n "}")
      
    • mu4e-compose-mode templates
      (よろ "よろしくお願いいたします。")
      (いじょう "以上、よろしくお願いいたします。")
      
  • elisp code from outside of Emacs

    This section mainly for elisp code associated with some other programs.

    <<elisp-code>>
    
  • obsolete features   ARCHIVE

bespoke org tweaks

  • keymap for global org commands
    (keymap-global-set "H-o"
                       (define-keymap
                         :prefix 'my/org-global-map
                         "o" #'org-open-at-point
                         "C-s" #'org-save-all-org-buffers
                         "J" #'org-babel-tangle-jump-to-org
                         "i" #'org-info-find-node
                         "m" #'org-mark-ring-goto
                         "@" #'org-previous-link))
    
    (provide 'my-org-global-map)
    
  • remove empty clock log unless manually clocked out   ARCHIVE
  • embark keymaps for consult-org-heading and consult-location
    (defun my/embark-org-clock-in-with-opening-thing (target)
      "Clock in and open thing for TARGET at once.
    
    If TARGET is an agenda item and `other-window-for-scrolling' is
    displaying an org mode buffer, then that is the source window.
    If TARGET is a minibuffer completion candidate, then the source
    window is the window selected before the command that opened the
    minibuffer ran."
      (embark-org--in-source-window
       target
       (lambda (marker)
         (org-with-point-at marker
           (org-clock-in)
           (org-open-at-point)))))
    
    (defun my/embark-org-clock-supersede (target)
      "Cancel the current clocking then clock in TARGET so as to fill out the gap time."
      (embark-org--in-source-window
       target
       (lambda (marker)
         (org-with-point-at marker
           (my/org-clock-supersede)))))
    
    (with-eval-after-load 'embark
      (cl-mapc (lambda (key-def)
                 (keymap-set embark-org-heading-map
                             (car key-def) (cdr key-def)))
               '(("M-I" . my/embark-org-clock-in-with-opening-thing)
                 ("V" . my/embark-org-clock-supersede))))
    
  • org move repeat map

    my/org-move-repeat-map allows to move around org elements and blocks with ease.

    (defvar-keymap my/org-move-repeat-map
      :doc "Keymap to repeat moving commands in `org-mode'.  Used in `repeat-mode'."
      :repeat t
      "n" #'org-next-item
      "p" #'org-previous-item
      "u" #'org-up-element
      "d" #'org-down-element
      "f" #'org-forward-element
      "b" #'org-backward-element
      ">" #'org-next-link
      "<" #'org-previous-link
      "F" #'org-next-block
      "B" #'org-previous-block
      "M-f" #'org-next-block
      "M-b" #'org-previous-block
      "M-n" #'org-babel-next-src-block
      "M-p" #'org-babel-previous-src-block)
    
  • show org entries pointing to the current (backlink)

    These commands show back links even in [BROKEN LINK: id:d5bd559a-2cab-4495-993b-f7a21637f0ea] files.

    (defun my/org-backlink-entry ()
      "Show all entries that point to the entry at point."
      (interactive)
      (let ((id (org-entry-get (point) "ID"))
            (heading (org-get-heading t t t t)))
        (org-ql-search
          (directory-files-recursively org-directory "\\.org$" t
                                       (lambda (subdir)
                                         (member (file-relative-name subdir org-directory)
                                                 '("agenda" "notes"))))
          `(link ,(or id heading))
          :super-groups '((:auto-parent)))))
    (keymap-global-set "C-c b b" #'my/org-backlink-entry)
    
    (defun my/org-backlink-by-tag ()
      "Show all entries that share tag with the entry at point."
      (interactive)
      (let* ((org-use-tag-inheritance
              (if (denote-file-is-note-p (buffer-file-name))
                  t nil))
             (tags (org-get-tags)))
        (org-ql-search
          (directory-files-recursively org-directory "\\.org$" t
                                       (lambda (subdir)
                                         (member (file-relative-name subdir org-directory)
                                                 '("agenda" "notes"))))
          `(tags ,@tags)
          :super-groups '((:auto-tags)))))
    (keymap-global-set "C-c b t" #'my/org-backlink-by-tag)
    
  • set timer at clocking in
    (with-eval-after-load 'org-clock
      (add-hook 'org-clock-in-prepare-hook
                #'my/org-clock-in-set-timer))
    
    (defun my/org-clock-in-set-timer ()
      "Start count down timer for a clocked in entry.
    
    If the entry has ATTENTION_SPAN property, use it for `org-timer-default-timer'.
    Otherwise count down time is Effort property value.
    In neither case, count down time is 25 min which is suggested in the Pomodoro-technique."
      (let* ((todo (org-get-todo-state))
             (attention-span (org-entry-get (point) "ATTENTION_SPAN" 'selective))
             (effort (org-entry-get (point) "Effort" 'selective))
             (countdown-time (or attention-span
                                 (and todo "25")))
             (org-timer-default-timer countdown-time))
        (and countdown-time
             (org-timer-set-timer '(16)))))
    
  • stop timer at clocking out
    (with-eval-after-load 'org-clock
      (dolist (hook '(org-clock-out-hook org-clock-cancel-hook))
        (add-hook hook
                  (lambda ()
                    (and (bound-and-true-p  org-timer-countdown-timer)
                         (org-timer-stop))))))
    
  • supersede clocking task
    (defun my/org-clock-supersede ()
      "Supersede clocking task with the task at point."
      (interactive)
      (let ((org-clock-continuously t))
        (save-excursion
          (and (bound-and-true-p org-clock-current-task)
               (org-clock-cancel)))
        (org-clock-in)))
    
    (with-eval-after-load 'org-keys
      (with-eval-after-load 'my-org-global-map
        (bind-keys :map my/org-global-map
                   ("V" . my/org-clock-supersede))
        (setf (map-elt org-speed-commands "V") #'my/org-clock-supersede)))
    
  • notify the completion of the timer with an alarm
    pactl list sinks | grep '^[[:space:]]Volume:' | head -n $(($SINK + 1 )) | tail -n 1 | sed -e 's,.* \([0-9][0-9]*\)%.*,\1,'
    
    (with-eval-after-load 'org-timer
      (add-hook 'org-timer-done-hook
                'my/org-task-timer-finish))
    
    (defun my/org-task-timer-finish ()
      "Notify task timer by appropriate means."
      (when (and (org-clocking-p)
                 org-clock-marker)
        (let ((alert (org-entry-get org-clock-marker
                                    "ALERT")))
          (and (stringp alert)
               (string= alert "alarm")
               (alert "Timer Done!" :style 'alarm))
          (alert "Timer Done!" :style 'fringe :mode 'org-mode :buffer (org-clocking-buffer) :severity 'trivial))))
    
    (defvar alarm-sound "/usr/share/sounds/freedesktop/stereo/complete.oga")
    
    (with-eval-after-load 'alert
      (alert-define-style 'alarm
                          :title "alarm"
                          :notifier
                          (lambda (info)
                            (my/play-sound alarm-sound t))))
    
  • org-scrap (jotting down in org)

    Memo file (memo.org) is useful for jotting down fragmented pieces of text, code and more.

    (with-eval-after-load 'org
      (defvar my/org-capture-scrap-file
        (file-name-concat org-directory "sandbox/scrap.org")
        "File for jotting down with `org-capture'.")
      (with-eval-after-load 'org-capture
        (add-to-list 'org-capture-templates
                     `("j" "Jot down"
                       entry (file ,my/org-capture-scrap-file)
                       "* %U\n#+begin_src %^{Lnaguage|text}\n%(my/org-capture-filter-scrap \"%i\")\n#+end_src"
                       :jump-to-captured t
                       :immediate-finish t)
                     t)))
    
    (defun my/org-capture-filter-scrap (&optional str)
      "Replace semantic representation in `org-mode' with plain string in STR."
      (cond
       ((stringp str)
        (replace-regexp-in-string "^\\(*+ \\)" ",\\1" str))
       (t "")))
    
  • reset org element contextually
    (with-eval-after-load 'org
      (bind-keys :map org-mode-map
                 ("C-c SPC" . my/org-reset-dwim)))
    
    (defun my/org-reset-dwim ()
      "Exert reset procedure depending on org element at point."
      (interactive)
      (cond
       ((org-at-table-p) (org-table-blank-field))
       ((org-in-subtree-not-table-p)
        (org-save-outline-visibility t
          (org-reset-checkbox-state-subtree)))))
    
  • copy arbitrary org property to kill ring
    (with-eval-after-load 'org
      (bind-keys :map org-mode-map
                 ("C-c W" . my/org-entry-kill)))
    
    (with-eval-after-load 'org-keys
      (add-to-list 'org-speed-commands
                   '("W" . my/org-entry-kill)
                   t))
    
    (defun my/org-entry-kill ()
      "Prompt user to select property to append to the kill ring.
    
    If property's value matches $(...) format, ... is interpreted as shell command and execute it."
      (interactive)
      (let* ((properties (org-entry-properties))
             (prop-key-to-copy
              (completing-read "Property name: "
                               (mapcar #'(lambda (var) (car var)) properties))))
        (when (stringp prop-key-to-copy)
          (let* ((prop-value (cdr (assoc prop-key-to-copy properties)))
                 (str-to-copy
                  (cond
                   ((string-match "$(\\(.+\\))" prop-value)
                    (shell-command-to-string (match-string 1 prop-value)))
                   (t prop-value))))
            (kill-new str-to-copy)))))
    
  • lob ingest all named source blocks
    (with-eval-after-load 'org
      (bind-keys :map my/org-global-map
                 ("b i" . my/org-babel-lob-ingest-all))
    
      (defvar my/org-babel-lob-ingest-files
        (seq-reduce (lambda (org-files dir)
                      (append org-files
                              (directory-files (expand-file-name dir org-directory) "\.org$")))
                    '("agenda" "notes")
                    nil)
        "A list of org files which is used in `my/org-babel-lob-ingest-all'.")
    
      (defun my/org-babel-lob-ingest-all ()
        "Add all named soruce blocks in cpecified files."
        (interactive)
        (dolist (org-file my/org-babel-lob-ingest-files)
          (org-babel-lob-ingest org-file))))
    
  • alter org heading

    I wrote a blog post for this topic.

    (with-eval-after-load 'org
      (bind-keys :map org-mode-map
                 ("C-c C-% h" . my/org-change-heading)))
    
    (defun my/org-change-heading ()
      "CHange the heading of the entry at `point'.
    
    The previous name will be stored along with the execution date."
      (interactive)
      (let* ((old-heading (org-get-heading t t t t))
             (new-heading (read-string "Heading: " old-heading)))
        (save-excursion
          (org-back-to-heading t)
          (replace-string old-heading new-heading nil
                          (line-beginning-position) (line-end-position))
          (org-align-tags))
        (org-add-log-setup 'state new-heading old-heading 'state)))
    
  • download media files in org   ARCHIVE
  • manage on-hold tasks
    • task lifecycle

      digraph{
      "web browsing" [shape = hexagon];
      "web browsing" -> task [label = "read later"];
      elfeed [shape = hexagon];
      elfeed -> task [label = "watch later"];
      "web browsing" -> someday [label = "read someday"];
      task -> someday [label = "expired"];
      someday -> task [label = "drill (active)"];
      task -> pending [label = "abandoned"];
      subgraph cluster_0 {
      someday -> someday [label = "drill (inactive)"];
      pending;
      label = "somedays.org";
      }
      }
      
    • review on-hold tasks

      On-hold tasks are encouraged to review and they prohibited to appear in agenda views.

      (defun my/org-on-hold-task-review (resume-p)
        "Start or resume (if RESUME-P is non-nil) a session for reviewing tasks."
        (interactive "P")
        (progn
          (org-id-goto "58da20ee-97a9-4463-96b8-6ebb1084b9b7")
          (let ((org-drill-scope 'tree)
                (org-drill-question-tag my/org-review-tag)
                (org-drill-maximum-items-per-session 40)
                (org-drill-hide-item-headings-p nil)
                ;; drill session regulations
                (org-drill-maximum-duration 12)
                (org-drill-maximum-items-per-session 30)
                (org-drill-item-count-includes-failed-items-p t)
                ;; Spaced repetition algorithm
                (org-drill-spaced-repetition-algorithm 'sm5)
                (org-drill-sm5-initial-interval 14.0)
                ;; Random variation of repetition intervals
                (org-drill-add-random-noise-to-intervals-p t)
                ;; Adjusting item difficulty globally
                (org-drill-learn-fraction 0.37)
                (org-startup-indented nil))
            (and (require 'cl nil t)
                 (org-drill nil (format "TODO=\"%s\"" my/org-todo-keyword--someday)
                            resume-p)))))
      
    • skip showing on-hold tasks in agenda view
      (defvar my/org-review-tag "review"
        "Tag name set for tasks which is encourged to review.")
      
      (defun my/org-agenda-skip-on-hold-task ()
        "Skip entries which hold schedule time and review tag in agenda view."
        (let ((tags (org-get-tags)))
          (if (and (member my/org-review-tag (org-get-tags))
                   (org-get-scheduled-time (point)))
              (progn (outline-next-heading) (point))
            nil)))
      
      ;; (with-eval-after-load 'org-agenda
      ;;   (setopt org-agenda-skip-function-global
      ;;           'my/org-agenda-skip-on-hold-task))
      
    • show on-hold/leg-go task list
      (with-eval-after-load 'org-ql-view
        (push `("On-Hold tasks"
                :buffers-files ,(list (file-name-concat org-directory
                                                        "agenda/on-holds.org"))
                :query (todo ,my/org-todo-keyword--someday)
                :super-groups ((:auto-property "ARCHIVE_OLPATH"))
                :sort (priority date))
              org-ql-views)
        (push `("Review: on-hold tasks"
                :buffers-files ,(list (file-name-concat org-directory
                                                        "agenda/on-holds.org"))
                :query (todo ,my/org-todo-keyword--someday)
                :sort (scheduled))
              org-ql-views)
        (push `("Let-go tasks"
                :buffers-files ,(org-agenda-files)
                :query (todo ,my/org-done-keyword--pending)
                :super-groups ((:auto-property "ARCHIVE_OLPATH"))
                :sort (scheduled priority date))
              org-ql-views))
      
    • immediate re-refile on-hold task
      (with-eval-after-load 'org-refile
        (add-hook 'org-after-refile-insert-hook
                  #'my/org-archive-suspended-task))
      
      (defun my/org-archive-suspended-task ()
        "Archive this entry to the file for suspended tasks if the TODO state fits the bill."
        (interactive)
        (when (member (org-entry-get nil "TODO")
                      `(,my/org-todo-keyword--someday ,my/org-done-keyword--pending))
          (org-archive-subtree)))
      
  • send org subtree via email
    (with-eval-after-load 'org-keys
      (bind-keys :map org-mode-map
                 ("C-c M" . my/org-subtree-send-email))
      (add-to-list 'org-speed-commands
                   '("M" . my/org-subtree-send-email)
                   t))
    
    (defun my/org-subtree-send-email ()
      "Send an email containing the contents of the subtree at the entry at point."
      (interactive)
      (org-save-outline-visibility t
        (save-excursion
          (org-fold-show-all)
          (let* ((heading (org-get-heading t t t t))
                 (org-export-with-toc nil)
                 (org-export-with-author nil)
                 (org-export-show-temporary-export-buffer nil)
                 (message-kill-buffer-on-exit t)
                 (exp-buf (org-ascii-export-as-ascii nil t t t)))
            (message-mail (cdr (assoc "username" (auth-source-pass-parse-entry "www.zoho.com")))
                          heading)
            (message-goto-body)
            (insert (with-current-buffer exp-buf (buffer-string)))
            (message-send-and-exit)))))
    
  • clean up after the task has chenged to DONE
    (with-eval-after-load 'org
      (add-hook 'org-after-todo-state-change-hook
                #'my/org-todo-cleanup-when-done))
    
    (defun my/org-todo-cleanup-when-done ()
      "Exert several procedure when the state of the task has chnaged to DONE.
    
    Including:
    - Remove priority
    - Reset DONE state if the task is non-repeated habit
    - Close the project of the task"
      ;; when the entry has any org-done-keywords
      (when (member org-state
                    `(,my/org-done-keyword--done ,my/org-done-keyword--cancel ,my/org-done-keyword--pending))
        ;; remove priority if exists
        (and ;; the entry has been set any priority
         (/= 21000 (save-excursion
                     (save-match-data
                       (beginning-of-line)
                       (and (looking-at org-heading-regexp)
                            (org-get-priority (match-string 0))))))
         (org-priority 'remove))
        ;; reset done state if the task is non-repeated habit
        (and (org-is-habit-p)
             (not (org-get-scheduled-time (point)))
             (org-todo ""))
        ;; close the project
        (save-excursion
          (org-back-to-heading t)
          (org-speed-move-safe 'outline-up-heading)
          (and (member my/org-project-tag (org-get-tags))
               (org-todo my/org-done-keyword--done)))))
    
  • flush old clock logs and state changes

    The hook functions, which is commented out, provoke malfunction failing to create todo state change logs which is necessary for habit graph.

    (with-eval-after-load 'midnight
      (add-hook 'midnight-hook
                (lambda ()
                  (org-map-entries (lambda () (when (org-is-habit-p)
                                            (revert-buffer t t t)
                                            (my/org-flush-blobs)
                                            (basic-save-buffer)))
                                   nil
                                   'agenda))))
    
    (defvar my/org-flush-blobs-old-timestamp-in-day 90
      "Flush blobs with timestamps that are earlier than this number of days.")
    
    (defvar my/org-flush-blobs-clock-log t
      "Whether `my/org-flush-blobs' flushes old clock logs.")
    
    (defvar my/org-flush-blobs-state-change t
      "Whether `my/org-flush-blobs' flushes old todo state changes.")
    
    (defun my/org-flush-blobs ()
      "Flush old blob."
      (interactive)
      (org-save-outline-visibility t
        (org-show-all)
        (save-excursion
          (let ((case-fold-search t)
                (next-heading (save-excursion
                                (org-next-visible-heading 1)
                                (point-marker))))
            (mapc (lambda (re)
                    (org-back-to-heading t)
                    (while-let ((ts-time (and (re-search-forward re (marker-position next-heading) t)
                                              (encode-time (parse-time-string (match-string 1))))))
                      (when (time-less-p ts-time
                                         (time-subtract (current-time) (days-to-time my/org-flush-blobs-old-timestamp-in-day)))
                        (kill-whole-line))))
                  (append (and my/org-flush-blobs-clock-log
                               (list org-tr-regexp-both))
                          (and my/org-flush-blobs-state-change
                               (list (concat (rx "- State " "\"" (>= 1 upper) "\"" (+ space)
                                                 "from " (opt "\"" (>= 1 upper) "\"") (+ space))
                                             org-ts-regexp-inactive)))))
            (when (featurep 'org-clock)
              (org-clock-remove-empty-clock-drawer))))))
    
  • pad non-clocking time with break task
    (with-eval-after-load 'org-clock
      (add-hook 'org-clock-out-hook #'my/org-clock-in-break-time))
    
    (defun my/org-clock-in-break-time ()
      "Clock in the 'Break' task automatically as to pat non-clocking time."
      (let ((break-task-id "d49d97de-58ec-4e41-b58c-491a9e216e1c"))
        (when (and (not org-clock-clocking-in)
                   (not (string= (or (save-excursion
                                       (org-clock-goto)
                                       (org-id-get))
                                     "")
                                 break-task-id)))
          (org-with-point-at (org-id-find break-task-id t)
            (org-clock-in)))))
    
  • show week number from the timestamp at point
    (defun my/org-iso-week-at-point ()
      "Display ISO 8601 week number correspoinds to time stamp at point."
      (interactive)
      (when-let ((ts-bound (org-in-regexp (org-re-timestamp 'all)))
                 (ts (buffer-substring (car ts-bound) (cdr ts-bound))))
        (message "W%s" (format-time-string "%V" (org-read-date nil t ts nil)))))
    
  • show outline path for the entry at point
    (defun my/org-show-outline-path ()
      "Show sparse tree for the entry at point to represent outline path."
      (interactive)
      (save-excursion
        (cl-labels ((lookup-parent ()
                      (let* ((heading-raw (substring-no-properties
                                           (org-get-heading t t t t)))
                             (heading (if (string-match org-link-bracket-re heading-raw)
                                          (match-string 2 heading-raw)
                                        heading-raw))
                             (h-li (list heading)))
                        (if (org-up-heading-safe)
                            (append (lookup-parent) h-li)
                          h-li))))
          (org-ql-sparse-tree (format "(olps %s)"
                                      (mapconcat (lambda (str) (format "\"%s\"" str))
                                                 (lookup-parent)
                                                 " "))))))
    
    (with-eval-after-load 'my/org-mode-init
      (keymap-set org-mode-map "C-c M-/"
                  #'my/org-show-outline-path))
    
  • use ISO week tree instead of Y/M/D tree

    Following code is also responsible for deterministic archive location.

    (with-eval-after-load 'org-archive
      (advice-add #'org-archive-subtree :around
                  #'my/org-archive-subtree--iso-datetree))
    
    (defun my/org-archive-subtree--iso-datetree (oldfun &rest r)
      "Move the current subtree to the archive.
    
    If archiving location is datetree, it supposed to be iso week tree."
      (interactive)
      (when (member (org-get-todo-state)
                    `(,my/org-todo-keyword--urgent ,my/org-todo-keyword--todo ,my/org-todo-keyword--next ,my/org-todo-keyword--in-action))
        (org-todo my/org-done-keyword--cancel))
      (when (string= (org-get-todo-state)
                     my/org-todo-keyword--someday)
        (org-toggle-tag my/org-review-tag 'on))
      (let* ((todo-state (org-get-todo-state))
             (closed-time (org-entry-get nil "CLOSED" t))
             (org-archive-location
              (cond
               ((member todo-state
                        `(nil ,my/org-done-keyword--done ,my/org-done-keyword--cancel))
                (format "%s::datetree/"
                        (file-name-concat org-directory "archive"
                                          (format "archive_%s.org"
                                                  (if closed-time
                                                      (nth 5
                                                           (parse-time-string
                                                            (substring closed-time 1 -1)))
                                                    (format-time-string "%Y"))))))
               ((string= todo-state my/org-todo-keyword--someday)
                (format "%s::* on-hold tasks"
                        (expand-file-name "agenda/on-holds.org" org-directory)))
               ((string= todo-state my/org-done-keyword--pending)
                (format "%s::* let go tasks"
                        (expand-file-name "archive/agenda/let-goes.org" org-directory))))))
        (when (stringp org-archive-location)
          (advice-add #'org-datetree-find-date-create :override
                      #'org-datetree-find-iso-week-create)
          (apply oldfun r)
          (advice-remove #'org-datetree-find-date-create
                         #'org-datetree-find-iso-week-create))))
    
    (with-eval-after-load 'org-agenda
      (keymap-set org-agenda-mode-map "C" #'my/org-archive-find--iso-datetree))
    
    (with-eval-after-load 'calendar
      (keymap-set calendar-mode-map "C" #'my/org-archive-find--iso-datetree))
    
    ;;;###autoload
    (defun my/org-archive-find--iso-datetree (date)
      "Find and visit the location of DATE in archivee file.
    
    DATE must be a string representing the date to find and parsable with `format-time-string'.
    
    If called interactively, it prompt the user to select the date to find."
      (interactive
       (cond
        ((eq major-mode 'calendar-mode)
         (list (calendar-date-string (calendar-cursor-to-date))))
        ((eq major-mode 'org-agenda-mode)
         (let* ((day (or (get-text-property (min (1- (point-max)) (point)) 'day)
                         (user-error "Don't know which date to open in calendar")))
                (date (calendar-gregorian-from-absolute day)))
           (list (calendar-date-string date))))
        (t (let ((date-select (org-read-date)))
             (list date-select)))))
      (let* ((d (parse-time-string date))
             (day (decoded-time-day d))
             (month (decoded-time-month d))
             (year (decoded-time-year d)))
        (find-file (file-name-concat org-directory "archive" (format "archive_%d.org" year)))
        (org-datetree-find-iso-week-create `(,month ,day ,year))))
    
    (with-eval-after-load 'my-org-global-map
      (bind-keys :map my/org-global-map
                 ("D" . my/org-archive-find--iso-datetree)))
    
  • search org entry under 'default-directory'
    (defmacro with-org-files-under (&rest body)
      "Eval `BODY' with providing org file list from `default-directory'."
      (declare (indent defun))
      `(let ((org-files (directory-files-recursively
                         default-directory
                         "\\(?:\\.org\\(?:\\.gpg\\)?$\\)")))
         ,@body))
    
    (defun my/consult-org-heading-under ()
      "Invoke `consult-org-heading' with all org files under `default-directory'."
      (interactive)
      (with-org-files-under
        (let ((org-agenda-files org-files))
          (consult-org-agenda))))
    
    (with-eval-after-load 'embark
      (keymap-set embark-file-map "H"
                  (lambda (dir) (interactive "sDir: ")
                    (let ((default-directory dir))
                      (my/consult-org-heading-under)))))
    
    (with-eval-after-load 'my-org-global-map
      (keymap-set my/org-global-map "H"
                  #'my/consult-org-heading-under))
    
    (defun my/org-ql-search-under ()
      "Invoke `org-ql-search' with all org files under `default-directory'."
      (interactive)
      (with-org-files-under
        (let ((org-agenda-files org-files)
              (query (read-string "Query: ")))
          (org-ql-search (org-agenda-files) query))))
    
    (with-eval-after-load 'embark
      (keymap-set embark-file-map "Q"
                  (lambda (dir) (interactive "sDir: ")
                    (let ((default-directory dir))
                      (my/org-ql-search-under)))))
    
    (with-eval-after-load 'my-org-global-map
      (keymap-set my/org-global-map "q Q"
                  #'my/org-ql-search-under))
    
  • Opening Large Well-Accessed Org Files Stealthily
    (with-eval-after-load 'org
      (when (require 'denote nil t)
        (run-with-idle-timer (* 5 60) nil
                             (lambda ()
                               "Open large org files."
                               (mapc #'find-file-noselect
                                     (list (denote-get-path-by-id "20230605T170959")
                                           (file-name-concat org-directory (format-time-string "archive/archive_%Y.org"))))))))
    
  • Creating and showing project-task relationship
    (defun my/project-insert-link ()
      "Insert a org-link pointing to the project entry which locates upper position."
      (interactive)
      (when (org-get-todo-state)
        (save-excursion
          (outline-up-heading 1)
          (call-interactively #'org-store-link))
        (let ((org-log-note-headings (append '((project . "project %s %t"))
                                             org-log-note-headings))
              (link-str (with-temp-buffer
                          (call-interactively #'org-insert-last-stored-link)
                          (concat " " ; workaround for org-ql failing to recognize org-link with preceding "
                                  (buffer-substring-no-properties (point-min) (1- (point-max)))
                                  " "))))
          (org-add-log-setup 'project link-str
                             nil 'state (format-time-string (org-time-stamp-format t t)))
          (run-hooks 'post-command-hook))))
    
    (defun my/project-list-tasks ()
      "Show a list of tasks belonging to the current project."
      (interactive)
      (when (require 'org-ql nil t)
        (when-let ((id (org-id-get)))
          (org-ql-search
            (append (file-expand-wildcards (file-name-concat org-directory "archive" "archive_*.org"))
                    (org-agenda-files))
            `(link :target ,(format "id:%s" id))))))
    
    (with-eval-after-load 'org-refile
      (add-hook 'org-after-refile-insert-hook
                #'my/project-insert-link))
    

would-be packages

  • lfile (custom org link type)

    lfile is an extended org link type for files in local machine. It is inspired by Kerl Voit's implementation. It is implemented using abbrev mechanism since functionality of file: link type such as inline image or exporting can be easily imparted.

    (use-package lfile
      :after org
      :config
      (put 'lfile-locate 'org-link-abbrev-safe t))
    
    (require 'ol)
    (require 'org-element)
    
    (with-eval-after-load 'ol
      (add-to-list 'org-link-abbrev-alist
                   '("lfile" . "file:%(lfile-locate)")
                   t)
      (add-to-list 'org-link-abbrev-alist
                   '("lfile+emacs" . "file+emacs:%(lfile-locate)")
                   t)
      (add-to-list 'org-link-abbrev-alist
                   '("lfile+sys" . "file+sys:%(lfile-locate)")
                   t))
    
    (defun lfile-locate (tag)
      "Return a path found using TAG with locate program."
      (let* ((match-idx (string-match "^\\([^:]*\\)\\(::?\\(.*\\)\\)?$" tag))
             (link (if match-idx (match-string 1 tag) tag)))
        (concat (string-trim
                 (shell-command-to-string
                  (concat (format "plocate -d <<plocate-db()>> -e \"%s\" 2>/dev/null"
                                  (shell-quote-argument link))
                          " | head --lines=1")))
                (when match-idx (match-string 2 tag)))))
    
    (defun lfile-store-link ()
      "Store a lfile link.
    
    Prefix argument does matter in this function call.
    If `C-u' prefix is given, file: link type will be used instead."
      (when (and (derived-mode-p 'dired-mode)
                 (string-match-p
                  (format "^%s" (expand-file-name "~"))
                  (dired-current-directory nil)))
        (let ((path (dired-get-filename nil t)))
          (if (equal current-prefix-arg '(4))
              (org-link-store-props
               :type "file"
               :link (concat "file:" (abbreviate-file-name
                                      (expand-file-name path))))
            (org-link-store-props
             :type "lfile"
             :link (concat "lfile:" (file-name-nondirectory path)))))))
    
    (defun lfile-abbrev (raw-link &optional path-conv-fn)
      "Return an abbreviated lfile link from RAW-LINK.
    
    RAW-LINK is supposed to be a file link type with a path and the path is
    converted by `file-name-nondirectory' unless PATH-CONV-FN is supplied."
      (let* ((link (with-temp-buffer
                     (let ((org-inhibit-startup nil))
                       (insert raw-link)
                       (org-mode)
                       (goto-char (point-min))
                       (org-element-link-parser))))
             (type (org-element-property :type link))
             (path (org-element-property :path link))
             (abbrev-link (cond
                           ((string= type "file")
                            (format "%s:%s"
                                    "lfile"
                                    (if (functionp path-conv-fn)
                                        (funcall path-conv-fn path)
                                      (file-name-nondirectory path))))
                           ((stringp type)
                            raw-link)
                           (t nil))))
        abbrev-link))
    
    (with-eval-after-load 'org
      (org-link-set-parameters "lfile"
                               :store #'lfile-store-link))
    
    (provide 'lfile)
    
    • lfile (legacy)   ARCHIVE
  • open-file

    Open-file is a feature that allows you to specify the application to open a file based on its type.

    By default, PDF and EPUB files are opened in EWW after converted into HTML. Videos and audio files are played in MPV media player.

    (require 'open-file)
    
    ;; general functions
    
    (defvar my/open-file-dir
      "<<htmlize-dir()>>"
      "Directory where html file is converted into.")
    
    (defvar my/open-file-media-player "mpv"
      "Media player used to play media file in open-file.")
    
    (defvar my/open-file-media-extensions
      '("mpg" "mpeg" "mp3" "mp4" "m4v" "m4a"
        "avi" "wmv" "wav" "mov" "flv" "mkv" "mka" "webm" "3gp"
        "flac" "ogv" "oga" "ogx" "ogg" "spx" "opus"
        "xbm" "pbm" "pgm" "ppm" "pnm" "png" "gif" "bmp" "tif" "jpeg" "jpg" "webp")
      "Extensions files which can play with media player must have.")
    
    (defvar my/open-file-compressed-media-extensions
      '("tar.gz" "tgz" "tar.xz" "txz" "zip" "7z")
      "Extensions compressed files which can play with media player must have.")
    
    ;; TODO: This could be achieved by command builder
    (defmacro my/open-file-as-html (file convert-cmd)
      `(let* ((cap-command (if (string-match-p url-handler-regexp ,file)
                               "curl"
                             (if (string-suffix-p ".gpg" ,file)
                                 "gpg -d"
                               "cat")))
              (dir-name (file-name-base ,file))
              (dirname-html
               (expand-file-name
                dir-name
                my/open-file-dir))
              (filename-html
               (concat (file-name-as-directory dirname-html)
                       "index.html"))
              (command ,convert-cmd))
         (unless (file-exists-p filename-html)
           (make-directory dirname-html t)
           (call-process-shell-command command))
         (browse-url filename-html)))
    
    (defun my/open-pdf-as-html (pdf)
      "Open PDF in `eww' after the process of convertion into html."
      (my/open-file-as-html
       (expand-file-name pdf)
       (format "%1$s %2$s 2>/dev/null | pdftohtml -p -s -noframes -nomerge -nodrm - %3$s"
               cap-command
               (shell-quote-argument (expand-file-name pdf))
               (shell-quote-argument filename-html))))
    
    (defun my/open-epub-as-html (epub)
      "Open EPUB in `eww' after the process of convertion into html."
      (let ((exp-epub (if (file-exists-p epub)
                          (expand-file-name epub)
                        epub)))
        (my/open-file-as-html
         exp-epub
         (format "%1$s %2$s 2>/dev/null | pandoc --from=epub --to=html --extract-media=%3$s - | strip_ruby > %4$s"
                 cap-command
                 (shell-quote-argument exp-epub)
                 (shell-quote-argument dirname-html)
                 (shell-quote-argument filename-html)))))
    
    (defun my/open-media (media &optional query)
      "Open MEDIA file in media player with respecting QUERY.
    
    QUERY must comply to RFC 822 format, which holds attribute/value pairs."
      (let ((ask-file (when (or (file-directory-p media)
                                (string-match-p (eval `(rx (or ,@my/open-file-compressed-media-extensions)))
                                                media))
                        (file-name-nondirectory media))))
        (when (or (not ask-file)
                  (and ask-file
                       (y-or-n-p (format "Play %s?: " ask-file))))
          (apply 'funcall-interactively
                 'mpv-dispatch-dwim media nil
                 (and (stringp query)
                      (let ((start) (end))
                        (mapc (lambda (cons-cell)
                                (pcase cons-cell
                                  (`(,val "") (setq start val))
                                  (`(,(or "s" "start") . (,val)) (setq start val))
                                  (`(,(or "e" "end") . (,val)) (setq end val))))
                              (url-parse-query-string query))
                        `(,start ,end))
                      )))))
    
    ;; ==============================================
    ;; browse-url handling
    
    (with-eval-after-load 'browse-url
      (setopt browse-url-handlers
              `(((lambda (url) (equal current-prefix-arg '(16)))
                 . browse-url-firefox)
                ((lambda (url) (equal current-prefix-arg '(4)))
                 . browse-url-emacs)
                (ghq-list
                 . (lambda (url &rest args)
                     (find-file (car (ghq-list url)))))
                ("\\.html\\'"
                 . (lambda (url &optional same-window)
                     (cond
                      ((file-exists-p url) (eww-open-file url))
                      ((string-match-p url-handler-regexp url)
                       (browse-web url)))))
                ("\\.pdf\\(\\.gpg\\)?\\'" . (lambda (url &optional same-window)
                                              (my/open-pdf-as-html url)))
                ("\\.epub\\'" . (lambda (url &optional same-window)
                                  (my/open-epub-as-html url)))
                (,(eval `(rx "." (or ,@my/open-file-media-extensions
                                     ,@my/open-file-compressed-media-extensions)
                             (opt ".gpg")))
                 . my/open-media))))
    
    ;; ==============================================
    ;; org-open handling
    
    (defun my/org-open-html (file-path link-string)
      "Open a html file at FILE-PATH via org-open command."
      (browse-url file-path)
      (when-let ((search (my/org-link-search link-string)))
        (org-link-search search)))
    
    (defun my/org-open-pdf (file-path link-string)
      "Open a pdf file at FILE-PATH via org-open command."
      (my/open-pdf-as-html file-path)
      (when-let ((search (my/org-link-search link-string)))
        (org-link-search search)))
    
    (defun my/org-open-epub (file-path link-string)
      "Open an epub file at FILE-PATH via org-open command."
      (my/open-epub-as-html file-path)
      (when-let ((search (my/org-link-search link-string)))
        (org-link-search search)))
    
    (defun my/org-open-media (file-path link-string)
      "Open a media file at FILE-PATH via org-open command."
      (my/open-media file-path
                     (my/org-link-search link-string)))
    
    (defun my/org-link-search (link-string)
      (and (stringp link-string)
           (string-match "::\\(.*\\)\\'" link-string)
           (match-string 1 link-string)))
    
    (defun my/org-execute-file-search-line-number (s)
      "A search function responsible for line number inside of S."
      (and (string-match (rx bos
                             (opt "l=")
                             (group-n 1
                               (opt (or ?- ?+))
                               (1+ digit))
                             eos)
                         s)
           (goto-line (string-to-number (match-string 1 s)))))
    
    (add-hook 'org-execute-file-search-functions
              'my/org-execute-file-search-line-number)
    
    (with-eval-after-load 'org
      (setopt org-file-apps
              `(("\\.html\\'" . my/org-open-html)
                ("\\.pdf\\(\\.gpg\\)?\\'" . my/org-open-pdf)
                ("\\.epub\\'" . my/org-open-epub)
                (,(eval `(rx "." (or ,@my/open-file-media-extensions
                                     ,@my/open-file-compressed-media-extensions)
                             (opt ".gpg") string-end))
                 . my/org-open-media)
                (auto-mode . emacs)
                (directory . my/org-open-media)
                (system . browse-url-default-browser))))
    
    ;; ==============================================
    ;; dired-open handling
    
    (with-eval-after-load 'dired-open
      (setq dired-open-functions '(dired-open-call-function-by-extension
                                   dired-open-by-extension
                                   dired-open-subdir)
            dired-open-extensions-elisp `(("pdf" . my/open-pdf-as-html)
                                          ("epub" . my/open-epub-as-html)
                                          ,@(mapcar (lambda (ext)
                                                      (cons ext 'my/open-media))
                                                    my/open-file-media-extensions))
            dired-open-find-file-function 'my/dired-find-file))
    
    (defun my/dired-find-file ()
      "A function for `dired-open-find-file-function' dealing with
    a `dired-open-file' call with `C-u' or `C-u C-u'."
      (let ((file-for-visit (dired-get-file-for-visit)))
        (cond
         ((equal current-prefix-arg '(16))
          (browse-url-default-browser file-for-visit))
         ((equal current-prefix-arg '(4))
          (dired--find-possibly-alternative-file file-for-visit))
         ((string-match-p (eval `(rx (or ,@my/open-file-compressed-media-extensions)))
                          file-for-visit)
          (my/open-media file-for-visit))
         (t (dired-find-file)))))
    
    (provide 'open-file)
    
    • get original file name from converted html file

      (defun my/open-file-original-file (html)
        "Revert HTML file into original file name."
        (interactive (list (if (use-region-p)
                               (buffer-substring (use-region-beginning)
                                                 (use-region-end))
                             (read-string "HTML: "))))
        (when-let* ((regex (rx (group-n 1 (opt (or "file://" "file:~")))
                               (1+ (: "/" (1+ nonl)))
                               (group-n 2 (1+ (: "!" (1+ (: nonl)))))
                               "/index.html"))
                    (index (string-match regex html))
                    (rep (concat (and (cl-some (lambda (cand)
                                                 (string= cand (match-string 1 html)))
                                               '("file://" "file:~"))
                                      "lfile:")
                                 (substring (match-string 2 html) 1))))
          (if (use-region-p)
              (replace-string-in-region html rep)
            (princ (replace-regexp-in-string regex rep html)))))
      
  • org-password-store
    (use-package org-password-store
      :after org
      :bind (:map org-mode-map
                  ("C-c p c c" . my/password-store-copy)
                  ("C-c p c f" . my/password-store-copy-field)
                  ("C-c p s" . my/password-store-show-field)
                  ("C-c p w" . my/password-store-url)
                  ("C-c p l" . my/password-store-web-login)
                  ("C-c p n" . my/password-store-create)
                  ("C-c p e" . my/password-store-edit)
                  ("C-c p v" . my/password-store-edit)
                  ("C-c p k" . my/password-store-remove)))
    
    (require 'password-store)
    
    (defcustom my/org-password-store-property "PASSWORD_STORE"
      "Property used in my/password-store feature to get password-store entry name attached to current org entry.")
    
    (defmacro with-password-store-entry (entry &rest body)
      "Eval BODY that can refer password-store ENTRY."
      (declare (indent defun))
      `(let ((entry (or ,entry
                        (when (derived-mode-p 'org-mode) (org-entry-get (point) my/org-password-store-property))
                        (password-store--completing-read t))))
         ,@body))
    
    (defmacro with-password-store-entry-field (entry field &rest body)
      "Eval BODY which can refer password-store ENTRY and FIELD..
    
    Plus, OBDY can refer value of FIELD named field-value."
      (declare (indent defun))
      `(let* ((entry (or ,entry
                         (when (derived-mode-p 'org-mode) (org-entry-get (point) my/org-password-store-property))
                         (password-store--completing-read t)))
              (field (or ,field
                         (password-store-read-field entry)))
              (field-value (password-store-get-field entry
                                                     (or (and (string= field "secret")
                                                              'secret)
                                                         (and (stringp field)
                                                              field)))))
         ,@body))
    
    (defun my/password-store-copy ()
      "Add the secret to system clipboard and the username to primary `x-selection'.
    
    `password-store-copy' is responsible for managing the kill ring."
      (interactive)
      (with-password-store-entry-field nil "username"
                                       (password-store-copy entry)
                                       (gui-set-selection 'PRIMARY
                                                          (or (password-store-get-field entry "username")
                                                              (password-store-get-field entry "email")
                                                              (nth 1 (password-store-parse-entry entry))))
                                       (message "USERNAME => X-SEL, SECRET => CLIPBOARD")))
    
    (defun my/password-store-copy-field ()
      "Add field for entry into the kill ring.
    
    entry and field is determined by appropriate manner."
      (interactive)
      (with-password-store-entry-field nil nil
                                       (password-store-copy-field entry field)))
    
    (defun my/password-store-url (&optional arg)
      "Browse url stored in entry by appropriate manner determined by ARG."
      (interactive "P")
      (with-password-store-entry-field nil "url"
                                       (browse-url field-value)))
    
    (defun my/password-store-show-field ()
      "Show a field value in the minibuffer.
    
    Password-store entry and field used to derive this value are from org property
    or selected by user."
      (interactive)
      (with-password-store-entry-field nil nil
                                       (message "%s: %s" field field-value)))
    
    (defun my/password-store-create ()
      (interactive)
      (let* ((input (read-string "Entry-name or URL: "))
             (domain (when (string-match-p browse-url-button-regexp input)
                       (string-trim-right
                        (shell-command-to-string (format "echo %s | awk -F[/:] '{print $4}'"
                                                         (shell-quote-argument input)))))))
        (with-password-store-entry (cond
                                    ((> (length domain) 0) domain)
                                    (input))
                                   (when (derived-mode-p 'org-mode)
                                     (org-entry-put (point) my/org-password-store-property entry))
                                   (password-store-edit entry))))
    
    ;;;###autoload
    (defun my/password-store-edit ()
      (interactive)
      (with-password-store-entry nil
                                 (password-store-edit entry)))
    
    ;; TODO: make my/password-store-insert as soon after
    ;;     : password-store--run-insert has been implemented
    
    (defun my/password-store-remove ()
      (interactive)
      (with-password-store-entry nil
                                 (password-store-remove entry)
                                 (when (derived-mode-p 'org-mode)
                                   (org-entry-delete (point) my/org-password-store-property))))
    
    (defun my/password-store-web-login ()
      "Open url for the entry at point.
    
    Additionally the username and the secret are saved in X selection and clipboard respectively."
      (interactive)
      (my/password-store-copy)
      (funcall-interactively #'my/password-store-url '(16)))
    
    (advice-add #'password-store-clear :before #'my/password-store-clear)
    
    (defun my/password-store-clear (&optional field)
      "Overwrite clipboard data so that password once saved in clipboard is deleted
     for security risk.
    
    FIELD originally for messaging is ignored in this function."
      (interactive)
      (when password-store-kill-ring-pointer
        (kill-new "")
        (setcar kill-ring-yank-pointer "")))
    
    (provide 'org-password-store)
    
  • text-mail   ARCHIVE
  • learning english words/phrases

    My workflow for acquiring English words is:

    1. Capture a word which come across in the article I'm reading with my/org-english-capture
      • Captured words will be appended to my English word list
    2. Review my English word list with my/org-english-drill everyday
      • Org-drill ask questions to level up my vocabulary
    (use-package org-drill-english
      :after org
      :bind (:map my/org-global-map
                  ("e e" . my/org-english-capture)
                  ("e d" . my/org-english-drill)
                  ("e r" . my/org-english-drill-resume)
                  ("e l" . my/org-english-link)
                  ("E" . my/cloze-deletion-replace)
                  ("e q" . my/org-english-search)
                  :map engine-mode-prefixed-map
                  ("j" . (lambda (word)
                           "Copy Japanese translation as well as the pronunciation to `kill-ring'."
                           (interactive (list (determine-search-word)))
                           (when (if (featurep 'org-drill-english)
                                     t
                                   (require 'org-drill-english nil t))
                             (kill-new (message "%s %s"
                                                (my/english-japanese-translate word t)
                                                (my/english-pronunciation word t)))))))
      :config
      (when (require 'denote nil t)
        (setq my/org-english-file (denote-get-path-by-id "20230605T170959"))))
    
    ;; -*- lexical-binding: t -*-
    
    (require 'define-word)
    (require 'org)
    (require 'org-drill)
    (require 'cl)
    (require 'request)
    (require 'logos)
    (require 'org-tidy)
    
    (defvar my/org-english-file "~/documents/english.org"
      "A file where all english words to learn locate.")
    
    (defvar my/org-english-word nil
      "An english word to capture.")
    
    (defvar my/org-english-match-entry nil
      "An org entry which matches search word.")
    
    (defun my/org-english-capture (word)
      "Capture an english WORD as a `org-mode' entry suitable for org-drill.
    
    Capture URL or file currently visiting as well as a sentence at point."
      (interactive (list (determine-search-word "Word to capture: ")))
      (setq org-english-word (downcase word))
      (setq my/org-english-match-entry nil)
      (let* ((sentence-end-double-space nil)
             (my/org-english-match-entry (org-ql-select
                                          `(,my/org-english-file)
                                          `(and (heading-regexp
                                                 ,(eval `(rx word-start
                                                             ,org-english-word
                                                             word-end)))
                                                (tags "drill"))
                                          :action 'element-with-markers))
             (sentence (replace-regexp-in-string word
                                                 (my/cloze-deletion word)
                                                 (or (sentence-at-point) ""))))
        (kill-new (if (stringp sentence) sentence ""))
        (cond
         (my/org-english-match-entry (org-capture nil "!1"))
         (t (org-capture nil "!0")))))
    
    (defun my/cloze-deletion (word)
      "Build cloze deletion string from WORD."
      (seq-reduce (lambda (string chunk)
                    (replace-regexp-in-string chunk (format "[%s||%c]" chunk (string-to-char chunk))
                                              string))
                  (split-string word "[ -]+")
                  word))
    
    (defun my/cloze-deletion-replace (word start end)
      "Convert WORD into cloze deletion.
    OWRD can be determined a string between START and END.
    
    When called Interactively, WORD is either a word at point or a string in region."
      (interactive (cond
                    ((region-active-p)
                     `(,(buffer-substring (region-beginning) (region-end))
                       ,(region-beginning)
                       ,(region-end)))
                    (t
                     (let ((bounds (bounds-of-thing-at-point 'word)))
                       `(,(thing-at-point 'word)
                         ,(car bounds)
                         ,(cdr bounds))))))
      (replace-string word
                      (my/cloze-deletion word)
                      nil
                      start
                      end))
    
    (with-eval-after-load 'org-ql-view
      (push `("English words to learn"
              :buffers-files ,(list my/org-english-file)
              :query (and (tags "drill")
                          (tags "fd_en"))
              :sort (scheduled date))
            org-ql-views))
    
    (with-eval-after-load 'org-capture
      (add-to-list 'org-capture-templates
                   '("!0" "drill entry for english word"
                     entry (id "de0983a7-9d1a-4ef8-b882-148c401e862d")
                     "* %i :drill:fd_en:
    [%(my/english-japanese-translate org-english-word t) %(my/english-pronunciation org-english-word t)]
    %(my/org-english-insert-english-translation)
    - %a
      %c"))
      (add-to-list 'org-capture-templates
                   '("!1" "drill entry for english word"
                     item (function my/org-english-goto-match)
                     "- %a
      %c")))
    
    (defun my/org-english-goto-match ()
      "Goto function for org-capture-template designed to insert an edditional example
    to existing english word entry."
      (org-goto-marker-or-bmk
       (plist-get (cadar my/org-english-match-entry) :org-marker)))
    
    (defun my/org-english-insert-english-translation ()
      "Insert English translation into org capture entry."
      (let* ((url (format "http://wordnik.com/words/%s" (downcase org-english-word)))
             (buffer (url-retrieve-synchronously url t t))
             (vertico-sort-function nil))
        (with-temp-buffer
          (url-insert-buffer-contents buffer url)
          (completing-read "match" (split-string (funcall 'my/define-word--parse-wordnik) "\n")))))
    
    (advice-add #'my/org-english-insert-english-translation :around #'my/define-word--fix-31)
    
    (defun my/english-japanese-translate (word &optional sync)
      "Search Japanese translation for WORD and add it to kill ring.
    
    SYNC handles whether network access procedure is synchronous.
    Return translation result as a string If SYNC is non-nil.
    If called interactively, display results and add them to kill ring."
      (let (trans)
        (request
          (concat "https://ejje.weblio.jp/content/" (downcase word))
          :sync sync
          :timeout 60
          :parser
          (lambda ()
            (let ((str (buffer-string)))
              (with-temp-buffer
                (insert str)
                (shr-render-region (point-min) (point-max))
                (buffer-substring-no-properties (point-min) (point-max)))))
          :success
          (cl-function
           (lambda (&key data &allow-other-keys)
             (and (string-match
                   (rx (seq "意味・対訳 "
                            (group-n 1 (+ (or (category base)
                                              (category combining-diacritic)
                                              (category latin)
                                              (category japanese-katakana-two-byte)
                                              (category korean)
                                              (category greek))))))
                   data)
                  (setq trans (string-trim (match-string 1 data)))))))
        trans))
    
    (defun my/english-pronunciation (word &optional sync)
      "Search pronunciation information for WORD and add it to kill ring.
    
    SYNC handles whether network access procedure is synchronous.
    Return search infromation as a string If SYNC is non-nil.
    Display information and add it to kill ring if called interactively."
      (let (pron)
        (request
          (concat "https://eow.alc.co.jp/search?q=" (string-replace " " "+" (downcase word)))
          :sync sync
          :timeout 10
          :parser
          (lambda ()
            (let ((str (buffer-string)))
              (with-temp-buffer
                (insert str)
                (shr-render-region (point-min) (point-max))
                (buffer-substring-no-properties (point-min) (point-max)))))
          :success
          (cl-function
           (lambda (&key data &allow-other-keys)
             (and (string-match
                   (rx (seq (opt "【レベル】" (** 1 2 digit) "、")
                            "【発音" (opt "!") "】"
                            (+ (or (category combining-diacritic)
                                   (category latin)
                                   (category japanese-katakana-two-byte)
                                   (category korean)
                                   (category greek)))
                            "、"
                            (? (or (seq "【@】" (opt "[US]")
                                        (+ (or (category japanese-katakana-two-byte)
                                               "(" ")"))
                                        (? "、"))
                                   (seq (+ any) "*")))))
                   data)
                  (setq pron (replace-regexp-in-string
                              "\\[\\(.\\{2\\}\\)\\]" "【\\1】"
                              (string-trim-right (match-string 0 data)
                                                 "、")))))))
        pron))
    
    (defmacro with-org-drill-english-config (&rest body)
      "Evaluate BODY with config for Org-Drill for english words review."
      `(progn
         (org-id-goto "de0983a7-9d1a-4ef8-b882-148c401e862d")
         (let (;; imperative settings
               (org-drill-scope 'tree)
               (org-drill-question-tag "drill")
               (org-drill-hide-item-headings-p t)
               ;; drill session regulations
               (org-drill-maximum-duration 25)
               (org-drill-maximum-items-per-session 50)
               ;; Definition of old and overdue items
               (org-drill-overdue-interval-factor 1.4) ; more permissive for overdue than 1.2 (default)
               (org-drill-days-before-old 15)
               ;; Spaced repetition algorithm
               (org-drill-spaced-repetition-algorithm 'sm5)
               (org-drill-sm5-initial-interval 7.0) ; initial step is 7 day
               ;; Random variation of repetition intervals
               (org-drill-add-random-noise-to-intervals-p t)
               ;; Adjustment for early or late review of items
               (org-drill-adjust-intervals-for-early-and-late-repetitions-p t)
               ;; Adjusting item difficulty globally
               (org-drill-learn-fraction 0.43) ; intend not to reschedule drastically forward in 10th+ interval
               (org-startup-indented nil)
               (logos-hide-cursor t)
               (logos-hide-mode-line t)
               (logos-hide-buffer-boundaries t)
               (logos-buffer-read-only nil)
               (logos-olivetti nil)
               (logos-hide-fringe t))
           (org-indent-mode -1)
           (visual-line-mode 1)
           (text-scale-set -1)
           (logos-focus-mode 1)
           ,@body)
         (org-indent-mode 1)
         (text-scale-set 0)
         (visual-line-mode -1)
         (logos-focus-mode -1)))
    
    ;;;###autoload
    (defun my/org-english-drill ()
      "Invoke Org-Drill for English word review."
      (interactive)
      (with-org-drill-english-config (org-drill)))
    
    (defun my/org-english-drill-resume ()
      "Resume Org-Drill session for english word review."
      (interactive)
      (with-org-drill-english-config (org-drill-resume)))
    
    (defun my/org-english-link ()
      "Make a link pointing the word with context."
      (interactive)
      (save-excursion
        (let ((current-prefix-arg '(4)))
          (call-interactively #'org-refile ))
        (call-interactively #'org-store-link))
      (let* ((contexts '("synonym" "antonym" "collocation"
                         "distinct" "similar" "akin" "relevant"
                         "morphological" "derivative"))
             (context (completing-read "Context: " contexts))
             (org-log-note-headings (append `((context . ,(format "<%s> %%s %%t" context)))
                                            org-log-note-headings))
             (link-str (with-temp-buffer
                         (call-interactively #'org-insert-last-stored-link)
                         (concat " " ; workaround for org-ql failing to recognize org-link with preceding "
                                 (buffer-substring-no-properties (point-min) (1- (point-max)))
                                 " "))))
        (org-add-log-setup 'context link-str
                           nil 'state (format-time-string (org-time-stamp-format t t)))
        (run-hooks 'post-command-hook)))
    
    (defun my/org-english-list-undrilled ()
      "List english words which haven't been tested before in level ascending order."
      (interactive)
      (with-current-buffer (find-file-noselect my/org-english-file)
        (org-ql-search my/org-english-file
                       '(and (tags "drill")
                             (tags "fd_en")
                             (not (property "DRILL_LAST_REVIEWED")))
                       :sort 'my/org-ql--english-level<)))
    
    (defun my/org-english-search (word)
      "Look up WORD in my archive files using `org-ql-search'."
      (interactive (list (if (use-region-p)
                             (buffer-substring (region-beginning) (region-end))
                           (read-string "Word: "))))
      (org-ql-search
        my/org-english-file
        `(heading-regexp ,(eval `(rx word-start
                                     ,(downcase word)
                                     word-end)))))
    
    (defun my/org-english-level (word)
      "Return difficulty level of `WORD' in number."
      (cl-labels ((parse-level (text)
                    (if (string-match
                         (rx (seq "【レベル】"
                                  (group-n 1 (repeat 1 2 digit))))
                         text)
                        (match-string 1 text)
                      nil)))
        (let ((local-str (with-current-buffer (find-file-noselect my/org-english-file)
                           (let ((headline (org-ql-select
                                             `(,my/org-english-file)
                                             `(and (heading-regexp ,(eval `(rx word-start
                                                                               ,(downcase word)
                                                                               word-end)))
                                                   (tags "drill"))
                                             :action 'element-with-markers)))
                             (org-with-point-at (plist-get (cadar headline) :org-marker)
                               (org-get-entry))))))
          (string-to-number (or (and local-str
                                     (parse-level local-str))
                                ;; CAVEAT: getting level from web costs a lot
                                ;; (when-let ((remote-str (my/english-pronunciation word)))
                                ;;   (parse-level remote-str))
                                "99")))))
    
    (defun my/org-ql--english-level< (a b)
      "Return non-nil if A's difficulty level is higher than B's.
    A and B are Org headline elements."
      (cl-macrolet ((level (item)
                      `(my/org-english-level
                        (org-element-property :raw-value ,item))))
        (let ((a-level (level a))
              (b-level (level b)))
          (cond ((and (numberp a-level) (numberp b-level))
                 (< a-level b-level))
                (a-level t)
                (b-level nil)))))
    
    (provide 'org-drill-english)
    

    Call ERT with test-org-english-pron to test this code.

    test code

    (require 'ert)
    
    (ert-deftest test-org-english-pron ()
      "Tests the output string of `my/org-english-insert-japanese-pronunciation'."
      (should (string= (my/org-english-insert-japanese-pronunciation "rife")
                       "【レベル】11、【発音】ráif、【@】ライフ"))
      (should (string= (my/org-english-insert-japanese-pronunciation "congregate")
                       "【レベル】12、【発音!】【US】《動》kɑ́ngrəgèit 《形》kɑ́ŋgrəgit | 【UK】《動》kɔ́ŋgrigèit 《形》kɔ́ŋgrigit"))
      (should (string= (my/org-english-insert-japanese-pronunciation "unleash")
                       "【レベル】9、【発音】ʌ̀nlíːʃ、【@】アンリーシュ"))
      (should (string= (my/org-english-insert-japanese-pronunciation "homage")
                       "【レベル】11、【発音】【US】 hɑ́midʒ | 【UK】 hɔ́midʒ、【@】【US】ハミジ、【UK】ホミジ"))
      (should (string= (my/org-english-insert-japanese-pronunciation "interstitial")
                       "【発音】ìntərstíʃəl、【@】インタ(ー)スティシャル"))
      (should (string= (my/org-english-insert-japanese-pronunciation "veterinarian")
                       "【レベル】12、【発音】vètərənέəriən、【@】ヴェトラネアリアン"))
      (should (string= (my/org-english-insert-japanese-pronunciation "warehouse")
                       "【レベル】8、【発音!】《動》wέərhàuz 《名》wέərhàus、【@】ウェアハウス、ウエアハウス"))
      (should (string= (my/org-english-insert-japanese-pronunciation "lambaste")
                       ""))
      (should (string= (my/org-english-insert-japanese-pronunciation "in a nutshell")
                       ""))
      (should (string= (my/org-english-insert-japanese-pronunciation "traverse")
                       "【レベル】8、【発音!】《名・形》trǽvəːrs 《動》trəvʌ́rs、【@】トゥラヴァース、トラバース")))
    
  • org-relate   ARCHIVE
  • org-clocktable-by-tag

    Provide a function to Build a clock table summing up time by tag.

    (with-eval-after-load 'org
      (require 'org-clocktable-by-tag))
    
    (require 'org-table)
    (require 'org-clock)
    
    (defun clocktable-by-tag/shift-cell (n)
      (let ((str ""))
        (dotimes (i n)
          (setq str (concat str "| ")))
        str))
    
    (defun clocktable-by-tag/insert-tag (params)
      (let ((match (plist-get params :match)))
        (insert "|--\n")
        (insert (format "| %s | *Tag time* |\n" match))
        (let ((total 0))
          (mapcar
           (lambda (file)
             (let ((clock-data (with-current-buffer (find-file-noselect file)
                                 (org-clock-get-table-data (buffer-name) params))))
               (when (> (nth 1 clock-data) 0)
                 (setq total (+ total (nth 1 clock-data)))
                 (insert (format "| | File *%s* | %.2f |\n"
                                 (file-name-nondirectory file)
                                 (/ (nth 1 clock-data) 60.0)))
                 (dolist (entry (nth 2 clock-data))
                   (insert (format "| | . %s%s | %s %.2f |\n"
                                   (org-clocktable-indent-string (nth 0 entry))
                                   (replace-regexp-in-string "|" "\\vert\{\}" (nth 1 entry) nil t)
                                   (clocktable-by-tag/shift-cell (nth 0 entry))
                                   (/ (nth 4 entry) 60.0)))))))
           (org-agenda-files))
          (if (= total 0)
              (save-excursion
                (re-search-backward "*Tag time*")
                (forward-line -1)
                (dotimes (i 2)
                  (org-table-kill-row)))
            (save-excursion
              (re-search-backward "*Tag time*")
              (org-table-next-field)
              (org-table-blank-field)
              (insert (format "*%.2f*" (/ total 60.0))))))
        (org-table-align)))
    
    ;;;###autoload
    (defun org-dblock-write:clocktable-by-tag (params)
      (funcall indent-line-function)
      (insert "| Tag | Headline | Time (h) |\n")
      (insert "|     |          | <r>  |\n")
      (let ((matches
             (org-global-tags-completion-table)))
        (mapcar (lambda (match)
                  (let ((match-str (car match))
                        (case-fold-search nil))
                    (when (string-match-p "^AC_" match-str)
                      (setq params (plist-put params :match match-str))
                      (clocktable-by-tag/insert-tag params))))
                matches)))
    
    (provide 'org-clocktable-by-tag)
    
  • weather
    ;; 気象庁配信の天気情報を加工して表示
    
    (require 'request)
    (require 'cl-lib)
    
    (defvar weather-areas
      '(((url . "http://www.drk7.jp/weather/xml/14.xml")
         (pref . "神奈川県")
         (area . "西部"))
        ((url . "http://www.drk7.jp/weather/xml/22.xml")
         (pref . "静岡県")
         (area . "中部"))))
    
    (defun weather-area-show (weather-area-alist weather-buffer)
      (lexical-let ((url (assoc-default 'url weather-area-alist))
                    (pref (assoc-default 'pref weather-area-alist))
                    (area (assoc-default 'area weather-area-alist))
                    (buffer weather-buffer))
        (request
          url
          :parser
          (lambda ()
            (let ((xml-string (string-as-multibyte (string-as-unibyte (buffer-string)))))
              (with-temp-buffer
                (erase-buffer)
                (insert xml-string)
                (libxml-parse-xml-region (point-min) (point-max)))))
          :success
          (cl-function
           (lambda (&key data &allow-other-keys)
             (when data
               (with-current-buffer buffer
                 (insert (format "%s %s\n" pref area))
                 (mapcar
                  (lambda (info-node)
                    (insert
                     (format "%s %s %s %s\n"
                             (dom-attr info-node 'date)
                             (apply (lambda (max-temp-node min-temp-node)
                                      (format "%2d/%2d℃"
                                              (string-to-number (dom-text min-temp-node))
                                              (string-to-number (dom-text max-temp-node))))
                                    (dom-by-tag (dom-child-by-tag info-node 'temperature) 'range))
                             (apply (lambda (node-1 node-2 node-3 node-4)
                                      (format "%2d-%2d-%2d-%2d%"
                                              (string-to-number (dom-text node-1))
                                              (string-to-number (dom-text node-2))
                                              (string-to-number (dom-text node-3))
                                              (string-to-number (dom-text node-4))))
                                    (dom-by-tag (dom-child-by-tag info-node 'rainfallchance) 'period))
                             (dom-text (dom-child-by-tag info-node 'weather)))))
                  (dom-by-tag (dom-by-id data area) 'info))
                 (insert "\n"))))))))
    
    (defun weather ()
      (interactive)
      (let ((buffer (get-buffer-create "weather")))
        (mapcar (lambda (weather-area)
                  (weather-area-show weather-area buffer))
                weather-areas)
        (switch-to-buffer buffer)))
    
    (provide 'weather)
    
  • dispatch-dwim
    ;; -*- lexical-binding: t -*-
    
    (require 'transient)
    (require 'detached)
    
    ;;; Variables
    
    (defvar dispatch-dwim--precedings-followings nil)
    (defvar dispatch-dwim--overriding-method nil)
    
    (defvar dispatch-dwim--default-method 'detached-shell-command)
    
    (add-hook 'transient-exit-hook
              (lambda () (unless transient--stack
                       (setq dispatch-dwim--precedings-followings nil
                             dispatch-dwim--overriding-method nil))))
    
    ;;; Classes & Objects
    
    (defclass dispatch-shell-command (transient-suffix)
      ((command-format :initarg :command-format :initform "%a")
       (command-format-spec :initarg :command-format-spec :initform nil)
       (dispatch-method :initarg :dispatch-method)))
    
    (cl-defmethod transient-init-value ((obj dispatch-shell-command))
      (unless (slot-boundp obj :command-format)
        (oset obj command-format "%a"))
      (oset obj dispatch-method
            (cond
             ((and transient--stack
                   dispatch-dwim--overriding-method))
             ((member major-mode '(minibuffer-mode eshell-mode))
              'insert)
             (t dispatch-dwim--default-method))))
    
    (transient-define-suffix dispatch-dwim-execute ()
      :class dispatch-shell-command
      :transient 'transient--do-return
      :key "RET" :description "execute"
      (interactive)
      (let* ((cur-obj (transient-suffix-object))
             (command-format-spec (and (slot-boundp cur-obj :command-format-spec)
                                       (oref cur-obj command-format-spec)))
             (main-comm (format-spec
                         (replace-regexp-in-string
                          "\\\\\%" "\\\\\%\%" ; avoid replacing "\%" in shell argument
                          (eval (oref cur-obj command-format)))
                         (append
                          `((?a . ,(string-join (transient-get-value) " ")))
                          (when command-format-spec
                            (mapcar (lambda (key-val) (cons (car key-val) (eval (cdr key-val))))
                                    command-format-spec)))))
             (comm (mapconcat (lambda (elm)
                                (when (length> (cadr elm) 0)
                                  (concat (cadr elm) "; ")))
                              (sort (append dispatch-dwim--precedings-followings
                                            `(("@" ,main-comm 50)))
                                    (lambda (a b) (< (nth 2 a) (nth 2 b)))))))
        (apply (oref cur-obj dispatch-method)
               `(,(if transient--stack
                      main-comm comm)))
        (unless transient--stack
          (setq dispatch-dwim--precedings-followings nil))))
    
    (defclass dispatch-dwim-stack-suffix (transient-suffix)
      ((prefix :initarg :prefix)
       (prefix-arg :initarg :prefix-arg :initform nil)
       (priority :initarg :priority :initform 0)
       (default :initarg :default)
       (format :initform " %k %d (%v)")))
    
    (transient-define-suffix dispatch-dwim-fraction-command ()
      :class dispatch-dwim-stack-suffix
      :transient 'transient--do-recurse
      (interactive)
      (let ((cur-obj (transient-suffix-object)))
        (setq dispatch-dwim--overriding-method
              (lambda (shell-command)
                (setf (alist-get (oref cur-obj key) dispatch-dwim--precedings-followings)
                      (list shell-command
                            (oref cur-obj priority)))))
        (apply (oref cur-obj prefix)
               (oref cur-obj prefix-arg))))
    
    (cl-defmethod transient-init-value ((obj dispatch-dwim-stack-suffix))
      (when (slot-boundp obj :default)
        (setf (alist-get (oref obj key) dispatch-dwim--precedings-followings)
              (list (oref obj default)
                    (oref obj priority)))))
    
    (cl-defmethod transient-format-value ((obj dispatch-dwim-stack-suffix))
      (if-let ((comm (assoc (oref obj key)
                            dispatch-dwim--precedings-followings)))
          (propertize (cadr comm)
                      'face 'transient-argument)
        ""))
    
    (cl-defmethod transient-format ((obj dispatch-dwim-stack-suffix))
      (format-spec (oref obj format)
                   `((?k . ,(transient-format-key obj))
                     (?d . ,(transient-format-description obj))
                     (?v . ,(transient-format-value obj)))))
    
    (defun dispatch-shell-command--key-vals (keys)
      (mapconcat 'transient-infix-value
                 (seq-filter (lambda (obj) (and (member (oref obj key) keys) obj))
                             (transient-suffixes (oref transient-current-prefix command)))
                 " "))
    
    ;;; Functions
    
    (defmacro dispatch-dwim-determine-file-name ()
      "Determine the available file name depending on the condition."
      `(cond
        ((derived-mode-p 'dired-mode)
         (dired-get-marked-files))
        ((derived-mode-p 'minibuffer-mode)
         (cond
          ((equal minibuffer-history-variable 'dired-shell-command-history)
           " * ")
          ((or (member minibuffer-history-variable
                       '(detached-shell-command-history
                         shell-command-history)))
           (with-current-buffer (other-buffer nil t)
             (when (derived-mode-p 'dired-mode)
               (dired-get-marked-files))))))
        (t (ffap-file-at-point))))
    
    (defmacro dispatch-dwim-determine-url ()
      "Determine the URL for dispatch-dwim commands, respecting the conditions."
      '(cond
        ((thing-at-point-url-at-point))
        ((derived-mode-p 'org-mode)
         (org-link-at-point-map #'car))
        ((derived-mode-p 'eww-mode)
         (or (shr-url-at-point nil)
             (plist-get eww-data :url)))))
    
    (defun dispatch-dwim-shell-argument (arg)
      "Return a ARG's valid format for shell arguments."
      (pcase arg
        ((and (pred (lambda (a) (member a `(" * " "?" "`?`")))) file-substitute)
         file-substitute)
        ((and (pred stringp) (pred file-exists-p) file)
         (shell-quote-argument (expand-file-name file)))
        ((and (pred stringp) (pred (string-match ffap-url-regexp)) url)
         (format "'%s'" url))
        ((and (pred listp) li) (mapconcat #'dispatch-dwim-shell-argument li " "))))
    
    (provide 'dispatch-dwim)
    
  • fingertip mode
    ;;; fingertip.el --- one-handed buffer browsing -*- lexical-binding: t -*-
    
    ;; Author: p-snow
    ;; Version: 0.0.1
    ;; Package-Requires: no
    ;; Homepage: homepage
    ;; Keywords: files
    
    ;; This file is not part of GNU Emacs
    
    ;; This program is free software: you can redistribute it and/or modify
    ;; it under the terms of the GNU General Public License as published by
    ;; the Free Software Foundation, either version 3 of the License, or
    ;; (at your option) any later version.
    
    ;; This program is distributed in the hope that it will be useful,
    ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
    ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ;; GNU General Public License for more details.
    
    ;; You should have received a copy of the GNU General Public License
    ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    ;;; Commentary:
    
    ;; commentary
    
    ;;; Code:
    
    (defgroup fingertip nil
      "Customization group for fingertip minor mode."
      :group 'text)
    
    (defvar-keymap fingertip-mode-map
      :doc "Keymap for `fingertip-mode'."
      "f" #'scroll-up-line
      "e" #'scroll-down-line
      "SPC" #'fingertip-forward-paragraph
      "E" #'fingertip-backward-paragraph
      ">" #'fingertip-scroll-left-column
      "<" #'fingertip-scroll-right-column)
    
    ;;; Minor modes
    ;;;###autoload
    (define-minor-mode fingertip-mode
      "Buffer-local mode for one-handed file browsing."
      :init-value nil
      :global nil
      :lighter " FGT"
      :keymap fingertip-mode-map)
    
    (defun fingertip-forward-paragraph ()
      (interactive)
      (forward-paragraph 2)
      (backward-paragraph 1)
      (forward-line 1)
      (recenter-top-bottom 0))
    
    (defun fingertip-backward-paragraph ()
      (interactive)
      (backward-paragraph 2)
      (forward-paragraph 1)
      (backward-paragraph 1)
      (forward-line 1)
      (recenter-top-bottom 0))
    
    (defun fingertip-scroll-left-column ()
      (interactive)
      (scroll-left 1))
    
    (defun fingertip-scroll-right-column ()
      (interactive)
      (scroll-right 1))
    
    (provide 'fingertip)
    ;;; fingertip.el ends here
    

Findutils

GNU Findutils offers following commands

  • find
  • locate
  • updatedb
  • xargs

'locate' is GNU implementation of mlocate. I prefer '[BROKEN LINK: id:c56e19dd-d3b1-4595-be74-0ff7c6558bc8]', which is fast than mlocate, for indexed file searching,

"findutils"

plocate

"plocate"
echo -n '<<share-dir()>>/plocate/plocate.db'
export LOCATE_PATH=<<plocate-db()>>
$GUIX_EXTRA_PROFILES/base/base/sbin/updatedb -l 0 -U <<home-dir()>> --prune-bind-mounts 0 \
                                             -e <<htmlize-dir()>> \
                                             -e <<trash-dir()>> \
                                             -e <<data-dir()>> \
                                             -o <<plocate-db()>>
*/20 * * * * /usr/bin/bash -ci "updatedb-home"

Git

"git"

git ignore

#
# ~/.gitignote
#
## Compiled object files ##
*.slo
*.lo
*.o
*.obj

## Compiled Dynamic libraries ##
*.so
*.dylib
*.dll

## Compiled Static libraries ##
*.lai
*.la
*.a
*.lib

## Executables ##
*.out
*.app

## Windows ##
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msm
*.msp

## Mac ##
.DS_Store
.AppleDouble
.LSOverride
.Spotlight-V100
.Trashes
._*

## Linux ##
locate.db

## version control system ##
.git/
.hg/
.svn/

## backup,log ##
*~
~*
*.swp
.swp.*
*.tmp
*.bak
*.old
*.log
.cache/
*.autosave

## Emacs ##
*.elc

## Vim ##
*.un~
Session.vim
.netrwhist

## GNU GLOBAL ##
GPATH
GRTAGS
GTAGS

## netbeans ##
nbproject/
## intellij idea ##
.idea/
## eclipse ##
.settings/
.project
.classpath
.buildpath
## XCode ##
*.xcodeproj/*

## Build dir ##
build/

git config

This page show you how to get diffs of gpg files in Magit.

[core]
	excludesfile = <<home-dir()>>/.config/git/info/ignore
	attributesfile = <<home-dir()>>/.config/git/info/attributes
	editor = emacs
	symlinks = true
	# Unicode
	precomposeunicode = true
	quotepath = true
	autocrlf = false
[push]
	# simple,matching,upstream,current
	default = simple
[color]
	ui = auto
	status = auto
	diff = auto
	branch = auto
	interactive = auto
	grep = auto
[diff]
	patience = true
[diff "gpg"]
	textconv = gpg --no-tty --decrypt
[diff "common-lisp"]
	xfuncname="^\\((def\\S+\\s+\\S+)"
[diff "elisp"]
	xfuncname="^\\((((def\\S+)|use-package)\\s+\\S+)"
[diff "org"]
	xfuncname="^\\*+ +(.*)$"
[help]
	autocorrect = 0
[alias]
	co = checkout
	ca = commit -a -v
	ce = commit -v --amend
	st = status --branch --short
	si = status --ignored --short
	branches = branch -a
	remotes = remote -v
	tags = tag -l
	lg = log --graph --all --decorate --abbrev-commit --branches --date=short --pretty=format:\"%C(red)%h%C(reset) %C(green)[%ad]%C(reset) %s %C(cyan)@%an%C(reset) %C(yellow)%d%C(reset)\"
	fp = fetch --prune
	di = diff
	dh = diff --histogram
	dw = diff --word-diff
	dc = diff --cached
	wc = whatchanged
[user]
	email = 6841207+p-snow@users.noreply.github.com
	name = p-snow
	signingkey = BDAF62D24445EFD3
[commit]
	gpgsign = true
<<git-config>>

git attributes

*.c diff=cpp
*.h diff=cpp
*.cpp diff=cpp
*.hpp diff=cpp
*.cs diff=csharp
*.m diff=objc
*.java diff=java
*.html diff=html
*.xml diff=html
*.pl diff=perl
*.pm diff=perl
*.t diff=perl
*.php diff=php
*.ptml diff=php
*.py diff=python
*.rb diff=ruby
*.js diff=java
*.csv encoding=cp932
*.json diff=json
*.gpg filter=gpg diff=gpg
*.org diff=org
*.lisp diff=common-lisp
*.el diff=elisp

pass-git-helper

pass-git-helper is a utility program that allows users to use password-store's passwords for git credentials.

"pass-git-helper"
[credential]
	helper = !pass-git-helper -m <<home-dir()>>/.config/pass-git-helper/git-pass-mapping.ini $@

git-annex

git-annex aims to manage large files based on git system. It is alse designed to manage copies of binary in distributed repositories. walkthrough offers handy instruction.

Git-LFS is alse designed to manage large files. This article compares Git-LFS and git-annex precisely.

"git-annex"
("https://git-annex.branchable.com/news/index.rss" soft_update)
("https://git-annex.branchable.com/devblog/index.rss" soft_update)
[annex]
	youtube-dl-command = yt-dlp
	synccontent = false
[annex.security]
	allowed-ip-addresses = all
[safe]
	directory = /mnt/ns01/doc

sync annex repos

40 */4 * * * /usr/bin/bash -ci "sync-annex-doc 2>&1 | tee -a <<log-dir()>>/sync-git-annex.log"
function sync_doc {
  if cd $1; then
    if [ $2 -ne 0 ]; then
      (git rev-parse --is-inside-work-tree 2>/dev/null && git annex sync && git annex get)
    else
      (git rev-parse --is-inside-work-tree 2>/dev/null && git annex sync)
    fi
  fi
}

sync_doc /mnt/exdoc/doc/ 1
sync_doc /mnt/vt-m/doc/ 1
sync_doc /mnt/vt-h/doc/ 1
sync_doc /mnt/vt-j/doc/ 1
sync_doc <<doc-dir()>> 0
sync_doc /mnt/exdoc/doc/ 0

ghq

ghq is management system for remote git repositories.

"ghq"
[ghq]
	root = <<ghq-root()>>
[github.com*]
username_extractor=specific_line
line_username=2
target=github.com
# username_extractor=entry_name
ghq list --full-path $name | head -n 1 | sed "s:$HOME:~:"
(defun ghq-list (query)
  "Return a list containing all ghq repositories matching QUERY."
  (split-string (string-trim-right
                 (shell-command-to-string
                  (format "ghq list --full-path '%s'" query)))
                "\n" t))

personal ghq environment

echo -n '<<share-dir()>>/ghq'

Pass

Pass is a simple password manager.

I've chosen to use gpg/gpg-agent and pass on Ubuntu system since pass (password-store) spouts a warning saying there's gpg version mismatch persistently.

;; "password-store"
[github.com*]
username_extractor=specific_line
line_username=2
target=github.com
# username_extractor=entry_name
  • additional environment variables

    export PASSWORD_STORE_CLIP_TIME=25
    

mpv

"mpv-libarchive"
  • mpv.conf

    ##################
    # video settings #
    ##################
    
    # Start in fullscreen mode by default.
    fs=yes
    
    
    ###########
    # General #
    ###########
    
    quiet
    save-position-on-quit
    no-border                               # no window title bar
    msg-module                              # prepend module name to log messages
    msg-color                               # color log messages on terminal
    term-osd-bar                            # display a progress bar on the terminal
    use-filedir-conf                        # look for additional config files in the directory of the opened file
    pause                                   # no autoplay
    force-window=immediate
    keep-open                               # keep the player open when a file's end is reached
    autofit-larger=100%x95%                 # resize window in case it's larger than W%xH% of the screen
    cursor-autohide-fs-only                 # don't autohide the cursor in window mode, only fullscreen
    # input-media-keys=no                     # enable/disable OSX media keys
    cursor-autohide=1000                    # autohide the curser after 1s
    
    screenshot-format=png
    screenshot-png-compression=8
    screenshot-template='~/Desktop/%F (%P) %n'
    
    hls-bitrate=max                         # use max quality for HLS streams
    
    
    #########
    # Cache #
    #########
    
    cache=yes
    cache-secs=10                           # how many seconds of audio/video to prefetch if the cache is active
    
    
    #############
    # OSD / OSC #
    #############
    
    osd-level=1                             # enable osd and display --osd-status-msg on interaction
    osd-duration=2500                       # hide the osd after x ms
    osd-status-msg='${time-pos} / ${duration}${?percent-pos: (${percent-pos}%)}${?frame-drop-count:${!frame-drop-count==0: Dropped: ${frame-drop-count}}}\n${?chapter:Chapter: ${chapter}}'
    # osd-status-msg='${=time-pos}'         # show raw position
    
    osd-font='Source Sans Pro'
    osd-font-size=96
    osd-color='#CCFFFFFF'                   # ARGB format
    osd-border-color='#DD322640'            # ARGB format
    #osd-shadow-offset=1                    # pixel width for osd text and progress bar
    osd-bar-align-y=0                       # progress bar y alignment (-1 top, 0 centered, 1 bottom)
    osd-border-size=2                       # size for osd text and progress bar
    osd-bar-h=2                             # height of osd bar as a fractional percentage of your screen height
    osd-bar-w=60                            # width of " " "
    
    
    #############
    # Subtitles #
    #############
    
    sub-use-margins
    sub-ass-force-margins
    
    demuxer-mkv-subtitle-preroll            # try to correctly show embedded subs when seeking
    sub-auto=fuzzy                          # external subs don't have to match the file name exactly to autoload
    embeddedfonts=yes                       # use embedded fonts for SSA/ASS subs
    sub-fix-timing=no                       # do not try to fix gaps (which might make it worse in some cases)
    sub-ass-style-overrides=Kerning=yes     # allows you to override style parameters of ASS scripts
    
    # the following options only apply to subtitles without own styling (i.e. not ASS but e.g. SRT)
    sub-font="Source Sans Pro Semibold"
    sub-font-size=36
    sub-color="#FFFFFFFF"
    sub-border-color="#FF262626"
    sub-border-size=3.2
    sub-shadow-offset=1
    sub-shadow-color="#33000000"
    sub-spacing=0.5
    
    
    #############
    # Languages #
    #############
    
    slang=enm,en,eng,de,deu,ger             # automatically select these subtitles (decreasing priority)
    alang=ja,jp,jpn,en,eng,de,deu,ger       # automatically select these audio tracks (decreasing priority)
    
    #########
    # Image #
    #########
    
    image-display-duration=4
    
    #########
    # Audio #
    #########
    
    audio-file-auto=fuzzy                   # external audio doesn't has to match the file name exactly to autoload
    audio-pitch-correction=yes              # automatically insert scaletempo when playing with higher speed
    volume-max=200                          # maximum volume in %, everything above 100 results in amplification
    volume=100                              # default volume, 100 = unchanged
    
    
    ################
    # Video Output #
    ################
    
    # Active VOs (and some other options) are set conditionally
    # See here for more information: https://github.com/wm4/mpv-scripts/blob/master/auto-profiles.lua
    # The script was modified to import functions from scripts/auto-profiles-functions.lua
    
    # Defaults for all profiles
    tscale=catmull_rom                      # sharp: oversample <-> linear (triangle) <-> catmull_rom <-> mitchell <-> gaussian <-> bicubic : smooth
    opengl-early-flush=no
    opengl-pbo=yes
    
    
    [high-quality]
    profile-desc=cond:is_desktop() and get('width', math.huge) < 3840
    scale=ewa_lanczossharp
    cscale=ewa_lanczossoft
    dscale=mitchell
    scale-antiring=0.7
    cscale-antiring=0.7
    dither-depth=auto
    correct-downscaling=yes
    sigmoid-upscaling=yes
    deband=yes
    hwdec=no
    
    [mid-quality]
    profile-desc=cond:(is_laptop() and not on_battery() and get('width', math.huge) < 1920) or (is_desktop() and get('width', math.huge) >= 3840)
    scale=spline36
    cscale=bilinear
    dscale=mitchell
    scale-antiring=1.0
    cscale-antiring=1.0
    dither-depth=auto
    correct-downscaling=yes
    sigmoid-upscaling=yes
    deband=yes
    hwdec=no
    
    [low-quality]
    profile-desc=cond:is_laptop() and (on_battery() or get('width', math.huge) >= 1920)
    scale=bilinear
    cscale=bilinear
    dscale=bilinear
    scale-antiring=0.0
    cscale-antiring=0.0
    dither-depth=no
    correct-downscaling=no
    sigmoid-upscaling=no
    deband=no
    hwdec=auto
    
    [60FPS]
    profile-desc=cond:is_laptop() and get('container-fps', 0) >= 59
    scale=bilinear
    cscale=bilinear
    
    [4K]
    profile-desc=cond:get('width', -math.huge) >= 3840
    vd-lavc-threads=32
    
    [4K-inverted]
    profile-desc=cond:get('width', -math.huge) < 3840
    vd-lavc-threads=0
    
    
    [default]
    
    
    ###################################
    # Protocol Specific Configuration #
    ###################################
    
    [protocol.https]
    #cache=yes
    #cache-default=500000                    # size in KB
    #cache-backbuffer=250000                 # size in KB
    cache-secs=100                          # how many seconds of audio/video to prefetch
    user-agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'
    
    [protocol.http]
    #cache=yes
    #cache-default=500000                    # size in KB
    #cache-backbuffer=250000                 # size in KB
    cache-secs=100                          # how many seconds of audio/video to prefetch
    user-agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0'
    
    [extension.gif]
    cache=no
    no-pause
    loop-file=yes
    
    [extension.webm]
    #cache=no
    #no-pause
    #loop-file=yes
    
  • input.conf

    # mpv keybindings
    #
    # Location of user-defined bindings: ~/.config/mpv/input.conf
    #
    # Lines starting with # are comments. Use SHARP to assign the # key.
    # Copy this file and uncomment and edit the bindings you want to change.
    #
    # List of commands and further details: DOCS/man/input.rst
    # List of special keys: --input-keylist
    # Keybindings testing mode: mpv --input-test --force-window --idle
    #
    # Use 'ignore' to unbind a key fully (e.g. 'ctrl+a ignore').
    #
    # Strings need to be quoted and escaped:
    #   KEY show-text "This is a single backslash: \\ and a quote: \" !"
    #
    # You can use modifier-key combinations like Shift+Left or Ctrl+Alt+x with
    # the modifiers Shift, Ctrl, Alt and Meta (may not work on the terminal).
    #
    # The default keybindings are hardcoded into the mpv binary.
    # You can disable them completely with: --no-input-default-bindings
    
    # Developer note:
    # On compilation, this file is baked into the mpv binary, and all lines are
    # uncommented (unless '#' is followed by a space) - thus this file defines the
    # default key bindings.
    
    # If this is enabled, treat all the following bindings as default.
    #default-bindings start
    
    #MOUSE_BTN0 ignore                      # don't do anything
    #MOUSE_BTN0_DBL cycle fullscreen        # toggle fullscreen on/off
    #MOUSE_BTN2 cycle pause                 # toggle pause on/off
    #MOUSE_BTN3 seek 10
    #MOUSE_BTN4 seek -10
    #MOUSE_BTN5 add volume -2
    #MOUSE_BTN6 add volume 2
    
    # Mouse wheels, touchpad or other input devices that have axes
    # if the input devices supports precise scrolling it will also scale the
    # numeric value accordingly
    #AXIS_UP    seek 10
    #AXIS_DOWN  seek -10
    #AXIS_LEFT  seek 5
    #AXIS_RIGHT seek -5
    
    ## Seek units are in seconds, but note that these are limited by keyframes
    #RIGHT seek  5
    #LEFT  seek -5
    #UP    seek  60
    #DOWN  seek -60
    RIGHT  seek  5
    LEFT   seek -5
    UP     seek -60
    DOWN   seek  60
    Ctrl+f seek  3
    Ctrl+b seek -3
    Ctrl+p seek -60
    Ctrl+n seek  60
    # Do smaller, always exact (non-keyframe-limited), seeks with shift.
    # Don't show them on the OSD (no-osd).
    #Shift+RIGHT no-osd seek  1 exact
    #Shift+LEFT  no-osd seek -1 exact
    #Shift+UP    no-osd seek  5 exact
    #Shift+DOWN  no-osd seek -5 exact
    # Skip to previous/next subtitle (subject to some restrictions; see manpage)
    #Ctrl+LEFT   no-osd sub-seek -1
    #Ctrl+RIGHT  no-osd sub-seek  1
    #PGUP add chapter 1                     # skip to next chapter
    #PGDWN add chapter -1                   # skip to previous chapter
    PGUP add chapter -1                     # skip to next chapter
    PGDWN add chapter 1                     # skip to previous chapter
    #Shift+PGUP seek 600
    #Shift+PGDWN seek -600
    #[ multiply speed 0.9091                # scale playback speed
    #] multiply speed 1.1
    #{ multiply speed 0.5
    #} multiply speed 2.0
    #BS set speed 1.0                       # reset speed to normal
    #q quit
    #Q quit-watch-later
    #q {encode} quit 4
    #ESC set fullscreen no
    #ESC {encode} quit 4
    #p cycle pause                          # toggle pause/playback mode
    #. frame-step                           # advance one frame and pause
    #, frame-back-step                      # go back by one frame and pause
    #SPACE cycle pause
    ? playlist-shuffle                      # skip to random file
    #> playlist-next                        # skip to next file
    ENTER playlist-next                     # skip to next file
    #< playlist-prev                        # skip to previous file
    Shift+ENTER playlist-prev               # skip to next file
    #O no-osd cycle-values osd-level 3 1    # cycle through OSD mode
    #o show-progress
    #P show-progress
    #I show-text "${filename}"              # display filename in osd
    #z add sub-delay -0.1                   # subtract 100 ms delay from subs
    #x add sub-delay +0.1                   # add
    #ctrl++ add audio-delay 0.100           # this changes audio/video sync
    #ctrl+- add audio-delay -0.100
    #9 add volume -2
    #/ add volume -2
    #0 add volume 2
    #* add volume 2
    #m cycle mute
    #1 add contrast -1
    #2 add contrast 1
    #3 add brightness -1
    #4 add brightness 1
    #5 add gamma -1
    #6 add gamma 1
    #7 add saturation -1
    #8 add saturation 1
    #Alt+0 set window-scale 0.5
    #Alt+1 set window-scale 1.0
    #Alt+2 set window-scale 2.0
    # toggle deinterlacer (automatically inserts or removes required filter)
    #d cycle deinterlace
    #r add sub-pos -1                       # move subtitles up
    r cycle_values video-rotate 90 180 270 0
    #t add sub-pos +1                       #                down
    #v cycle sub-visibility
    # stretch SSA/ASS subtitles with anamorphic videos to match historical
    #V cycle sub-ass-vsfilter-aspect-compat
    # switch between applying no style overrides to SSA/ASS subtitles, and
    # overriding them almost completely with the normal subtitle style
    #u cycle-values sub-ass-style-override "force" "no"
    #j cycle sub                            # cycle through subtitles
    #J cycle sub down                       # ...backwards
    #SHARP cycle audio                      # switch audio streams
    #_ cycle video
    #T cycle ontop                          # toggle video window ontop of other windows
    #f cycle fullscreen                     # toggle fullscreen
    #s async screenshot                     # take a screenshot
    #S async screenshot video               # ...without subtitles
    #Ctrl+s async screenshot window         # ...with subtitles and OSD, and scaled
    #Alt+s screenshot each-frame            # automatically screenshot every frame
    #w add panscan -0.1                     # zoom out with -panscan 0 -fs
    #e add panscan +0.1                     #      in
    # cycle video aspect ratios; "-1" is the container aspect
    #A cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"
    #POWER quit
    #PLAY cycle pause
    #PAUSE cycle pause
    #PLAYPAUSE cycle pause
    #STOP quit
    #FORWARD seek 60
    #REWIND seek -60
    #NEXT playlist-next
    #PREV playlist-prev
    #VOLUME_UP add volume 2
    #VOLUME_DOWN add volume -2
    #MUTE cycle mute
    #CLOSE_WIN quit
    #CLOSE_WIN {encode} quit 4
    #E cycle edition                        # next edition
    #l ab-loop                              # Set/clear A-B loop points
    #L cycle-values loop-file "inf" "no"    # toggle infinite looping
    #ctrl+c quit 4
    
    # Apple Remote section
    #AR_PLAY cycle pause
    #AR_PLAY_HOLD quit
    #AR_CENTER cycle pause
    #AR_CENTER_HOLD quit
    #AR_NEXT seek 10
    #AR_NEXT_HOLD seek 120
    #AR_PREV seek -10
    #AR_PREV_HOLD seek -120
    #AR_MENU show-progress
    #AR_MENU_HOLD cycle mute
    #AR_VUP add volume 2
    #AR_VUP_HOLD add chapter 1
    #AR_VDOWN add volume -2
    #AR_VDOWN_HOLD add chapter -1
    
    # For tv://
    #h cycle tv-channel -1                  # previous channel
    #k cycle tv-channel +1                  # next channel
    
    # For dvb://
    #H cycle dvb-channel-name -1            # previous channel
    #K cycle dvb-channel-name +1            # next channel
    
    #
    # Legacy bindings (may or may not be removed in the future)
    #
    #! add chapter -1                       # skip to previous chapter
    #@ add chapter 1                        #         next
    
    #
    # Not assigned by default
    # (not an exhaustive list of unbound commands)
    #
    
    # ? add sub-scale +0.1                  # increase subtitle font size
    # ? add sub-scale -0.1                  # decrease subtitle font size
    # ? sub-step -1                         # immediately display next subtitle
    # ? sub-step +1                         #                     previous
    # ? cycle angle                         # switch DVD/Bluray angle
    # ? add balance -0.1                    # adjust audio balance in favor of left
    Z add balance -0.1                    # adjust audio balance in favor of left
    # ? add balance 0.1                     #                                  right
    X add balance 0.1                     #                                  right
    # ? cycle sub-forced-only               # toggle DVD forced subs
    # ? cycle program                       # cycle transport stream programs
    # ? stop                                # stop playback (quit or enter idle mode)
    
  • scripts

    local utils = require "mp.utils"
    
    del_list = {}
    
    function contains_item(l, i)
       for k, v in pairs(l) do
          if v == i then
             mp.osd_message("undeleting current file")
             l[k] = nil
             return true
          end
       end
       mp.osd_message("deleting current file")
       return false
    end
    
    function mark_delete()
       local work_dir = mp.get_property_native("working-directory")
       local file_path = mp.get_property_native("path")
       local s = file_path:find(work_dir, 0, true)
       local final_path
       if s and s == 0 then
          final_path = file_path
       else
          final_path = utils.join_path(work_dir, file_path)
       end
       if not contains_item(del_list, final_path) then
          table.insert(del_list, final_path)
       end
    end
    
    function delete(e)
       if e.reason == "quit" then
          for i, v in pairs(del_list) do
             print("deleting: "..v)
             os.remove(v)
          end
       end
    end
    
    mp.add_key_binding("ctrl+DEL", "delete_file", mark_delete)
    mp.register_event("end-file", delete)
    

unison

  • music directory syncing

    # Roots of the synchronization
    root = <<home-dir()>>/mnt/ruizu/Music
    root = <<home-dir()>>/Audio
    
    # Paths to synchronize
    path = .
    
    # Ruizu's file system is FAT
    fat = true
    
    # Overwrite by newer file on confliction
    prefer = newer
    
    # Show nothing
    silent = true
    confirmbigdel = false
    
    # Do fast checking
    fastcheck = true
    
    # Log settings
    log = true
    logfile = <<home-dir()>>/.local/state/log/log/unison/music.log
    
    
  • doc directory backup

    # Roots of the synchronization
    root = <<home-dir()>>/mnt/ns01
    root = <<home-dir()>>
    
    # Paths to synchronize
    path = doc
    
    # Overwrite by newer file on confliction
    prefer = newer
    
    # Use this command for displaying diffs
    diff = diff -y -W 79 --suppress-common-lines
    
    # Write down synchronization activity on log file rather than show in stdout
    auto = true
    confirmbigdel = false
    silent = true
    log = true
    logfile = <<home-dir()>>/.local/state/log/unison/doc.log
    
    # Use inode number to verify identity rather than ckeck whole byte sequence
    fastcheck = true
    
    # Abort if device is not mounted
    mountpoint = doc
    

Aspell

aspell - interactive spell checker

lang en_US

textlint

install-textlint
textlint @textlint/ast-node-types textlint-rule-preset-ja-spacing textlint-rule-preset-ja-technical-writing textlint-rule-spellcheck-tech-word textlint-rule-ginger textlint-rule-write-good textlint-rule-prh textlint-filter-rule-node-types textlint-plugin-org traverse
  • config for English

    {
      "rules": {
        "ginger": true,
        "write-good": true,
      }
    }
    
  • config for Japanese

    {
      "rules": {
        "preset-ja-technical-writing": {
          "no-exclamation-question-mark": false,
          "sentence-length": {
            "max": 200
          }
        },
        "spellcheck-tech-word": true,
        "textlint-rule-ginger": {
          "skipPatterns": [
            "`(.*)`"
          ]
        },
        "textlint-rule-write-good": true,
        "preset-ja-spacing": true
      }
    }
    

xkeysnail   ARCHIVE

SKK

SKK is Simple Kana to Kanji conversion program, an input method of Japanese.

"skk-jisyo"
"skktools"
echo -n '<<guix/ex-prof()>>/base/base/share/skk/'
  • TODO: following programs have no guix package. It is better to write my own one for my reproducible system.

derive unique entries in user-jisyo   ARCHIVE

Festival

Festival is a framework for building and manipulating speech synthesis engine.

"festival"
  • configuration

    (Parameter.set 'Audio_Required_Format 'aiff)
    (Parameter.set 'Audio_Method 'Audio_Command)
    (Parameter.set 'Audio_Command "paplay $FILE --client-name=Festival --stream-name=Speech")
    
    ;; add SLT voice and use it as a default one
    (voice-location 'cmu_us_slt_cg
                    (path-as-directory
                     (let ((path "~/.local/share/festival/lib/voices/us/cmu_us_slt_cg"))
                       (if (string-equal (substring path 0 1) "~")
                           (string-append (getenv "HOME") (substring path 1 (length path)))
                           path)))
                    "English American Female")
    (set! voice_default voice_cmu_us_slt_cg)
    
  • a script to download voices

    download-festival-voices
    
    work_dir=~/.local/share/festival
    voices=(
      cmu_us_aew_cg
      cmu_us_rxr_cg
      cmu_us_slt_cg
    )
    
    if [ ! -d "${work_dir}" ]; then
      mkdir -p "${work_dir}"
    fi
    
    cd "${work_dir}"
    for voice in "${voices[@]}"; do
      voice_pack_file=festvox_${voice}.tar.gz
      if [ ! -e ${voice_pack_file} ]; then
        wget -c http://festvox.org/packed/festival/2.5/voices/${voice_pack_file}
        tar zxf "${voice_pack_file}" -C ../../share
      fi
    done
    
    echo "${work_dir}"
    
  • festival.el

    (use-package festival
      :defer t
      :config
      (say-minor-mode 1)
      (setq festival-tmp-file (make-temp-file "festival-")))
    

Open JTalk

(file-name-concat "<<share-dir()>>" "hts-voice")
<<hts-voice-dir()>>
  • install on Ubuntu

    open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
    
  • download hts voices

    download-hts-voices
    
    data_dir=<<hts-voice-dir()>>
    hts_workdir=/tmp
    hts_fname=MMDAgent_Example-1.8.zip
    
    if [ ! -d "${data_dir}" ]; then
      mkdir -p "${data_dir}"
    fi
    
    cd "${hts_workdir}" \
      && curl -sLO http://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/${hts_fname%.zip}/${hts_fname} \
      && unzip -o -d ${data_dir} ${hts_fname} \
      && ln -sfv ${data_dir}/${hts_fname%.zip}/Voice ${data_dir}/
    

Jack   ARCHIVE

XRandR

XRandR is a command line utility for changing display options without restarting X server.

xrandr - ArchWiki

  • show avaulable modes for all displays

    xrandr
    
  • change mode (resolution) to 1920x1080 for a display identified as "HDMI-2"

    xrandr --output HDMI-2 --mode 1920x1080
    
  • change mode back to preferred (maximum)

    xrandr --output HDMI-2 --auto
    
"xrandr"

C/Migemo

C/Migemo allows us to search Japanese with incremental ASCII charactor input.

"cmigemo"
"migemo-dict-azik"
("https://github.com/koron/cmigemo/releases.atom" soft_update)
echo -n '<<guix/ex-prof()>>/emacs/emacs/share/migemo/'

C/Migemo in apt   ARCHIVE

AZIK-compliant C/Migemo   ARCHIVE

LilyPond

LilyPond elisp package is included in source tarball available at here, you are required to locate that package where emacs can search.

"lilypond"
(use-package lilypond-mode
  :delight (LilyPond-mode " LP")
  :mode ("\\.ly$" . LilyPond-mode)
  :custom
  (LilyPond-pdf-command "firefox")
  (LilyPond-midi-command "fluidsynth -a alsa -m alsa_seq -l"))

Graphviz

Documentation is available at here and gallery is also useful.

"graphviz"
"xdot"
"emacs-graphviz-dot-mode"
(use-package graphviz-dot-mode
  :after eglot
  :custom
  (graphviz-dot-indent-width 2)
  :config
  (add-to-list 'eglot-server-programs
               '(graphviz-dot-mode "dot-language-server" "--stdio")))
dot-language-server

Rsync

"rsync"

sync server data   ARCHIVE

backup script   ARCHIVE

libvirt

"libvirt"
"virt-manager"
qemu-kvm libvirt-clients libvirt-daemon libvirt-daemon-system virtinst virt-manager virt-top bridge-utils
systemctl enable libvirtd
systemctl start libvirtd
uri_default = "qemu:///system"
  • how to set up local storage pool (named 'local')

    $ virsh pool-define ~/.config/libvirt/pool.xml
    $ virsh pool-autostart local
    $ virsh pool-start local
    
    <pool type='dir'>
      <name>local</name>
      <source>
      </source>
      <target>
        <path>/home/<<login-name()>>/<<laptop-node()>>/virt</path>
        <permissions>
          <mode>0711</mode>
          <owner>0</owner>
          <group>0</group>
        </permissions>
      </target>
    </pool>
    

Calibre

Calibre for linux is a e-book reader. I use it in the mood of reading epub in GUI app.

"calibre"

yt-dlp

"yt-dlp"
--output %(title)s.%(ext)s
--trim-filenames 70

rTorrent   ARCHIVE

Ledger

Ledger is double-entry accounting cli tool. hledger is feature rich reimplementation of ledger in Haskell. User data in ledger is interoperable for hledger and vice versa.

"ledger"
;; "hledger"

ripgrep-all

ripgrep-all or rga command enables to search media files such as pdf documents or epub files in grep-like way.

rga_url=https://github.com/phiresky/ripgrep-all/releases/download/v0.9.6
rga_fname=ripgrep_all-v0.9.6-x86_64-unknown-linux-musl.tar.gz
rga_workdir=/tmp
rga_lbinpath=<<home-dir()>>/.local/bin

curl -sL "${rga_url}/${rga_fname}" | tar zxf - -C ${rga_workdir} \
  && cp -fv ${rga_workdir}/${rga_fname%.tar.gz}/{rga,rga-preproc} ${rga_lbinpath}

Recoll

"recoll"
recollcmd
topdirs = <<doc-dir()>> ~/data

[<<doc-dir()>>]
followLinks = 1

skippedNames = .git

skippedPaths = <<share-dir()>>/emacs/open-file <<share-dir()>>/emacs/org-subtree-view

indexstemminglanguages = english

systemd setting   ARCHIVE

Enchant

ENCHANT offers efficient spell checking mechanism by using checker in order.

"enchant"
"hunspell"
"hunspell-dict-en"
"hunspell-dict-en-us"
"nuspell"
"aspell"
"aspell-dict-en"
export ENCHANT_CONFIG_DIR=<<home-dir()>>/.config/enchant
*:hunspell,nuspell,aspell
en:hunspell,nuspell,aspell
en_US:hunspell,nuspell,aspell
TempEL

Modern alternative command line tools

They are mostly written in Rust. moderntools offers updated info for this category.

"bat"
"du-dust"
"eza"
"fd"
"zoxide"
"just"
"tmux"
"moreutils"

Haunt

"haunt"

Wine

"wine64"
"winetricks"
export WINEARCH=win64
export WINEPREFIX=~/.wine

Font Viewer

"gnome-font-viewer"

Development Infrastructures

C/C++

"tree-sitter-c"
"tree-sitter-cpp"

Python

"python"
"python-pip"
"python-sphinx"
"python-lxml"
"python-lsp-server"
"python-jedi"
"python-send2trash"
"python-matplotlib"
"python-numpy"
"python-pandas"

Ruby

"ruby"
"ruby-pry"
"ruby-rubocop"
"ruby-thor"
"tree-sitter-ruby"
export RUBYLIB=${HOME}/.local/lib

# set path for ruby gems in local
if which ruby >/dev/null && which gem >/dev/null; then
  PATH="$(ruby -r rubygems -e 'puts Gem.user_dir')/bin:$PATH"
fi

Lua

"tree-sitter-lua"

Common Lisp

"sbcl"
"sbcl-slynk"

Node.js

echo -n '~/.npm-global'
"node"
NODE_PATH="<<npm-prefix()>>/lib/node_modules:${NODE_PATH}"
PATH="<<npm-prefix()>>/bin:${PATH}"
MANPATH="<<npm-prefix()>>/share/man:${MANPATH}"
prefix=<<npm-prefix()>>
  • make a directory for global npm packages

    mkdir -pv <<npm-prefix()>>
    
  • install npm packages

    npm install -g <<npm-pkg>>
    

asdf-vm   ARCHIVE

Language Servers

Bash Language Server

Bash Language Server

bash-language-server

Solargraph

Solargraph is Ruby Language Server.

"ruby-solargraph"

Stuffs for Dedicated Tasks

Batch Processing

Morning Batch

<<morning-batch>>

Midnight Batch

<<midnight-batch>>

Transcode Video Files

Transcoding my local video files with open codec, like VP9 and AV1, is effective for disc space usage.

#!/usr/bin/env bash
set -euo pipefail

CRF=28
KBITRATE_AUDIO=128

function usage() {
  cat <<_EOT_
Usage:
  $(basename $0) [OPTIONS] <video_in> [<video_out>]

Description:
  $(basename $0) encodes <video_in> to <video_out> in specified codec.

Options:
  -c <CRF>      CRF value instead of ${CRF} (DEFAULT)
  -B <AUDIO_BR> Audio Bitrate in kbps

_EOT_
}

if [ "${OPTIND}" = 1 ]; then
  while getopts c:B:h OPT
  do
    case ${OPT} in
    c)
      CRF=${OPTARG}
      ;;
    B)
      CRF=${OPTARG}
      ;;
    h)
      usage
      ;;
    \?)
      echo "Try to enter the h option." 1>&2
      ;;
    esac
  done
else
  echo "No installed getopts-command." 1>&2
  exit 1
fi

shift $((OPTIND - 1))

if [ $# -lt 1 ] || [ $# -gt 2 ]; then
  usage
  exit -1
fi

INPUT_VIDEO=$1
if [ ! -e "${INPUT_VIDEO}" ]; then
  echo "${INPUT_VIDEO} does not exist." 1>&2
  exit -1
fi

if [[ $# -eq 2 ]]; then
  output_cand=$2
else
  output_cand=$(basename "${INPUT_VIDEO}").webm
fi
if [ -e "${output_cand}" ]; then
  OUTPUT_VIDEO=${output_cand%.*}_$(date +%Y%m%d_%H%M).webm
else
  OUTPUT_VIDEO="${output_cand}"
fi

WORK_DIR="${INPUT_VIDEO%.*}"

vp9 transcoding

  • transcode video to vp9

    $ vp9transc -c 32 video_orig.mkv video_vp9.webm
    
<<transc-shared>>

vp9_opts_pass1="-c:v libvpx-vp9 -cpu-used 4 -threads 3 -row-mt 1 -speed 1 -b:v 0 -crf ${CRF} -pass 1 -an -f null"
vp9_opts_pass2="-c:v libvpx-vp9 -cpu-used 4 -threads 3 -row-mt 1 -speed 1 -b:v 0 -crf ${CRF} -pass 2 -map_metadata 0"
opus_opts="-c:a libopus -b:a ${KBITRATE_AUDIO}k -ac 2"

mkdir -p "${WORK_DIR}" && cd "${WORK_DIR}" \
  && ffmpeg -i "../${INPUT_VIDEO}" ${vp9_opts_pass1} /dev/null \
  && ffmpeg -i "../${INPUT_VIDEO}" ${vp9_opts_pass2} ${opus_opts} "../${OUTPUT_VIDEO}"

av1 transcoding

  • transcode video to av1

    $ av1transc video_orig.mkv video_av1.webm
    
<<transc-shared>>

av1_opts_common="-c:v libaom-av1 -cpu-used 2 -row-mt 1 -threads $(nproc) -b:v 0 -crf ${CRF}"
av1_opts_pass1="${av1_opts_common} -pass 1 -an -f null"
av1_opts_pass2="${av1_opts_common} -pass 2 -map_metadata 0"
opus_opts="-c:a libopus -b:a ${KBITRATE_AUDIO}k -ac 2"

mkdir -p "${WORK_DIR}" && cd "${WORK_DIR}" \
  && ffmpeg -i "../${INPUT_VIDEO}" ${av1_opts_pass1} /dev/null \
  && ffmpeg -i "../${INPUT_VIDEO}" ${av1_opts_pass2} ${opus_opts} "../${OUTPUT_VIDEO}"

Transcode Audio Files   ARCHIVE

DVD Packing

"dvdbackup"
"libdvdnav"
"libdvdcss"
"libdvdread"
"handbrake"
set -euo pipefail

if [ "${OPTIND}" = 1 ]; then
  while getopts t: OPT
  do
    case ${OPT} in
    t)
      TITLE=${OPTARG}
      ;;
    esac
  done
else
  echo "No installed getopts-command." 1>&2
  exit 1
fi
shift $((OPTIND - 1))

if [[ $# -ge 1 ]]; then
  DVD_DIR=$1
  if [[ $# -eq 2 ]]; then
    DVD_PACK_VIDEO=$2
  else
    DVD_PACK_VIDEO=$(basename "${DVD_DIR}").mkv
  fi
fi

if [ -v TITLE ]; then
  title_opt="--title ${TITLE}"
else
  title_opt='--main-feature'
fi

hb_opts="--x264-preset veryslow --quality 18 --aencoder copy ${title_opt} --markers --all-audio --all-subtitles"

HandBrakeCLI --input "${DVD_DIR}" --output "${DVD_PACK_VIDEO}" ${hb_opts}

CD Ripping

"abcde"
"eyed3"
"lame"
"opus-tools"
"cdrtools"
"easytag"

abcde

# .flac is only desired output format
OUTPUTTYPE="flac"
# or .flac and .opus (bitrate is 256 kbps) will be generated at the same time
# OUTPUTTYPE="flac,opus"
# OPUSENCOPTS='--bitrate 256'

# .flac files will be utterly compressed with verification
FLACOPTS="-V8"

MAXPROCS=4

OUTPUTFORMAT='${ARTISTFILE}/${ALBUMFILE}/${TRACKNUM}.${TRACKFILE}'

Tutorial Video Creation

"slop"
"gimp"
"obs"
"kdenlive"
"audacity"

Screenkey

"python-screenkey"
{
  "timeout": 0.8,
  "key_mode": "composed",
  "bak_mode": "normal",
  "mods_mode": "emacs",
  "opacity": 0.3,
  "screen": 0
}

Sync Git Repos Automatically

0 */2 * * * /usr/bin/bash -ci "ghq-sync >> $LOG_DIR/ghq-sync.log 2>&1"
function git-one-go {
  local _branch=$(git symbolic-ref -q HEAD | sed 's/refs\/heads\///')
  local _remote=$(git config --get branch.$_branch.pushRemote)
  local _commit_msg="auto-commit by $(whoami) on $(uname -n) at $(date +"%H:%M %b %d")"

  echo -e "\nStarting git syncing at $(pwd)"

  if [[ "true" = "$(git config --get --bool branch.$_branch.sync)" ]] ; then
    git add -u
    if [ -n "$(git status --porcelain)" ]; then
      git commit --allow-empty -m "${_commit_msg}"
    fi

    if [ -n "$(git log origin/main..HEAD)" ]; then
      git push $_remote $_branch
      if [ $? != 0 ]; then
        echo "ghq-sync: git push returned non-zero. Likely a connection failure." >&2
        exit 1
      fi
    else
      echo "No unpushed commits found. Nothing to push."
    fi
  fi
}

ghq_repos=$(ghq list --full-path github.com/p-snow)
for repo_path in ${ghq_repos[@]}; do
  (cd ${repo_path} && git-one-go)
done

git-sync   ARCHIVE

Download media at anywhere in desktop

I prefer using mpv, the video file player, instead of watching YouTube videos in a browser. I can download a video file from the internet by executing this shell script using a shortcut key. The script calls the Emacs dispatch-yt-dlp-dwim function using a client/server scheme.

if [ $# -ge 1 ]; then
  arg=" nil nil t"
else
  arg=""
fi
emacsclient -s server-<<hash-string(seed="server", len=6)>> --eval "(progn (yt-dlp-dispatch-dwim \"$(xclip -out)\"${arg})(dispatch-execute-macro))"

Babel Utility Functions

which path

Return the absolute path for the 'filename' without expansion, allowing for user-agnostic configuration.

which ${filename} | sed "s:$HOME:~:"

Generate hash string

Return hash string from 'seed'. You can cull the output to desired length by 'len'.

import hashlib

sha256_hash = hashlib.sha256(seed.encode("utf-8"))
return sha256_hash.hexdigest()[:len]

random number

return random number between 0 and max

(calc-eval (format "random(%d)" (1+ max)))

Hours in seconds

(* h 60 60)

Specialized Scripts

Declutters   ARCHIVE

Database back-up scripts

  • PostgreSQL

    #!/usr/bin/env bash
    
    # Usage: $ postgres_backup <db_name> <backup_dir>
    
    POSTGRES_DBNAME=$1
    BACKUP_DIR=$2
    DATE="$(date +%Y%m%d_%H%M)"
    
    pg_dump --format=custom ${POSTGRES_DBNAME} > ${BACKUP_DIR}/${DATE}_${POSTGRES_DBNAME}.custom || exit 1
    
    exit 0
    
  • MySQL

    BACKUP_DIR=~/data/share/backups/mysql
    RAW_SQL="$(date +%Y%m%d_%H%M)_backup.sql"
    TAR_SQL="${RAW_SQL}.txz"
    
    (
      cd "${BACKUP_DIR}" || exit 1
      mysqldump --opt --all-databases --events --default-character-set=binary -u root > "${RAW_SQL}"
      tar cfJ "${TAR_SQL}" "${RAW_SQL}"
      rm -f "${RAW_SQL}"
    ) || exit 1
    
    exit 0
    

filetype

This program determine type of the file.

#!/usr/bin/env ruby

require 'pathname'
require 'uri'
require 'shellwords'

class FileType
  ZIP_PREFIXES = ['Zip archive data']

  def self.type(filename, file_cmd_opt = "")
    if  File.exist?(filename)
      local_opt = ""
      if File.symlink?(filename)
        local_opt += "--dereference"
      end
      `file --brief #{file_cmd_opt} #{local_opt} #{Shellwords.shellescape(filename)}`.strip
    elsif filename =~ /https?:\/\/www\.youtube\.com.*/
      `yt-dlp -f worst -q #{Shellwords.shellescape(filename)} -o - 2>/dev/null | file --brief #{file_cmd_opt} -`.strip
    elsif filename =~ URI::DEFAULT_PARSER.make_regexp
      `curl --location --range 0-9999 #{Shellwords.shellescape(filename)} 2>/dev/null | file --brief #{file_cmd_opt} -`.strip
    else
      ''
    end
  end

  def self.mime_type(filename)
    type(filename, "--mime-type")
  end

  def self.block_device?(file)
    type(file).start_with?('block special') &&
      mime_type(file) == 'inode/blockdevice'
  end

  def self.iso9660?(file)
    mime_type(file) == 'application/x-iso9660-image'
  end

  def self.video_dvd_image?(file)
    iso9660?(file) && type(file).start_with?('UDF filesystem data')
  end

  def self.html?(file)
    type(file).start_with?("HTML document")
  end

  def self.pdf?(file)
    type(file).start_with?("PDF document")
  end

  def self.epub?(file)
    type(file) == "EPUB document"
  end

  def self.mobi?(file)
    type(file).start_with?("Mobipocket E-book")
  end

  def self.pgp?(file)
    type(file).start_with?("PGP")
  end

  def self.tarpgp?(file)
    pgp?(file) && file.end_with?('.tar.gpg')
  end

  def self.zip?(file)
    type(file).start_with?(*ZIP_PREFIXES)
  end

  def self.video?(file)
    mime_type(file).start_with?("video/")
  end

  def self.audio?(file)
    mime_type(file).start_with?('audio/')
  end

  def self.image?(file)
    mime_type(file).start_with?("image/")
  end

  def self.playable?(file)
    if File.directory?(file)
      Dir.foreach(file) do |fname|
        next if [".", ".."].include?(fname)
        return false unless playable?("#{file}/#{fname}")
      end
      true
    elsif file =~ /https?:\/\/www\.youtube\.com.*/
      true
    else
      type(file).start_with?("ISO Media", "Audio file") ||
        mime_type(file).start_with?("video", "audio", "image")
    end
  end

  def self.dvd_dir?(file)
    false unless mime_type(file) == 'inode/directory'
    File.exist?(Pathname.new(file).join("VIDEO_TS")) ||
      File.exist?(Pathname.new(file).join("video_ts"))
  end
end

mediautil

Utility ruby program for manipulating image, audio and video file.

"dvd+rw-tools"
require 'thor'
require 'open3'
require 'fileutils'
require 'tempfile'
require 'tmpdir'

require 'filetype'

class MediaUtil < Thor
  OUTFILE_SUFFIX = "_out"
  DEFAULT_VIDEO_EXT = ".mkv"

  class Names < Struct.new(:in_f, :out_f)
    def in_file()
      in_f
    end
    def out_file(extension = nil)
      if out_f
        out_f
      else
        bn = File.basename(in_f, ".*").gsub(/ /, '_')
        ext = ((extension) ? extension : File.extname(in_file))
        default_out = bn + ext
        if File.file?(default_out)
          10.times do |i|
            try_name = bn + "_" + (i+1).to_s + ext
            unless File.file?(try_name)
              return try_name
            end
          end
        end
        default_out
      end
    end
    def title()
      File.basename(out_file, ".*")
    end
  end
  class Commander < Struct.new(:log, :dry)
    def execute(command)
      if dry
        puts command
      else
        log_str, s_code = Open3.capture2e(command)
        if log
          user_cmd = caller.first.scan(/`(.*)'/).flatten.first.to_s
          log_fname = Time.now.strftime("%Y%m%d_%H%M%S_") + user_cmd + ".log"
          File.open(log_fname, "w") do |f|
            f.puts(log_str)
          end
        else
          puts log_str
        end
        exit false unless s_code.success?
        # exit false
      end
    end
  end

  class_option :log, aliases: "L", :type => :boolean,
               :desc => "log stdout and stderr instead they reveal in terminal"
  class_option :dryrun, aliases: "D", :type => :boolean

  desc "info <file>", "show information about the video file or DVD directory"
  option :short, aliases: "s", :type => :boolean
  def info(target)
    commander = Commander.new(options[:log], options[:dryrun])

    if FileType.video?(target) || FileType.audio?(target)
      commander.execute("ffprobe \"#{target}\" -hide_banner -show_entries format")
    elsif FileType.image?(target)
      if options[:short]
        commander.execute("identify #{target}")
      else
        commander.execute("identify -verbose #{target}")
      end
    elsif FileType.block_device?(target) ||
          FileType.video_dvd_image?(target) ||
          FileType.dvd_dir?(target)
      commander.execute("HandBrakeCLI -i \"#{target}\" --title 0")
    else
      STDERR.puts "Unknown Media Format"
      exit false
    end
  end

  default_command :encode
  desc "encode", "encode video file"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :vencoder, aliases: "e", :type => :string,
         :banner => "libx265|libx264",
         :desc => "video encoder"
  option :vquality, aliases: "q", :type => :string,
         :banner => "24.0",
         :desc => "video encoding quality"
  option :vbitrate, aliases: "b", :type => :string,
         :banner => "1000",
         :desc => "video encoding bitrate in kbps"
  option :aencoder, aliases: "E", :type => :string,
         :banner => "fdk_aac",
         :desc => "audio encoder"
  option :aquality, aliases: "Q", :type => :string,
         :banner => "100.0",
         :desc => "audio encoding quality"
  option :abitrate, aliases: "B", :type => :string,
         :banner => "128",
         :desc => "audio encoding bitrate in kbps"
  option :display, aliases: "d", :type => :string,
         :banner => "1920:1080",
         :desc => 'display (width:height) in which encoded video fit'
  option :fps, aliases: "f", :type => :string,
         :banner => "30",
         :desc => 'max frame rate'
  option :ffmpeg, :type => :string
  def encode()
    equip = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    vconf = aconf = pconf = nil
    if options[:vencoder] || options[:vquality]
      vconf = VideoConfig.new(options[:vencoder], options[:vquality], options[:vbitrate])
    end
    if options[:aencoder] || options[:aquality]
      aconf = AudioConfig.new(options[:aencoder], options[:aquality], options[:abitrate])
    end
    if options[:display] || options[:fps]
      pconf = PictureConfig.new
      pconf.fit_size(options[:display]) if options[:display]
      pconf.decrease_framerate(options[:fps]) if options[:fps]
    end

    if FileType.video?(equip.in_file) then
      commander.execute(FFMPEG.command([FFMPEGOptSet.new(equip.in_file, [])],
                                       [FFMPEGOptSet.new(equip.out_file, [vconf, aconf, pconf, "-map_metadata 0", options[:ffmpeg]])]))
    elsif FileType.audio?(equip.in_file) then
      commander.execute(FFMPEG.command([FFMPEGOptSet.new(equip.in_file, [])],
                                       [FFMPEGOptSet.new(equip.out_file, [aconf])]))
    elsif FileType.image?(equip.in_file) then
      pconf = ImageConfig.new
      commander.execute(ImageMagick.command(equip.in_file,
                                            equip.out_file,
                                            pconf))
    else
      STDERR.puts "Unknown Media Format"
      exit false
    end
  end

  desc 'cdencode', 'rip and encode CD contents'
  option :device, aliases: 'd', :type => :string,
         :desc => 'Device file'
  option :format, aliases: 'f', :type => :string,
         :banner => 'flac,ogg,m4a', :default => 'flac:-8',
         :desc => 'formats with additinal argument like compression level'
  def cdencode()
    commander = Commander.new(options[:log], options[:dryrun])

    abcde_args = []
    abcde_args << "-d #{options[:device]}" if options[:device]
    abcde_args << '-a default'
    if options[:format] then
      abcde_args << "-o '#{options[:format]}'"
    end
    abcde_args << '-G' # getalbumart
    abcde_args << '-x' # Eject CD

    commander.execute("abcde #{abcde_args.join(' ')}")
  end

  desc 'dvdrip', 'rip(backup) DVD contents'
  option :device, aliases: 'd', :type => :string,
         :desc => 'DVD device file'
  option :out, aliases: 'o', :type => :string,
         :desc => 'output directory name'
  option :name, aliases: 'n', :type => :string,
         :desc => 'root directory name of backup data'
  def dvdrip()
    commander = Commander.new(options[:log], options[:dryrun])

    dvdbackup_opts = ['dvdbackup']
    dvdbackup_opts << '--input=%s' % options[:device] if options[:device]
    dvdbackup_opts << '--output=%s' % options[:out] if options[:out]
    dvdbackup_opts << '--name=%s' % options[:name] if options[:name]
    dvdbackup_opts << '--mirror'
    dvdbackup_opts << '--progress'
    dvdbackup_opts << '--verbose'

    commander.execute(dvdbackup_opts.join(' '))
  end

  desc 'dvdencode', 'encode DVD contents'
  option :in, aliases: 'i', :type => :string, :required => true,
         :desc => 'input source, DVD device file or backuped DVD file'
  option :out, aliases: 'o', :type => :string,
         :desc => 'output directory name'
  option :vencoder, aliases: "e", :type => :string,
         :banner => "x265|x264",
         :desc => "video encoder"
  option :vquality, aliases: "q", :type => :string,
         :banner => "24.0",
         :desc => "video encoding quality"
  option :vbitrate, aliases: "b", :type => :string,
         :banner => "1000",
         :desc => "video bitrate in kbps"
  option :aencoder, aliases: "E", :type => :string,
         :banner => "fdk_aac",
         :desc => "audio encoder"
  option :aquality, aliases: "Q", :type => :string,
         :banner => "100.0",
         :desc => "audio encoding quality"
  option :abitrate, aliases: "B", :type => :string,
         :banner => "128",
         :desc => "audio bitrate in kbps"
  option :title, aliases: 't', :type => :string,
         :banner => '1|2|3|...',
         :desc => 'title number'
  option :chapters, aliases: 'c', :type => :string,
         :banner => '1|1-3|1,3,5|...',
         :desc => 'chapter numbers'
  option :audio, aliases: 'a', :type => :string,
         :banner => '1,2,3',
         :desc => 'audio channel'
  option :subtitle, aliases: 's', :type => :string,
         :banner => '1,2',
         :desc => 'subtitle channel'
  option :handbrake, :type => :string,
         :banner => '--handbrake \"--mixdown 5point1,stereo\"',
         :desc => 'HandBrakeCLI options'
  def dvdencode()
    equip = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    if FileType.block_device?(equip.in_file) ||
       FileType.video_dvd_image?(equip.in_file) ||
       FileType.dvd_dir?(equip.in_file)
      configs = []
      configs << VideoConfig.new(options[:vencoder], options[:vquality], options[:vbitrate])
      configs << AudioConfig.new(options[:aencoder], options[:aquality], options[:abitrate])
      dconf = DVDConfig.new
      dconf.title = options[:title] if options[:title]
      dconf.chapters = options[:chapters] if options[:chapters]
      dconf.audio = options[:audio] if options[:audio]
      dconf.subtitle = options[:subtitle] if options[:subtitle]
      configs << dconf
      commander.execute(HandBrakeCLI.command(equip.in_file,
                                             equip.out_file(DEFAULT_VIDEO_EXT),
                                             configs,
                                             options[:handbrake]))
    end
  end

  desc 'mount <dir>', 'Mount CD/DVD media to dir.'
  option :device, aliases: "d", :type => :string, :required => true,
         :desc => 'device or iso file to mount',
         :banner => '--mount /dev/dvd|image.iso'
  option :type, aliases: 't', :type => :string,
         :desc => 'mount DVD-Video',
         :banner => '--type data|video'
  def mount(dir)
    commander = Commander.new(options[:log], options[:dryrun])

    cmd_opts = ['mount']
    if options[:type] == 'video' || FileType.video_dvd_image?(options[:device])
      cmd_opts << ['-t', 'iso9660', '-o', 'loop']
    end
    cmd_opts << options[:device]
    cmd_opts << dir

    Dir.mkdir(dir) if !Dir.exist?(dir) && !options[:dryrun]
    commander.execute("sudo #{cmd_opts.join(' ')}")
  end

  desc 'umount', 'unmount dir'
  option :remove, aliases: 'r', :type => :boolean,
         :desc => 'remove dir after unmounting'
  def umount(dir)
    commander = Commander.new(options[:log], options[:dryrun])

    cmd_opts = ['umount']
    cmd_opts << dir

    commander.execute("sudo #{cmd_opts.join(' ')}")

    if options[:remove]
      FileUtils.rmdir(dir)
    end
  end

  desc 'mkiso', 'make ISO image file.'
  option :type, aliases: 't', :type => :string, :required => true,
         :desc => 'DVD image type. Data or Video DVD.',
         :banner => '--type data|video'
  option :volume, aliases: 'V', :type => :string,
         :desc => 'Volume Label'
  option :out, aliases: 'o', :type => :string,
         :desc => 'ISO image file name.',
         :banner => '--out movie.iso'
  def mkiso(file)
    names = Names.new(file, options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    mkisofs_opts = ['mkisofs']
    mkisofs_opts << ["-V", "\"#{options[:volume]}\""] if options[:volume]
    if options[:type] == "data"
      mkisofs_opts << ["-r", "-l", "-J"]
    elsif options[:type]
      mkisofs_opts << "-dvd-video"
    else
      exit 1
    end
    mkisofs_opts << ["-o", "\"#{names.out_file('.iso')}\""]
    mkisofs_opts << "\"#{file}\""

    commander.execute(mkisofs_opts.join(" "))
  end

  desc 'dvdburn <file>', 'Burn file to DVD.'
  option :iso, aliases: 'I', :type => :boolean,
         :desc => 'Set this option if <file> is ISO 9660 compliant.'
  option :volume, aliases: 'V', :type => :string, :default => 'DVD_VIDEO',
         :desc => 'Volume Label'
  option :video, aliases: 'v', :type => :boolean,
         :desc => 'Create DVD video image file to burn.'
  option :speed, aliases: 's', :type => :string, :default => '4',
         :desc => 'Burning Speed',
         :banner => 'n'
  option :device, aliases: 'd', :type => :string,
         :desc => 'Device file', :default => '/dev/sr0'
  def dvdburn(file)
    commander = Commander.new(options[:log], options[:dryrun])

    cmd_opts = ['growisofs']
    cmd_opts << '-dvd-compat'
    cmd_opts << '-speed=%s' % options[:speed]
    if FileType.iso9660?(file) || options[:iso]
      cmd_opts << '-Z %s=%s' % [options[:device], file]
    else
      cmd_opts << '-Z %s' % options[:device]
      mkisofs_opts = []
      mkisofs_opts << ['-V', "\"#{options[:volume]}\""] if options[:volume]
      if options[:video]
        mkisofs_opts << '-dvd-video'
      else # Data-DVD
        mkisofs_opts << ['-r', '-l', '-R']
      end
      mkisofs_opts << file
      cmd_opts << mkisofs_opts
    end

    commander.execute(cmd_opts.join(' '))
  end

  desc "cdburn", "burn files to CD device"
  option :in, aliases: "i", :type => :string, :required => true,
         :desc => "Root Directory for image file"
  option :volume, aliases: "v", :type => :string, :required => true,
         :desc => "Volume ID"
  option :device, aliases: "d", :type => :string,
         :desc => "Device file", :default => "/dev/sr0"
  def cdburn()
    commander = Commander.new(options[:log], options[:dryrun])

    commander.execute("mkisofs -V \"#{options[:volume]}\" -J -r #{options[:in]} | cdrecord -v dev=#{options[:device]} -waiti -")
  end

  desc "screenshot", "take a screenshot of a video at a time"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :time, aliases: "t", :type => :string,
         :desc => "ex. 00:10:22.300"
  def screenshot()
    equip = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])
    if FileType.video?(equip.in_file) then
      args = ['ffmpeg']
      args << ('-ss %s' % options[:time]) if options[:time]
      args << '-i %s' % equip.in_file
      args << '-vframes 1'
      args << '-q:v 2'
      args << equip.out_file('.jpg')
      commander.execute(args.join(' '))
    end
  end

  desc "clip", "clip a video with specific time span"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :time_begin, aliases: "b", :type => :string,
         :desc => "ex. 00:10:22.300"
  option :time_end, aliases: "e", :type => :string
  def clip()
    equip = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    if FileType.video?(equip.in_file) then
      if options[:time_begin] or options[:time_end] then
        in_opts = []
        in_opts << "-ss #{options[:time_begin]}" if options[:time_begin]
        in_opts << "-to #{options[:time_end]}" if options[:time_end]
        out_opts = []
        out_opts << "-vcodec copy"
        out_opts << "-acodec copy"
        out_opts << "-map_metadata 0"
        commander.execute(FFMPEG.command([FFMPEGOptSet.new(equip.in_file, in_opts)],
                                         [FFMPEGOptSet.new(equip.out_file, out_opts)]))
      end
    else
      STDERR.puts "This Media Format is not supported on this function"
      exit false
    end
  end

  desc "crop", "extract a specific rectangle of a image/video"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :width, aliases: "w", :type => :numeric, :required => true
  option :height, aliases: "h", :type => :numeric, :required => true
  option :xpos, aliases: "x", :type => :numeric,
         :desc => "x coordinate of an original image where cropped image of top-left corner overlaps"
  option :ypos, aliases: "y", :type => :numeric
  def crop()
    names = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    width = options[:width]
    height = options[:height]
    xpos = options[:xpos] ? options[:xpos] : 0
    ypos = options[:ypos] ? options[:ypos] : 0
    if FileType.image?(names.in_file) then
      opt = '-crop %dx%d+%dx%d' % [width, height, xpos, ypos]
      commander.execute("convert #{opt} #{names.in_file} #{names.out_file}")
    elsif FileType.video?(names.in_file) then
      args = ['ffmpeg']
      args << '-i %s' % names.in_file
      args << '-vf crop=%d:%d:%d:%d' % [width, height, xpos, ypos]
      args << "-map_metadata 0"
      args << names.out_file
      commander.execute(args.join(' '))
    else
      STDERR.puts "This Media Format is not supported on this function"
      exit false
    end
  end

  desc "trim", "trim a image/video with automated detection"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :threshold, aliases: "t", :type => :string,
         :desc => "threshold where trimming begin and end"
  def trim()
    names = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    if FileType.image?(names.in_file) then
      commander.execute("convert -trim #{names.in_file} #{names.out_file}")
    elsif FileType.audio?(names.in_file) then
      threshold = (options[:threshold]) ? options[:threshold] : "-40dB"
      commander.execute("ffmpeg -i #{names.in_file} -af \"silenceremove=start_periods=1:start_duration=0:start_threshold=#{threshold}:detection=peak\" #{names.out_file}")
    end
  end

  desc "resize", "resize the image"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  option :width, aliases: "w", :type => :string,
         :banner => "e.g. 300",
         :desc => "resized image width"
  option :height, aliases: "h", :type => :string,
         :banner => "e.g. 200",
         :desc => "resized image height"
  option :operation, aliases: "p", :type => :string,
         :banner => "enlarge|shrink",
         :desc => "permitted operation for resizing"
  def resize()
    names = Names.new(options[:in], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    if FileType.video?(names.in_file) then
      if options[:width] and options[:height]
        out_opts = []
        out_opts << format("-vf scale=%s:%s,setsar=1:1", options[:width], options[:height])
        commander.execute(FFMPEG.command([FFMPEGOptSet.new(names.in_file, [])],
                                         [FFMPEGOptSet.new(names.out_file, out_opts)]))
      end
    elsif FileType.image?(names.in_file) then
      return if options[:width] == nil && options[:height] == nil
      if options[:operation] == "enlarge"
        op = "<"
      elsif options[:operation] == "shrink"
        op = ">"
      else
        op = ""
      end
      geometry = format("%sx%s%s", options[:width], options[:height], op)
      commander.execute("convert -resize #{geometry} #{names.in_file} #{names.out_file}")
    end
  end

  desc "concat", "concat multiple files"
  option :in, aliases: "i", :type => :array, :required => true
  option :out, aliases: "o", :type => :string
  def concat()
    names = Names.new(options[:in][0], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    srcs = options[:in]
    output = names.out_file

    if srcs.all? { |src| FileType.video?(src) }
      src_list = ""
      src_file = Tempfile.open()
      chap_file = Tempfile.open()
      chap_file.write("### Create Chapterfile ###\n")
      files_to_remove = []
      time_accumulated = Time.new(0)
      srcs.each_with_index do |src, index|
        valid_src =
          if src =~ /^\p{Ascii}+$/
            src
          else
            Dir::Tmpname.create('base') do |src_dup|
              FileUtils.cp(src, src_dup)
              files_to_remove << src_dup
              src_dup
            end
          end
        src_file.write("file '#{File.expand_path(valid_src)}'\n")
        chap_file.write("CHAPTER#{index + 1}=#{time_accumulated.strftime('%H:%M:%S.%L')}\n")
        chap_file.write("CHAPTER#{index + 1}NAME=#{File.basename(src, File.extname(src))}\n")
        duration = `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "#{src}"`.strip().to_f()
        time_accumulated += duration
      end
      src_file.flush
      commander.execute("ffmpeg -n -f concat -safe 0 -i #{src_file.path} -c:v copy -c:a copy -c:s copy -map 0:v -map 0:a -map 0:s? #{output}")
      src_file.close
      chap_file.flush
      commander.execute("MP4Box -chap #{chap_file.path} #{output}")
      chap_file.close
      src_file.unlink
      chap_file.unlink
      FileUtils.rm_f(files_to_remove)
    elsif srcs.all? { |src| FileType.image?(src) }
      commander.execute "convert #{srcs.join(' ')} #{output}"
    else
      STDERR.puts "This Media Format is not supported on this function"
      exit false
    end
  end

  desc "diff", "measure the difference by providing SSIM value"
  option :out, aliases: "o", :type => :string
  def diff(file1, file2)
    commander = Commander.new(options[:log], options[:dryrun])

    if ((FileType.video?(file1) and FileType.video?(file2)) or
        (FileType.image?(file1) and FileType.image?(file2))) then
      if options[:out] then
        commander.execute("ffmpeg -i #{file1} -i #{file2} -filter_complex ssim=#{options[:out]} -an -f null -")
      else
        commander.execute("ffmpeg -i #{file1} -i #{file2} -filter_complex ssim -an -f null -")
      end
    end
  end

  desc "mux", "mux to one file which has streams correspond with input files"
  option :video, aliases: "v", :type => :string, :required => true
  option :audio, aliases: "a", :type => :string, :required => true
  option :audio_delay, aliases: "d", :type => :string
  option :out, aliases: "o", :type => :string
  def mux()
    names = Names.new(options[:video], options[:out])
    commander = Commander.new(options[:log], options[:dryrun])

    in_optsets = []
    in_optsets << FFMPEGOptSet.new(options[:video], [])
    aconf = []
    aconf << "-itsoffset #{options[:audio_delay]}" if options[:audio_delay]
    in_optsets << FFMPEGOptSet.new(options[:audio], aconf)
    commander.execute(FFMPEG.command(in_optsets,
                                     [FFMPEGOptSet.new(names.out_file, [])]))
  end
end

class MainConfig
  attr_accessor :start, :end
end

class VideoConfig
  attr_accessor :encode, :quality, :bitrate, :preset, :tune, :profile, :level

  DEFAULT_ENCODE = 'vp9'
  DEFAULT_QUALITY = '26'
  DEFAULT_BITRATE = '1000'

  def initialize(encode, quality=nil, bitrate=nil)
    @encode = encode
    @quality = quality
    @bitrate = bitrate

    case @encode
    when "x265"
      @preset = "veryslow"
      @tune = "ssim"
      @profile = "main"
      @level = nil
    when "x264"
      @preset = "veryslow"
      @tune = "film"
      @profile = "main"
      @level = "4.1"
    end
  end
end

class PictureConfig
  # conversion options
  attr_accessor :vf_opts, :fps

  def initialize()
    @vf_opts = []
    @vf_opts << "format=yuv420p"
  end

  # down-size to fit to display (width:heigh) in keeping aspect ratio
  def fit_size(display_size)
    @vf_opts << "scale=#{display_size}:force_original_aspect_ratio=decrease"
    @vf_opts << "pad=((iw+1)/2)*2:((ih+1)/2)*2" # avoid width/height to odd
  end

  def decrease_framerate(fps)
    @fps = fps
  end
end

class AudioConfig
  attr_accessor :encode, :quality, :bitrate

  DEFAULT_ENCODE = 'opus'
  DEFAULT_QUALITY = '100'
  DEFAULT_BITRATE = '128'

  def initialize(encode, quality=nil, bitrate=nil)
    # @encode = (encode ? encode : DEFAULT_ENCODE)
    @encode = encode
    @quality = quality
    @bitrate = bitrate
  end

  def quality
    case @encode
    when 'opus'
    else
      @quality
    end
  end

  def bitrate
    case @encode
    when 'opus'
      (@bitrate ? @bitrate : DEFAULT_BITRATE)
    else
      @bitrate
    end
  end
end

class DVDConfig
  attr_accessor :title, :chapters, :audio, :subtitle, :options
  attr_accessor :chapter_marker

  def initialize
    @chapter_marker = true
  end
end

class ImageConfig
  attr_reader :quality # 1~100
end

class FFMPEGOptSet < Struct.new(:fname, :optset)
end

class FFMPEG
  def self.command(in_optsets, out_optsets)
    opts = ["ffmpeg"]

    infilep = proc do |fname, optset|
      opt_arr = []
      optset.each do |opt|
        if opt.instance_of?(String)
          opt_arr << opt
        end
      end
      opt_arr << ["-i", "\"#{fname}\""]
      next opt_arr
    end
    in_optsets.each do |in_optset|
      opts << infilep.call(in_optset.fname, in_optset.optset)
    end

    outfilep = proc do |fname, optset|
      oopts = []
      optset.each do |opt|
        if opt.instance_of?(VideoConfig)
          case opt.encode
          when 'av1'
            oopts << format("-c:v librav1e")
            if opt.bitrate
              oopts << format("-b:v %sk", opt.bitrate)
            end
          when 'vp9'
            oopts << format("-c:v libvpx-vp9")
            if opt.quality
              oopts << format("-crf %s", opt.quality)
              oopts << format("-b:v 0")
            elsif opt.bitrate
              oopts << format("-b:v %s", opt.bitrate)
            end
          when 'h265'
            oopts << format("-c:v libx265")
            oopts << format("-crf %s", opt.quality)
            oopts << format("-preset %s", opt.preset) if opt.preset
            oopts << format("-tune %s", opt.tune) if opt.tune
            # Currently, ffmpeg ​does not support setting profiles on x265
            oopts << format("-profile:v %s", opt.profile) if opt.profile && opt.encoder == 'x264'
            oopts << format("-level %s", opt.level) if opt.level
          else
            oopts << format("-c:v copy")
          end
        elsif opt.instance_of?(AudioConfig)
          case opt.encode
          when 'opus'
            oopts << format("-c:a lib%s", opt.encode)
            oopts << format("-b:a %sk", opt.bitrate) if opt.bitrate
          when 'aac'
            oopts << format("-c:a libfdk_aac")
            if opt.quality
              oopts << format("-aq %s", opt.quality)
              oopts << format("-b:a 0")
            elsif opt.bitrate
              oopts << format("-b:a %s", opt.bitrate)
            end
          else
            oopts << format("-c:a copy")
          end
        elsif opt.instance_of?(PictureConfig)
          oopts << format("-vf \"%s\"", opt.vf_opts.join(",")) if opt.vf_opts && opt.vf_opts.any?
          oopts << format("-r %s", opt.fps) if opt.fps
        elsif opt.instance_of?(String)
          oopts << opt
        end
      end
      oopts << "\"#{fname}\""
      next oopts
    end
    out_optsets.each do |out_optset|
      opts << outfilep.call(out_optset.fname, out_optset.optset)
    end

    opts.join(" ")
  end
end

class HandBrakeCLI
  def self.command(input, output, configs, raw_option)
    cmd_opts = ["HandBrakeCLI"]
    cmd_opts << format("--input \"%s\"", input)
    cmd_opts << format("--output \"%s\"", output)
    configs.each do |config|
      if config.instance_of?(VideoConfig)
        case config.encode
        when 'vp9'
          cmd_opts << format("--encoder VP9")
          if config.quality
            cmd_opts << format("--quality %s", config.quality)
            cmd_opts << format("--vb 0")
          elsif config.bitrate
            cmd_opts << format("--vb %s", config.bitrate)
          end
          cmd_opts << format("--encoder-preset veryslow")
        when 'h265' || 'hevc'
          cmd_opts << format("--encoder x265")
          if config.quality
            cmd_opts << format("--quality %s", config.quality)
            cmd_opts << format("--vb 0")
          elsif config.bitrate
            cmd_opts << format("--vb %s", config.bitrate)
          end
          cmd_opts << format("--encoder-preset %s", config.preset) if config.preset
          cmd_opts << format("--encoder-tune %s", config.tune) if config.tune
          cmd_opts << format("--encoder-profile %s", config.profile) if config.profile
          cmd_opts << format("--encoder-level %s", config.level) if config.level
        else
          cmd_opts << format("--encoder %s", config.encode) if config.encode
          cmd_opts << format("--quality %s", config.quality) if config.quality
          cmd_opts << format("--vb %s", config.bitrate) if config.bitrate
        end
      elsif config.instance_of?(AudioConfig)
        case config.encode
        when 'opus'
          cmd_opts << format("--aencoder %s", config.encode)
          cmd_opts << format("--aq %s", config.quality) if config.quality
          cmd_opts << format("--ab %s", config.bitrate) if config.bitrate
        when 'aac'
          cmd_opts << format("--aencoder ca_haac")
          if config.quality
            cmd_opts << format("--aq %s", config.quality)
            cmd_opts << format("--ab 0")
          elsif config.bitrate
            cmd_opts << format("--ab %s", config.bitrate)
          end
        else
          cmd_opts << format("--aencoder copy")
        end
      elsif config.instance_of?(DVDConfig)
        cmd_opts << (config.title ? format("--title %s", config.title) : "--main-feature")
        cmd_opts << format("--chapters %s", config.chapters) if config.chapters
        cmd_opts << "--markers" if config.chapter_marker
        cmd_opts << (config.audio ? format("--audio %s", config.audio) : "--all-audio" )
        cmd_opts << (config.subtitle ? format("--subtitle %s", config.subtitle) : "--all-subtitles" )
      end
    end

    cmd_opts << raw_option if raw_option

    cmd_opts.join(" ")
  end
end

class ImageMagick
  COMMAND = "convert %s %s %s %s "

  def self.command(input, output, pconfig, raw_option="")
    input_opt = ""
    output_opt = ""
    if pconfig.instance_of?(ImageConfig) then
      input_opt += "-quality %s" % pconfig.quality if pconfig.quality
      s = COMMAND % [input, input_opt, output, output_opt]
      s
    end
  end
end

MediaUtil.start(ARGV)

genalbum

Personal script for encoding family pictures and movies.

require 'fileutils'
require 'time'
require 'exifr'
require 'streamio-ffmpeg'
require 'pathname'
require 'thor'

PICTURE_FILE_EXTS = [".jpg"]
MOVIE_FILE_EXTS = [".mp4"]
ALBUM_DIR_NAME = "album/"
RENAME_DIR_NAME = "rename/"
WEB_DIR_NAME = "album_web/"
IMG_PREFIX = "img_"

class GenAlbum < Thor
  default_command :album
  desc "[album] -i <img_dir>", "generate album"
  option :in, aliases: "i", :type => :string, :required => true
  def album
    src_path = File.expand_path(options[:in], Dir.pwd)
    dest_path = ALBUM_DIR_NAME

    FileUtils.mkdir_p(dest_path) unless FileTest.exist?(dest_path)

    Dir.glob(src_path + "/*").each do |img_file|
      basename = File::basename(img_file)
      src_file = src_path + "/" + basename
      dest_file = dest_path + "/" + basename
      ext = File.extname(img_file)
      if PICTURE_FILE_EXTS.index(ext.downcase) != nil then
        system("convert -resize \"4096x>\" -quality 92 #{src_file} #{dest_file}")
      elsif MOVIE_FILE_EXTS.index(ext.downcase) != nil then
        system("mediautil encode -i #{src_file} -o #{dest_file} -q 22.0 -Q 100.0")
      end
    end
  end

  desc "web -i <img_dir>", "generate lightweight album for web"
  option :in, aliases: "i", :type => :string, :required => true
  def web
    src_path = File.expand_path(options[:in], Dir.pwd)
    dest_path = WEB_DIR_NAME

    FileUtils.mkdir_p(dest_path) unless FileTest.exist?(dest_path)

    Dir.glob(src_path + "/*").each do |img_file|
      basename = File::basename(img_file)
      src_file = src_path + "/" + basename
      dest_file = dest_path + "/" + basename
      ext = File.extname(img_file)
      if PICTURE_FILE_EXTS.index(ext.downcase) != nil then
        system("convert -resize \"1024x>\" -quality 80 #{src_file} #{dest_file}")
      elsif MOVIE_FILE_EXTS.index(ext.downcase) != nil then
        system("mediautil encode -i #{src_file} -o #{dest_file} -q 26.0 -Q 100.0")
      end
    end
  end

  desc "renmae -i <img_dir>", "rename image files in chlonological order"
  option :in, aliases: "i", :type => :string, :required => true
  option :out, aliases: "o", :type => :string
  def rename
    src_path = File.expand_path(options[:in], Dir.pwd)
    dest_path = ((options[:out]) ? options[:out] : RENAME_DIR_NAME)

    FileUtils.mkdir_p(dest_path) unless FileTest.exist?(dest_path)

    img_time = {}
    Dir.glob(src_path + "/*").each do |img_file|
      ext = File.extname(img_file)
      if PICTURE_FILE_EXTS.index(ext.downcase) != nil then
        pic = EXIFR::JPEG.new(img_file)
        if pic.exif? && pic.date_time_original then
          img_time[img_file] = pic.date_time_original
        else
          img_time[img_file] = Time.now
        end
      elsif MOVIE_FILE_EXTS.index(ext.downcase) != nil then
        movie = FFMPEG::Movie.new(img_file)
        time = movie.creation_time
        if time then
          img_time[img_file] = time
        else
          img_time[img_file] = Time.now
        end
      end
    end
    img_time = img_time.sort_by{ |_, v| v }

    digit = img_time.length.to_s.length
    digit = 2 if digit < 2
    img_time.each_with_index do |(img, time), i|
      ext = File.extname(img).downcase
      base = IMG_PREFIX + "%0#{digit}d" % (i + 1) + ext
      src_file = img
      dest_file = dest_path + "/" + base
      system("cp #{src_file} #{dest_file}")
    end
  end
end

GenAlbum.start(ARGV)

nhp   ARCHIVE

sendmail.py

Utility for sendind email programatically

import sys
import argparse
import subprocess
import smtplib
import ssl
from email.mime.text import MIMEText
from email.utils import formatdate

def sendmail(mail_to, mail_from, subject, message):
    smtp_host = 'smtp.<<my-domain()>>'
    smtp_port = '465'
    smtp_user = '<<email>>'.split(sep='@')[0]
    smtp_pass = subprocess.run(["pass {}:{}/{} | head -1".format(smtp_host, smtp_port, smtp_user)], stdout=subprocess.PIPE, shell=True).stdout.decode().strip()

    mime_msg = MIMEText(message)
    mime_msg['Subject'] = subject
    mime_msg['From'] = mail_from
    mime_msg['To'] = mail_to
    mime_msg["Date"] = formatdate(None, True)

    if smtp_port == '465':
        context = ssl.create_default_context()
        smtp = smtplib.SMTP_SSL(smtp_host, smtp_port, context=context)

    if smtp is not None:
        smtp.login(smtp_user, smtp_pass)
        smtp.sendmail(mail_from, mail_to, mime_msg.as_string())
        smtp.quit()


DESC = 'sendmail.py sends mail'
parser = argparse.ArgumentParser(description=DESC)
parser.add_argument('-s', '--subject')
parser.add_argument('-f', '--mailfrom')
parser.add_argument('to')
args = parser.parse_args()

message = sys.stdin.read()
sendmail(args.to, args.mailfrom, args.subject, message)

pwsudo

Nifty script that executes command with elevating privilege using password from pass command.

_userid=$(whoami)
_hostname=$(hostname)

pas=$(pass show "${_hostname}/${_userid}" 2>/dev/null) \
  && ( echo "${pas}" | head -1 | sudo -S -p "" "$@" ) \
  || sudo -S "$@"

exit $?

gitsync   ARCHIVE

filetype-cli   ARCHIVE

strip ruby

A program strips all ruby tags in a HTML stream. All HTML contents are expected to be from STDIN and go out as STDOUT. This procedure is mainly for html files converted from pdf and epub files. Stripping rules are:

  • <ruby> tag will be stripped
  • <rt> tree for furigana tree will be removed
  • <rp> tree contains parenthes for non-ruby-support brouwser will be removed
  • <rb> tag for delimiter will be stripped
  • <rtc> tree for semantic word will be removed
import sys
import lxml.html as html

INPUT_STR = sys.stdin.read()
ROOT = html.fromstring(INPUT_STR).getroottree()

for ruby in ROOT.xpath('//ruby'):
    for rt in ruby.xpath('//rt'):
        rt.drop_tree()
    for rp in ruby.xpath('//rp'):
        rp.drop_tree()
    for rb in ruby.xpath('//rb'):
        rb.drop_tag()
    for rtc in ruby.xpath('//rtc'):
        rtc.drop_tree()
        ruby.drop_tag()

CONTENT = html.tostring(ROOT, encoding="utf-8", method='html', pretty_print=True)
print(CONTENT.decode())

Local Variables