Dotfiles
Table of Contents
- Personal Information
- Linux Distributions
- Home Environment
- Application Configurations
- GNU Emacs
- Findutils
- plocate
- Git
- pass-git-helper
- git-annex
- ghq
- Pass
- mpv
- unison
- Aspell
- textlint
- xkeysnail ARCHIVE
- SKK
- Festival
- Open JTalk
- Jack ARCHIVE
- XRandR
- C/Migemo
- LilyPond
- Graphviz
- Rsync
- libvirt
- Calibre
- yt-dlp
- rTorrent ARCHIVE
- Ledger
- ripgrep-all
- Recoll
- Enchant
- Modern alternative command line tools
- Haunt
- Wine
- Font Viewer
- Gnuplot
- Development Infrastructures
- Stuffs for Dedicated Tasks
- Babel Utility Functions
- Specialized Scripts
- Local Variables
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 1m -F 80G
- 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" "graphicsmagick" "pngquant" "ffmpeg@6.1.1" ; mpv requires version 6 of FFMPEG "dav1d" "jq" "pup" "htmlq" "python-feedparser" "util-linux" "binutils" "make" "llvm" "cmake" "libtool" "pkgconf" "texinfo" "shellcheck" "global" "perl" "pfetch" "neofetch" "python-selenium" "gcc-toolchain@13.3.0" <<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-mocker" "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}/bin:${HOME}/.local/bin:${HOME}/.cargo/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
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)
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 ARCHIVE
XKB ARCHIVE
Interception Tools ARCHIVE
keyd
keyd is a key remapping daemon for Linux. It is fast with concise configuration introducing layer mechanism.
sudo systemctl enable keyd sudo systemctl start keyd
[ids] * [global] oneshot_timeout = 1000 [main] capslock = oneshot(control) leftcontrol = oneshot(capslock) tab = overload(numpad_layer, tab) rightcontrol = oneshot(app_layer) space = overload(shift, space) ' = overloadt(apostrophe_layer, ', 250) leftshift = overload(shift, =) rightshift = overload(shift, -) [apostrophe_layer:M] u = C-u [numpad_layer:C-M] m = 1 , = 2 . = 3 j = 4 k = 5 l = 6 u = 7 i = 8 o = 9 space = 0 ; = - / = . [app_layer:C-A-M]
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
- 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)
- Paths for tangling
- 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))))
- custom file
- 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)))
- modus-themes
- 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
;; 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)
- Modifier Keys
- 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
(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"))))
- Auto Revert
- 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)
- Fringe
- 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))
- Winner
- Minibuffer
(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))))
Formerly, I used to bind C-h to
backward-delete-char
and M-h tobackward-kill-word
, which was very comfortable. However, I realized that C-h should be a help key, as many Emacs packages are developed with the assumption that "C-h is a help key." That said, in the minibuffer where a help key does not necessarily play a significant role, I decided to bind C-h tobackward-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)
- 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))
- Savehist
- 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" (:eval (my/tab-bar-mode-line-string)) 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))
- 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))
- Hardware
- 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))
- Initialization
- 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
(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 :custom (tab-bar-show 8) (tab-bar-history-mode t) (tab-bar-tab-hints t) (tab-bar-tab-name-function (apply-partially #'my/project-root-path t)) :preface (defun my/tab-bar-mode-line-string () "Return the name for the current tab." (format "%s)" (assoc-default 'name (assoc-default 'current-tab (funcall tab-bar-tabs-function))))) (defun my/project-root-path (&optional base-name) "Return an absolute path for `project-current' or base name if BASE-NAME is non-nil." (if-let (project (project-current)) (funcall (if base-name #'file-name-nondirectory #'identity) (directory-file-name (project-root project))) (buffer-name))) :config (with-eval-after-load 'consult (bind-keys :map tab-prefix-map ("B" . consult-buffer-other-tab)))) (keymap-global-set "H-t" tab-prefix-map) (keymap-set tab-prefix-map "s" #'tab-switcher)
- 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)))
- Abbreviations
- Editing
;; the point will be at the beginning of duplicated lines (setopt duplicate-line-final-position 1)
- Indent
See also aggressive-indent-mode
(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
The
save-interprogram-paste-before-kill
variable controls whether clipboard text is moved to the kill-ring when some text is killed in Emacs. This is something I've been longing for!(setopt yank-pop-change-selection t) (setopt delete-active-region 'kill) (setopt kill-do-not-save-duplicates nil) (setopt kill-whole-line nil) (setopt save-interprogram-paste-before-kill (expt 2 16))
- 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))
- Isearch
- Paragraph
(setopt bidi-paragraph-direction 'left-to-right)
- Indent
- 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))
- Image
- Programming
- 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)
- Storage Allocation
- 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-"))
- Shortdoc
- 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))) )
- Transient
- Extensions
- Internal
- 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)
- Save Place
- 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>>"))
- Epa (EasyPG Assistant)
- 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)
- Proced
- 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))
- 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))
- EasyPG
- 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
- 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))
- Smtpmail
- 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
- 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)))))
- 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
(use-package eglot :disabled t :hook ((sh-mode ruby-mode python-mode graphviz-dot-mode) . eglot-ensure) :custom (eglot-extend-to-xref t))
- Mail
- Text
- 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)))
- Dictionary
- Communication
- Files
- 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)))))
- ddskk
- Dired
(use-package dired :bind (:map global-map ("C-x d" . dired-jump) :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 t) (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")))
- dired-rsync
- 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) :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 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)
- extractheadings
- 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.
-
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)
- Magit Essentials
- 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 :commands (mu4e) :hook (emacs-startup . (lambda () (mu4e 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))))
- mu4e-alert ARCHIVE
- 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) :init (with-eval-after-load 'denote (setq rmh-elfeed-org-files (list (denote-get-path-by-id "20230605T173315")))) :custom (rmh-elfeed-org-ignore-tag "ARCHIVE") (rmh-elfeed-org-auto-ignore-invalid-feeds nil) :config (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>>))))
- elfeed-score
- 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) (eshell-history-size 2500) (eshell-hist-ignoredups t) :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")) (with-eval-after-load 'consult ;; narrow down eshell history using vertico (when (require 'em-hist nil t) (bind-keys :map eshell-hist-mode-map ("C-M-n" . consult-history))))) (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))))
- shell
- Org Mode
"emacs-org" "tree-sitter-org"
("https://updates.orgmode.org/feed/releases" soft_update)
Basic settings for built-in Org features. Similar to this section, 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))))) (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 "IP" "TODO keyword acronym standing for 'In Progress") (put 'my/org-todo-keyword--in-action 'char ?i) (defvar my/org-todo-keyword--someday "SD" "TODO keyword acronym standing for 'Someday'") (put 'my/org-todo-keyword--someday 'char ?s) (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 "GU" "DONE keyword acronym standing for 'Given-Up'") (put 'my/org-done-keyword--pending 'char ?g) (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-S-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-someday-task))) ("l" "Log entries in a week" agenda "" ((org-agenda-skip-function #'my/org-agenda-skip-someday-task) (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-skip-function #'my/org-agenda-skip-someday-task) (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 (olp "Someday Tasks")))) (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 Agenda Custom Commands
- 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 org-startup-truncated t org-startup-with-inline-images t 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 "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 Edit Structure
- 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 Habit
- Org Progress
- 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 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 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 Export General
- Org Agenda
- org external 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.
("https://gitlab.com/phillord/org-drill/-/tags?format=atom" soft_update)
(use-package org-drill :ensure t :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)) ;; Workaround for the malfunction of org-expiry-insinuate (unless (ignore-errors (org-expiry-insinuate)) (add-hook 'org-insert-heading-hook 'org-expiry-insert-created) (add-hook 'org-after-todo-state-change-hook 'org-expiry-insert-created) (add-hook 'org-after-tags-change-hook 'org-expiry-insert-created)))
- 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 ;; :load-path "~/data/home/.local/share/ghq/github.com/p-snow/org-web-track" :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)))) (defvar my/usd-exchange-rates nil) (defun my/usd-excange-rate (currency) "Return the exchange rate between CURRENCY and the US dollar. This function also Update `my/usd-exchange-rates', a list of exchange rate against US dollar." (or my/usd-exchange-rates (request (concat "https://openexchangerates.org/api/latest.json?app_id=" (my/credential-info "openexchangerates.org" "app id")) :sync t :success (cl-function (lambda (&key response &allow-other-keys) (let ((content (request-response-data response))) (setq my/usd-exchange-rates (json-parse-string content :object-type 'alist))))) :complete (cl-function (lambda (&key data &allow-other-keys) data)))) (assoc-default currency (assoc-default 'rates my/usd-exchange-rates))) (defun my/convert-currency (target-currency amount) "Convert AMOUNT into TARGET-CURRENCY." (let* ((currs '(("$" . USD) ("€" . EUR) ("£" . GBP) ("₹" . INR) ("¥" . JPY) ("¥" . CNY) ("₩" . KRW) ("₽" . RUB))) (curr-re (rx-to-string `(seq (group-n 1 (or ,@(mapcar 'car currs))) (group-n 2 (1+ digit)))))) (when (string-match curr-re amount) (let ((amt-in-usd (string-to-number (match-string 2 amount))) (to-usd-rate (my/usd-excange-rate (assoc-default (match-string 1 amount) currs))) (from-usd-rate (my/usd-excange-rate target-currency))) (format "%s%.2f" (car (rassq target-currency currs)) (/ (* amt-in-usd from-usd-rate) to-usd-rate)))))) :custom (org-web-track-use-curl nil) (org-web-track-content-fetcher-alist `((,(regexp-quote "melpa.org/#/") . "fetch-dynamic-page.py %s"))) (org-web-track-selectors-alist `((,(regexp-quote "melpa\.org/#/") . "htmlq --text dd | head -n 1 | cut -d ' ' -f 1") (,(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)))))) (seq-some (lambda (prod) (let ((text (dom-text (or (dom-by-class prod "game_purchase_price") (dom-by-class prod "discount_final_price"))))) (if (length= text 0) nil text))) products)))) (,(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) (format "%.3f" (assoc-default 'JPY (assoc-default 'rates json))))) (,(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]) ("localhost" identity) (,(regexp-quote "store.minisforum.jp/products") (lambda (dom) (when-let* ((prod-price (car (dom-by-class dom "product-info__price"))) (sale-price (car (dom-by-tag prod-price 'sale-price)))) (dom-text sale-price)))) (,(rx (seq "www.gmktec.com/" (or "products" "collections"))) (lambda (dom) (when-let* ((price-w (car (dom-by-class dom "product-info__header_price-wrapper"))) (price (car (dom-by-class price-w "tw-text-save-color")))) (my/convert-currency 'JPY (dom-text price))))) (,(regexp-quote "api.github.com/repos") . ,(apply-partially 'assoc-default 'stargazers_count)))) (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/ext-config.el \ -l $XDG_CONFIG_HOME/emacs/lisp/org-web-track-config.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 make-backup-files nil) (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))
- batch tracking
- org-tidy
org-tidy attentively conceals all org drawers. I use it in my English drill session.
(use-package org-tidy :ensure t :after org :bind (:map org-mode-map ("C-c t" . org-tidy-mode)))
- org-super-links ARCHIVE
- org-appear
org-appear attentively displays emphasis markers, such as / for italic and _ for underline, only when the cursor is positioned over the word in Org Mode.
"emacs-org-appear"
(use-package org-appear :after org :disabled t ;; tempprarily not working with Org 9.7 :hook (org-mode . (org-appear-mode)))
- org-ai ARCHIVE
- org mode keymap
- 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-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))))
- 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 (("C-c q f" . consult-ghq-find) ("C-c q g" . consult-ghq-grep)) :custom (consult-ghq-find-function 'dired))
- consult-dir
- 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))))
- support for migemo in orderless
- 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))))))
- vertico
- 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")))
- nxml-mode
- 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")))
- 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))))))))))
- 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\\((.*)\\)[>*\"'] *"))
- inf-ruby
- 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)))
- sh-mode
- special-mode derivatives
- 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))
- sMerge
- 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))
- 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)
- package build
- 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-dotfile-url "https://p-snow.org/config/dotfiles.html#")
(blog-post-skeleton "#+title:" n "#+date:" n "#+description:" n "#+filetags:" n n "#+SETUPFILE: " (pcase (file-name-base (directory-file-name default-directory)) ("posts" "diary/header") ("org-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}}}")
(desktop-recording "ffmpeg -video_size 2560x1440 -framerate 25 -f x11grab -i :0.0 output.mp4")
- ox-haunt
"emacs-ox-haunt"
(use-package ox-haunt :after ox)
- org-static-blog ARCHIVE
- 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" year) "-W" (p "isoweek" isoweek) " :wstart 1" n> "#+end:" n "- meta-projects" n> "#+begin: clocktable :maxlevel 3 :scope agenda " ":block " year "-W" isoweek " :wstart 1 :narrow 40! :tcolumns 3 :formula %" n> "#+end:")
- journal entry template
- External Emacs 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://social.linux.pizza") (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/credential-info) (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/credential-info))
- 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 . It supports a large number of services (LLM models).
(use-package gptel :ensure t :after engine-mode :bind ("H-Q" . my/gpeel-invoke) :preface (defun my/gpeel-invoke (&optional arg) "Enable users to call gptel/gptel-menu/gptel-send by changing prefix argument." (interactive "P") (call-interactively (pcase arg ('(16) #'gptel) ('(4) #'gptel-menu) (default #'gptel-send)))) :custom (gptel-api-key (apply-partially #'my/credential-info "openai.com" "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 '(commit-message-pr . "Please help me proofread the following text as a commit message for Git.") 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/credential-info "www.google.com/<<full-name>>" "gemini apikey") :stream t) (gptel-make-openai "Perplexity" :host "api.perplexity.ai" :key (apply-partially 'my/credential-info "perplexity.ai") :endpoint "/chat/completions" :stream t :models '("llama-3.1-sonar-small-128k-online" "llama-3.1-sonar-large-128k-online" "llama-3.1-sonar-small-128k-chat" "llama-3.1-sonar-large-128k-chat" "llama-3.1-70b-instruct" "llama-3.1-8b-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
- create a session with S-RET(detached-vterm-send-input)
- detach from the session with C-c C-d(detached-detach-key)
- 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)))
- in vterm
- 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 rTorrent client for emacs.
Typical file downloading workflow:
- start mentor: M-x mentor
- add magnet link (URL or torrent file path): M-x mentor-download-load-magnet-link-or-url (l)
- view downloaded file in dired: M-x mentor-dired-jump (v)
- 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
"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)))
- 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 twitter 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))
- 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 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 :mode ("\\.\\(gnuplot\\|gpt\\|plt\\|gp\\)\\'" . gnuplot-mode) :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
(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)))
- consult-denote
"emacs-consult-denote"
(use-package consult-denote :after (consult denote) :init (consult-denote-mode) :bind (("C-c n f" . consult-denote-find) ("C-c n g" . consult-denote-grep)))
- capf-autosuggest
capf-autosuggest provides auto-suggestion functionality in Eshell and major modes, which is derived from Comint mode. In
capf-autosuggest-mode
, you can useC-e
to complete the entire command suggested as a placeholder, andM-f
to complete a single word.(use-package capf-autosuggest :ensure t :hook ((eshell-mode comint-mode) . capf-autosuggest-mode))
- denote-explore
(use-package denote-explore :ensure t :after denote)
- git-modes
- My Emacs Packages
- lfile
(use-package lfile :after org :config (put 'lfile-locate 'org-link-abbrev-safe t))
- open-file
(require 'open-file)
- 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)))
- org-english
(use-package org-english :after org :config (bind-keys :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))) (kill-new (message "%s %s" (my/english-japanese-translate word t) (my/english-pronunciation word t)))))) (with-eval-after-load 'denote-org-extras (setq my/org-english-file (denote-get-path-by-id "20230605T170959"))))
- lfile
Innovative Customization
- 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 ")))
- 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))))
- lambda key
- patches and workarounds
- disable external IME (fcitx)
As of 28.1, Emacs won't steal keyboard focus at startup which means we can use external input methods. But I'm confortable with shutting out external IM, fcitx in my case. Restarting fcitx makes my desire come true.
(add-hook 'emacs-startup-hook (lambda () (call-process-shell-command "fcitx -r")))
- ddskk corsor color malfunction in Emacs 28 and later
- disable external IME (fcitx)
- 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)
- dired subdir repeat map
- 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-update) (elfeed-db-compact) (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
- Increasing the number at point
Org Mode offers handy commands, namely org-increase-number-at-point and org-decrease-number-at-point. I often find myself handling zero-padded numbers such as '010'. I thought it would be nice if there was a command that could increase a number at point while preserving the preceding zeros - and this is the solution.
(defun my/increase-number-at-point (&optional inc) "Increase the number at point by INC." (interactive "p") (let (number (inhibit-message t)) (pcase-let* ((`(,beg . ,end) (save-match-data (setq number (number-at-point)) (cons (match-beginning 0) (match-end 0))))) (when (numberp number) (delete-region beg end) (insert (number-to-string (+ number inc))))))) (defun my/decrease-number-at-point (dec) "Decrease the number at point by DEC." (interactive "p") (funcall #'my/increase-number-at-point (* dec -1))) (keymap-global-set "C-x <up>" #'my/increase-number-at-point) (keymap-global-set "C-x <down>" #'my/decrease-number-at-point) (defvar-keymap my/increase-number-repeat-map :doc "Repeat keymap for increasing/decreasing numbers." :repeat (:enter (my/increase-number-at-point my/decrease-number-at-point)) "<up>" #'my/increase-number-at-point "<down>" #'my/decrease-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
(when (require 'tts nil t) (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/credential-info "openai.com" "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))))
- 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)))) (with-eval-after-load 're-builder (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)
- Reopening the current webpage in org format
(defun my/eww-reopen-in-org (&optional level) "Convert the current HTML page source into an org format, then open it with level LEVEL entries displayed." (interactive "p") (let ((source (plist-get eww-data :source)) (buffer (get-buffer-create "EWW page in org")) (org-startup-folded t)) (with-current-buffer buffer (insert source) (shell-command-on-region (point-min) (point-max) "pandoc - -f html -t org" t t) (org-mode) (org-ctrl-c-tab level)) (switch-to-buffer buffer))) (with-eval-after-load 'eww (bind-keys :map eww-mode-map ("O" . my/eww-reopen-in-org)))
- Retrieving Credential Information
(when (require 'auth-source-pass nil t) (auth-source-pass-enable) (defun my/credential-info (host key) "Return API key for HOST or OpenAI." (or (cdr (assoc key (auth-source-pass-parse-entry host))) (let ((sec (plist-get (car (auth-source-search :host host)) :secret))) (if (functionp sec) (funcall sec) sec)))))
- Binding Saved Kmacro under H-k
Calling my/insert-last-kbd-macro after saving the desired keyboard macro will generate a set of code for persistent use.
(keymap-global-set "H-k" (define-keymap :prefix 'my/kmacro-map)) (defun my/insert-last-kbd-macro (macroname keys) "Customized `insert-kbd-macro' to bind last recorded kbd macro to `my/kmacro-map'.." (interactive (list (read-string "Macro name: ") (read-string "Keys (following H-k): "))) (kmacro-name-last-macro (intern macroname)) (insert-kbd-macro (intern macroname)) (insert "(keymap-set my/kmacro-map") (insert (format " \"%s\"" keys)) (insert (format " #'%s)" macroname))) (keymap-global-set "C-x C-k i" #'my/insert-last-kbd-macro) (mapc #'load-file (file-expand-wildcards (concat (no-littering-expand-etc-file-name "kmacro") "/*.el")))
- 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))))
- update elfeed feeds automatically
- 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"))))
- Common Babel Config
- 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 nil t) ;;; 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 "+6h15m" nil))))] [("{" "preceding sleep" dispatch-dwim-fraction-command :prefix sleep-dispatch-dwim :prefix-arg ("30m") :priority 20 :default "sleep 30m") ("[" "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") ("-F" "free format" "--prefer-free-formats" :init-value (lambda (obj) (oset obj value "--prefer-free-formats"))) ("-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 "'codec,ext,size: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)))
- mpv command
- 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)))))))
- eshell-mode templates
<<eshell-mode-template>>
- 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
(よろ "よろしくお願いいたします。") (いじょう "以上、よろしくお願いいたします。")
- fundamental-mode templates
- elisp code from outside of Emacs
This section mainly for elisp code associated with some other programs.
<<elisp-code>>
- obsolete features ARCHIVE
Org Tweaks for Specific Needs
- 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)))))))
- 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 "M-f" #'org-next-block "M-b" #'org-previous-block "M-F" #'org-babel-next-src-block "M-B" #'org-babel-previous-src-block)
- show org entries pointing to the current (backlink)
These commands show back links even in denote 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)))))
- Copying org property to kill ring
(defun my/org-copy-property-as-kill (property) "Copy the value of PROPERTY to `kill-ring'. If the value matches $(...) format, ... will be treated as a shell command, and the output of the command will be copied to the `kill-ring' instead." (interactive (progn (setq properties (org-entry-properties)) (list (completing-read "Property: " properties)))) (pcase (setq value (alist-get property properties nil nil #'equal)) ((rx (seq "$(" (group (+ print)) ")")) (setq value (shell-command-to-string (match-string 1 value))))) (when (stringp value) (kill-new value) (message "Copied: %s" value))) (with-eval-after-load 'org (keymap-set org-mode-map "C-c K" #'my/org-copy-property-as-kill) (setf (alist-get "K" org-speed-commands nil nil #'equal) #'my/org-copy-property-as-kill))
- 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))))
- Altering the Org Heading Text
I wrote a blog post for this topic.
(with-eval-after-load 'org (bind-keys :map org-mode-map ("C-c C-%" . my/org-alter-heading))) (defun my/org-alter-heading () "CHange the heading text of the entry at point. The previous heading is logged as a log note." (interactive) (let* ((old-heading (org-get-heading t t t t)) (new-heading (read-string "Heading: " old-heading)) (org-log-note-headings (append '((heading . "Heading %-12s from %-12S %t")) org-log-note-headings))) (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 'heading new-heading old-heading 'state) (run-hooks 'post-command-hook)))
- download media files in org ARCHIVE
- Managing Someday 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"; } }
- Reviewing Someday Tasks
Someday tasks are encouraged to review and they prohibited to appear in agenda views.
(defun my/org-someday-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)))))
- Skipping the display of "someday" tasks in the agenda view.
(defvar my/org-review-tag "review" "Tag name set for tasks which is encourged to review.") (defun my/org-agenda-skip-someday-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-someday-task))
- Showing SOMEDAY/GIVEUP Tasks
(with-eval-after-load 'org-ql-view (push `("Someday tasks" :buffers-files ,(list (file-name-concat org-directory "agenda/somedays.org")) :query (todo ,my/org-todo-keyword--someday) :super-groups ((:auto-property "ARCHIVE_OLPATH")) :sort (priority date)) org-ql-views) (push `("Review: someday tasks" :buffers-files ,(list (file-name-concat org-directory "agenda/somedays.org")) :query (todo ,my/org-todo-keyword--someday) :sort (scheduled)) org-ql-views) (push `("Given-up 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-Refiling SOMEDAY Tasks
(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)))))
- Showing an Outline Path for the Entry
(defun my/org-show-outline-path (pom) "Display a sparse tree that shows an outline path for the entry at POM." (interactive (list (point))) (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 'org-ql (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::* Someday Tasks" (expand-file-name "agenda/somedays.org" org-directory))) ((string= todo-state my/org-done-keyword--pending) (format "%s::* Given-up Tasks" (expand-file-name "archive/agenda/given-ups.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))
- Custom Org Link Types
- "project" org link type
A "project" link type is designed for local copies of Git or other CVS repositories. It supports auto-completion when creating a new link and opens the local project repository directly. Additionally, it converts the link to the corresponding URL, such as https://github.com/xxx.
(defun my/ghq-root () "Return ghq root directory." (let ((ghq-root "<<ghq-root()>>")) (expand-file-name (if-let ((symlink (file-symlink-p ghq-root))) (file-name-concat (file-name-directory ghq-root) (file-symlink-p ghq-root)) ghq-root)))) (defun my/org-project-complete-link (&optional arg) "Prompt to select the project from ghq project list." (when (require 'consult-ghq nil t) (let* ((proj (completing-read "Project: " (consult-ghq--list-candidates))) (proj-rel (file-relative-name proj (my/ghq-root)))) (pcase proj-rel ((rx (seq "github.com/" (group (+ print)))) (concat "project+gh:" (match-string 1 proj-rel))) ((rx (seq "gitlab.com/" (group (+ print)))) (concat "project+gl:" (match-string 1 proj-rel))))))) (defun my/org-project-follow-link (link _) "Open LINK in Emacs." (find-file (file-name-concat (my/ghq-root) link))) (defun my/org-project-export-link (link desc format _) "Export LINK to file." (setq link (concat "https://" (file-relative-name (with-current-buffer (find-file-noselect (file-name-concat (my/ghq-root) link)) (project-root (project-current))) (my/ghq-root)))) (pcase format ('html (format "<a href=\"%s\">%s</a>" link (or desc link))) ('md (if desc (format "[%s](%s)" desc link) (format "<%s>" link))) ('latex (format "\\href{%s}{%s}" link desc)) ('texinfo (format "@uref{%s,%s}" link desc)) ('ascii (format "%s (%s)" desc link)) (_ (format "%s (%s)" desc link)))) (with-eval-after-load 'ol (org-link-set-parameters "project" :complete #'my/org-project-complete-link :follow #'my/org-project-follow-link :export #'my/org-project-export-link) (mapc (apply-partially 'add-to-list 'org-link-abbrev-alist) '(("project+gh" . "project:github.com/") ("project+gl" . "project:gitlab.com/"))))
- "dotfile" org link type
(defvar my/dotfile (file-name-concat "<<ghq-root()>>" "github.com/p-snow/config/dotfiles.org") "My dotfile") (defun my/org-dotfile-complete-link (&optional arg) "Prompt to select org heading." (concat "dotfile:" (save-excursion (with-org-files-under (with-current-buffer (find-file-noselect my/dotfile) (consult--read (consult--slow-operation "Collecting headings..." (or (consult-org--headings nil nil 'file) (user-error "No headings")))) ;; FIXME (or (org-entry-get nil "CUSTOM_ID") (org-get-heading t t t t))))))) (defun my/org-dotfile-follow-link (link _) "Open LINK in Emacs." (org-link-open-as-file (concat my/dotfile "::#gnu-emacs") nil)) (defun my/org-dotfile-export-link (link desc format _) "Export LINK to file." (setq link (concat "https://p-snow.org/config/dotfiles.html" link)) (pcase format ('html (format "<a href=\"%s\">%s</a>" link (or desc link))) ('md (if desc (format "[%s](%s)" desc link) (format "<%s>" link))) ('latex (format "\\href{%s}{%s}" link desc)) ('texinfo (format "@uref{%s,%s}" link desc)) ('ascii (format "%s (%s)" desc link)) (_ (format "%s (%s)" desc link)))) (with-eval-after-load 'ol (org-link-set-parameters "dotfile" :complete #'my/org-dotfile-complete-link :follow #'my/org-dotfile-follow-link :export #'my/org-dotfile-export-link))
- "project" org link type
would-be packages
- Package: 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.
(require 'ol) (require 'org-element) (with-eval-after-load 'ol (mapc (apply-partially 'add-to-list 'org-link-abbrev-alist) '(("lfile" . "file:%(lfile-locate)") ("lfile+emacs" . "file+emacs:%(lfile-locate)") ("lfile+sys" . "file+sys:%(lfile-locate)")))) (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)
- Package: 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.
;; 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) ("\\.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)))))
- Package: org-password-store
(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:
- 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
- Review my English word list with my/org-english-drill everyday
- Org-drill ask questions to level up my vocabulary
(require 'define-word) (require 'org) (require 'org-drill) (require 'cl-lib) (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 (&optional unwrap) "Make a word at point cloze deletion format by wrapping brackets or UNWRAP." (interactive "P") (pcase-let* ((thing (if (equal unwrap '(4)) 'list 'word)) (word (if (region-active-p) (buffer-substring (region-beginning) (region-end)) (thing-at-point thing))) (`(,start . ,end) (if (region-active-p) (cons (region-beginning) (region-end)) (let ((bounds (bounds-of-thing-at-point thing))) (cons (car bounds) (cdr bounds)))))) (replace-string word (if (equal unwrap '(4)) (save-match-data (string-match (rx (seq "[" (group (+ graph)) "||" alnum "]")) word) (match-string 1 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-drill-leech-method nil) (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))) ;;; Minor modes ;;;###autoload (define-minor-mode org-english-mode "Buffer-local mode for English learning." :lighter " Eng" (when (boundp 'eldoc-documentation-functions) (add-hook 'eldoc-documentation-functions #'my/org-english-eldoc-documentation-function nil t))) (defun my/org-english-eldoc-documentation-function (&rest args) "Return description of links both forward and backward " (when (and (derived-mode-p 'org-mode) (not (org-before-first-heading-p))) (string-join (append (my/org-english-links) (my/org-english-backlinks)) " "))) (defun my/org-english-links () "Return description of all links." (org-with-wide-buffer (org-back-to-heading t) (let* ((case-fold-search t) (subtree-end (save-excursion (org-end-of-subtree) (point))) (re (rx-to-string `(seq "- " "<" (group (1+ alnum)) ">" (1+ space) "\" " (regexp ,org-link-any-re) " \""))) link-descs) (while (re-search-forward re subtree-end t) (push (match-string-no-properties 4) link-descs)) link-descs))) (defun my/org-english-backlinks () "Return description of all backlinks." (mapcar (lambda (elem) (org-entry-get elem "ITEM")) (let* ((id (org-entry-get nil "ID")) (custom_id (org-entry-get nil "CUSTOM_ID"))) (when (or id custom_id) (org-ql-select (current-buffer) (cond ((and (stringp id) (stringp custom_id)) `(or (link :target ,id) (link :target ,custom_id))) ((stringp id) `(link :target ,id)) ((stringp custom_id) `(link :target ,custom_id)))))))) (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-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、【@】トゥラヴァース、トラバース")))
- Capture a word which come across in the article I'm reading with my/org-english-capture
- 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)
- Package: dispatch-dwim
(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)
- Package: 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 'plocate', 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:~:"
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 input.conf (sample)
RIGHT seek 3 LEFT seek -3 UP seek -30 DOWN seek 30 Ctrl+f seek 5 Ctrl+b seek -5 Ctrl+p seek -60 Ctrl+n seek 60 PGUP add chapter -1 PGDWN add chapter 1 ? playlist-shuffle ENTER playlist-next Shift+ENTER playlist-prev r cycle_values video-rotate 90 180 270 0 R cycle_values video-rotate 270 180 90 0
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.
[X]
skktools[ ]
ibus-skk
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.
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
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"
Gnuplot
"gnuplot"
set colorsequence podo
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
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
Controlling Emacs from Desktop
Using the Emacs server capability, the following actions can be executed from a Linux desktop:
- Download media files
- Browse URLs in EWW
command=$1 opt=$(xclip -out) emacsclient -s server-<<hash-string(seed="server", len=6)>> \ --eval "(my/emacs-batch \"${command}\" \"${opt}\")"
(defun my/emacs-batch (command &optional args) "" (pcase command ("download" (yt-dlp-dispatch-dwim args) (dispatch-execute-macro)) ("browse" (eww args))))
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())