diff --git a/public/0x914472A1FD6775C166F96EBEED739ADF81C78160.asc b/public/0x914472A1FD6775C166F96EBEED739ADF81C78160.asc deleted file mode 100644 index 7f10e8b..0000000 --- a/public/0x914472A1FD6775C166F96EBEED739ADF81C78160.asc +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQGNBGDvIxUBDADI+XOmlMyqATKaIFZuJs4jXcrHVdSV2/h4tWQSgDluogC3U7nt -1BLeNeJ1iB751LIQ3iAhPNbEah1WRnBaNNRkwLi1kzVWtpNWYNa2tCsnzKd2pYHD -vC0FEgdgd+O3p/Q3lL4GJh0BNZrOE0MEmNYISXlhvDfBEzE30tOTJUpG87jRYOmh -X9Fm0nd0s7lelPLbxPy9RHolsmApESIcnjaXBQdeMMBVrQowCBY5r5LCqLXh0wfp -KOLg21D0Pe8Jq48Kxdq7C1MUdwT9RRkDdffxdH83m6HnOhMHsc9YjiI2Ecnsdwgi -0uhN0NVEoi7ulNOAO4ZKxdlWu2OyKvGAUqA3LeMHcGtglNOid8/aUtwiptoEV+qX -dI65uGH4oKOabRKoRqUp1nrACmJMK55ROvjel36tgWNh3YxCIiyxHwQQxpLwMLtd -C3F+ni7XWZlUy26KTvTXBRdQb6p40Q7XIht7KlJsLNptxYRsG5Zt+nRWmS1Srw2p -eKyU1r45Wxx4TnMAEQEAAbQjUGF2ZWwgS29yeXRvdiA8dGhleGNsb3VkQGdtYWls -LmNvbT6JAdQEEwEIAD4WIQSRRHKh/Wd1wWb5br7tc5rfgceBYAUCYO8jFQIbAwUJ -AeEzgAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDtc5rfgceBYHfUC/49HRNJ -vzHLJfpxa2u+qlqQPBE+2gIBkGK24BxzPaYrhGVBi49Q1wDz/vYMknMMLTre4U9D -xJQ7+JgHJmWAc1Of1MdOL6pkBfv/4h33841SWqaRklnrHoflucmJGTE0SdX4RDD6 -6P1RqQF1hXk9h9UMLSeo2NC+Hi0DdcFCMZxmsJtVfgI9TvJvjGCzGtQA5AOwQnIp -1a/SLl1E697sHMbmNvIFSS0xTHm9boR9N5j1NajZqvA3wrvtT60ItiL6A69Du//n -UZBoo02YUK0Rq6pT/dmBaz1EIz8u4Vd2n0x6q8d4cAeLEv6viKdGqG3jqsV3ProV -p/jelhFnyaX2dH0+JKLChdj94kU03g7URqSdnjgpWTGWCryKhMO2k3gfckPU/O+I -cwJv8IhaRrJh2W1qKns1fpCivvmbv9oWeJBeqqR72CIwVKt0kgEN1Y6zRRiF95dO -je0pKCSHerifcEvTMwZef0JL29QW97i4A6GhWvkWGLF8rFjhCToWMgIdQne5AY0E -YO8jFQEMANQYKYWzjMaaqr8J9O+3cgpNMVB66MZjBfCpiLI8V0Q7RZ6T2sAOqIV8 -InZZq/+S014AZIiNzrHTMwxquBwYKjw+V1VbUI5ryUspZu/ROk4dWMvKNYreiBtO -lA2zL+XyhuNXbd9DI0/Vmgdtiqhdaqxkfbwp0bROB6Ad0Ho/yXmJ/vrP2N8dNu7Y -sZtDPeqArR1swz0oDOgdhstvuNL/E4Pg0SBCuJSWDKvL42Ih7JILgMk1bGueL4pZ -bvmi34boMuzqxXOr6fmX9LgtuR3Q3TVW0tqVTHkUySvFihL84euE1BSSufRlc3vL -aPobL5eeB65FSbXjsb+W4fwUyLoPn1sJhRJg3a4rQnxoIMS3jWAqfX18hBjmlsST -8/uxUCoIY/+P8I9H3hNeCKY94SFV6zH/We5hvylJWoydbL2OSHDeEbJb2zHYaCNY -kl14O/kEFwcDPb0UoW5pQaEyE+h+dFEv/zjo+3fb6uDWFNCiLcHHgz8lRvbR/ULN -xCq22I9fzQARAQABiQG8BBgBCAAmFiEEkURyof1ndcFm+W6+7XOa34HHgWAFAmDv -IxUCGwwFCQHhM4AACgkQ7XOa34HHgWBsRAv/YzCTfxupXbCoCdsZL2b17xNboS13 -wnE4ZfsZnwPdf0IM1+txTJ8jJPZpT/W7DuKm5jdwor0avyvkvxnVJmz8zc3MY+Gu -6WrLy3E+5i2/uAAZXzDJo8GXkYsIngERPw3x/OLHu2xySAXstd3Ag4FsEi2dkBsN -A1s3QDJJ4Y5Z74TeYql8gZeSL2w8BQf8HF6hV8Ygpp3ia6DOCeHt8H0oJ5GtZpaG -zNUA0AcRF68uTK/pvmiPDmP9hUHz9b1sG7SNcBM11N3EgQLAAmT8wCZxKqf0wWUJ -VDR3auxRLYTjRuyqu4jHV5ty6hioLF9L/WW+DTc9lOU4gBmvwwbPkoIWWorplF0q -3bUCcJ+A1NjqWjC7kEfKe3vwytQ9q5jHqPn67+B0zL0Gym+j6xSXe7B04s5wCMLn -AA+V3dP97zl87g7zjC1FPJWIw04o3vzAIupNH9WxPJcGWimfrGd6598IraD+zcbA -C5SIw6C0fe2NF4/1d4h7Cvh5Pav3c5WGqfGY -=7b28 ------END PGP PUBLIC KEY BLOCK----- diff --git a/public/404.html b/public/404.html deleted file mode 100644 index 81b710d..0000000 --- a/public/404.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 404 Page not found - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

404 Error

-

Page does not exist.

-
- -
- - diff --git a/public/categories/index.html b/public/categories/index.html deleted file mode 100644 index e5ab3f9..0000000 --- a/public/categories/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - Categories - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Categories

- -
- -
- - diff --git a/public/categories/index.xml b/public/categories/index.xml deleted file mode 100644 index 55ca817..0000000 --- a/public/categories/index.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - Categories on SqrtMinusOne - https://sqrtminusone.xyz/categories/ - Recent content in Categories on SqrtMinusOne - Hugo -- gohugo.io - en-us - - diff --git a/public/config/index.html b/public/config/index.html deleted file mode 100644 index c072819..0000000 --- a/public/config/index.html +++ /dev/null @@ -1 +0,0 @@ -https://sqrtminusone.xyz/configs/readme/ \ No newline at end of file diff --git a/public/configs/console/index.html b/public/configs/console/index.html deleted file mode 100644 index 2bd82f4..0000000 --- a/public/configs/console/index.html +++ /dev/null @@ -1,980 +0,0 @@ - - - - - - Console - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

#+TOC headlines 6

-

.profile

-

Environment

-
export QT_QPA_PLATFORMTHEME="qt5ct"
-export QT_AUTO_SCREEN_SCALE_FACTOR=0
-

Set ripgrep config path

-
export RIPGREP_CONFIG_PATH=$HOME/.config/ripgrep/ripgreprc
-

My paths

-

My script folders

-
if [ -d "$HOME/bin" ] ; then
-    export PATH="$HOME/bin:$PATH"
-    export PATH="$HOME/bin/scripts:$PATH"
-fi
-

Guix settings

-

Enable extra profiles

-
if [ -z "$IS_ANDROID" ]; then
-    GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles
-    for i in $GUIX_EXTRA_PROFILES/*; do
-	profile=$i/$(basename "$i")
-	if [ -f "$profile"/etc/profile ]; then
-	    GUIX_PROFILE="$profile"
-	    . "$GUIX_PROFILE"/etc/profile
-	fi
-	export XDG_DATA_DIRS="$XDG_DATA_DIRS:$profile/share"
-	unset profile
-    done
-fi
-

Set Jupyter config PATH. It defaults to readonly directory somewhere in Guix profile.

-
export JUPYTER_CONFIG_DIR=$HOME/.config/jupyter
-

Set a folder for my packages.

-
export GUIX_PACKAGE_PATH=~/guix-packages
-

Other package managers

-

Using other package managers with Guix requires some extra work.

-

Cask

-
if [ -d "$HOME/.cask" ]; then
-    export PATH="/home/pavel/.cask/bin:$PATH"
-fi
-

Make flatpak apps visible to launchers:

-
if [ -d "$HOME/.local/share/flatpak" ]; then
-    export XDG_DATA_DIRS="$XDG_DATA_DIRS:$HOME/.local/share/flatpak/exports/share"
-fi
-

Enable Nix

-
if [ -f /run/current-system/profile/etc/profile.d/nix.sh ]; then
-  . /run/current-system/profile/etc/profile.d/nix.sh
-fi
-

Use Guix fontconfig. Necessary for nix apps

-
if [ -d "$HOME/.guix-extra-profiles/desktop-misc" ]; then
-    export FONTCONFIG_PATH="$HOME/.guix-extra-profiles/desktop-misc/desktop-misc/etc/fonts"
-fi
-

Make nix apps visible to launchers:

-
if [ -d "$HOME/.nix-profile" ]; then
-    export XDG_DATA_DIRS="$XDG_DATA_DIRS:$HOME/.nix-profile/share/applications"
-fi
-

npm

-

npm is especially cumbersome, for instance because by default it tries to install packages to /gnu/store/.

-

In principle, one can set a prefix like this:

-
prefix=/home/pavel/.npm-packages
-

But I also want to use node from conda occasionally, where prefix is already set correctly. So instead of tangling the above to the ~/.npmrc directly, I set an environment variable in the profile:

-
export NPM_CONFIG_USERCONFIG=$HOME/._npmrc
-

The variable is unset in a script in Guix.org.

-

Set PATH & MANPATH

-
NPM_PACKAGES="${HOME}/.npm-packages"
-
-export PATH="$PATH:$NPM_PACKAGES/bin"
-export MANPATH="${MANPATH-$(manpath)}:$NPM_PACKAGES/share/man"
-

XResources

- - - - - - - - - - - -
Guix dependency
xrdb
-
if [ -z "$IS_ANDROID" ]; then
-    xrdb ~/.Xresources
-fi
-

OFF (OFF) Package manager paths

-

Turned off for now, because probably it won’t be necessary in Guix.

-

LaTeX

-
if [ -d "/usr/local/texlive/2020" ]; then
-    export MANPATH="/usr/local/texlive/2020/texmf-dist/doc/man:$MANPATH"
-    export INFOPATH="/usr/local/texlive/2020/texmf-dist/doc/info:$INFOPATH"
-    export PATH="/usr/local/texlive/2020/bin/x86_64-linux:$PATH"
-fi
-

Cargo (Rust)

-
if [ -d "$HOME/.cargo" ] ; then
-    export PATH="$HOME/.cargo/bin:$PATH"
-fi
-

RVM (Ruby)

-
if [ -d "$HOME/.rvm" ] ; then
-    export PATH="$PATH:$HOME/.rvm/bin"
-fi
-# if [ -d "$HOME/.gem" ]; then
-#     export PATH="$HOME/.gem/ruby/2.7.0/bin:$PATH"
-# fi
-

Go

-
if [ -d "$HOME/go" ] ; then
-    export PATH="$HOME/go/bin:$PATH"
-fi
-

ghcup (Haskell)

-
[ -f "/home/pavel/.ghcup/env" ] && source "/home/pavel/.ghcup/env" # ghcup-env
-

Perl

-
if [ -d "$HOME/perl5" ] ; then
-    PATH="/home/pavel/perl5/bin${PATH:+:${PATH}}"
-    PERL5LIB="/home/pavel/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
-    PERL_LOCAL_LIB_ROOT="/home/pavel/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
-    PERL_MB_OPT="--install_base \"/home/pavel/perl5\""; export PERL_MB_OPT;
-    PERL_MM_OPT="INSTALL_BASE=/home/pavel/perl5"; export PERL_MM_OPT;
-fi
-

Bash

-

.bash_profile

-
[[ -f ~/.profile ]] && . ~/.profile
-
-[[ -f ~/.bashrc ]] && . ~/.bashrc
-

.bashrc

-

My .bashrc, which has pieces from the default ones in Guix & Manjaro, as well some mine settings.

-

Startup & environment

-

Export ‘SHELL’ to child processes. Programs such as ‘screen’ honor it and otherwise use /bin/sh.

-
export SHELL
-

We are being invoked from a non-interactive shell. If this is an SSH session (as in “ssh host command”), source /etc/profile so we get PATH and other essential variables.

-
if [[ $- != *i* ]]
-then
-    [[ -n "$SSH_CLIENT" && -f "/etc/bashrc" ]] && source /etc/profile
-    return
-fi
-

Source the system-wide file

-
if [[ -f "/etc/bashrc" ]]; then
-    source /etc/bashrc
-fi
-
- - - - - - - - - - -
Guix dependency
xhost
-

Allow other users to access X server. Necessary for stuff like aw-watcher-window.

-
xhost +local:root > /dev/null 2>&1
-

Set manpager to bat

-
export MANPAGER="sh -c 'sed -e s/.\\\\x08//g | bat -l man -p'"
-

Launch fish

-

Launch fish shell unless bash itself is launched from fish.

-
use_fish=true
-
-if [[ $(ps --no-header --pid=$PPID --format=cmd) != "fish" && ${use_fish} && $(command -v fish) ]]
-then
-    exec fish
-fi
-

The rest of .bashrc is not executed if fish was launched.

-

Colors

-

Setting for colors, packed in the default .bashrc in Manjaro

-
use_color=true
-
-# Set colorful PS1 only on colorful terminals.
-# dircolors --print-database uses its own built-in database
-# instead of using /etc/DIR_COLORS.  Try to use the external file
-# first to take advantage of user additions.  Use internal bash
-# globbing instead of external grep binary.
-safe_term=${TERM//[^[:alnum:]]/?}   # sanitize TERM
-match_lhs=""
-[[ -f ~/.dir_colors   ]] && match_lhs="${match_lhs}$(<~/.dir_colors)"
-[[ -f /etc/DIR_COLORS ]] && match_lhs="${match_lhs}$(</etc/DIR_COLORS)"
-[[ -z ${match_lhs}    ]] \
-    && type -P dircolors >/dev/null \
-    && match_lhs=$(dircolors --print-database)
-[[ $'\n'${match_lhs} == *$'\n'"TERM "${safe_term}* ]] && use_color=true
-
-if ${use_color} ; then
-    # Enable colors for ls, etc.  Prefer ~/.dir_colors #64489
-    if type -P dircolors >/dev/null ; then
-	if [[ -f ~/.dir_colors ]] ; then
-	    eval $(dircolors -b ~/.dir_colors)
-	elif [[ -f /etc/DIR_COLORS ]] ; then
-	    eval $(dircolors -b /etc/DIR_COLORS)
-	fi
-    fi
-
-    if [[ ${EUID} == 0 ]] ; then
-	PS1='\[\033[01;31m\][\h\[\033[01;36m\] \W\[\033[01;31m\]]\$\[\033[00m\] '
-    else
-	PS1='\[\033[01;32m\][\u@\h\[\033[01;37m\] \W\[\033[01;32m\]]\$\[\033[00m\] '
-    fi
-
-    alias ls='ls --color=auto'
-    alias grep='grep --colour=auto'
-    alias egrep='egrep --colour=auto'
-    alias fgrep='fgrep --colour=auto'
-else
-    if [[ ${EUID} == 0 ]] ; then
-	# show root@ when we don't have colors
-	PS1='\u@\h \W \$ '
-    else
-	PS1='\u@\h \w \$ '
-    fi
-fi
-
-unset use_color safe_term match_lhs sh
-

Settings

-

Some general bash settings.

-

References:

- - -
complete -cf sudo           # Sudo autocompletion
-
-shopt -s checkwinsize       # Check windows size after each command
-shopt -s expand_aliases     # Aliases
-shopt -s autocd             # Cd to directory just by typing its name (without cd)
-

History control

-
shopt -s histappend
-export HISTCONTROL=ignoredups:erasedups
-HISTSIZE=
-HISTFILESIZE=
-

Autocompletions

-
[ -r /usr/share/bash-completion/bash_completion ] && . /usr/share/bash-completion/bash_completion
-if [ -d "/usr/share/fzf" ]; then
-    source /usr/share/fzf/completion.bash
-    source /usr/share/fzf/key-bindings.bash
-fi
-

Aliases

-
alias v="vim"
-alias gg="lazygit"
-alias ls="exa --icons"
-alias ll="exa -lah --icons"
-alias q="exit"
-alias c="clear"
-alias ci="init_conda"
-alias ca="conda activate"
-alias cii="export INIT_CONDA=true && init_conda"
-
if [[ ! -z "$SIMPLE" ]]; then
-    unalias ls
-    alias ll="ls -lah"
-fi
-

Anaconda

-
-

managed by ‘conda init’ !!!

-
-

Yeah, tell this to yourself

-
init_conda () {
-    __conda_setup="$('/home/pavel/.guix-extra-profiles/dev/dev/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
-    if [ $? -eq 0 ]; then
-	eval "$__conda_setup"
-    else
-	if [ -f "/home/pavel/.guix-extra-profiles/dev/dev/etc/profile.d/conda.sh" ]; then
-	    . "/home/pavel/.guix-extra-profiles/dev/dev/etc/profile.d/conda.sh"
-	else
-	    # export PATH="/home/pavel/Programs/miniconda3/bin:$PATH"
-	    echo "what"
-	fi
-    fi
-    unset __conda_setup
-}
-
-if [[ ! -z "$INIT_CONDA" ]]; then
-    init_conda
-fi
-

Starship prompt

-
if [[ -z "$SIMPLE" ]]; then
-    eval "$(starship init bash)"
-fi
-

Fish

- - - - - - - - - - - - - -
Guix dependencyDescription
fishAn alternative non POSIX-compliant shell
-

Fish shell is a non-POSIX-compliant shell, which offers some fancy UI & UX features.

-

Launch starship

-
starship init fish | source
-

Enable vi keybindings & aliases. The alias syntax is the same as in bash, so it’s just a noweb reference to .bashrc.

-
fish_vi_key_bindings
-
-<<shell-aliases>>
-alias cad="conda activate (basename (pwd))"
-
- - - - - - - - - - -
Guix dependency
dt-colorscripts
-

Launch a random DT’s colorscript unless ran inside tmux or Emacs.

-
if ! test -n "$TMUX"; and ! test -n "$IS_EMACS";
-    colorscript random
-end
-

Suppress fish greeting

-
set fish_greeting
-

Anaconda

-

First, a function to initialize anaconda.

-
function init_conda
-    eval /home/pavel/.guix-extra-profiles/dev/dev/bin/conda "shell.fish" "hook" $argv | source
-end
-
-if test -n "$INIT_CONDA";
-    init_conda
-end
-

Then, check if launched from Emacs with environment activated.

-
if test -n "$EMACS_CONDA_ENV";
-    conda activate $EMACS_CONDA_ENV
-end
-

Colors

-

Fish seems to have hardcoded colorcodes in some color settings. I set these to base16 colors so they would match Xresources.

-
set fish_color_command cyan
-set fish_color_comment green
-set fish_color_end white
-set fish_color_error red
-set fish_color_escape yellow
-set fish_color_operator yellow
-set fish_color_param magenta
-set fish_color_quote brwhite
-set fish_color_redirection yellow
-

Keybindings

-
bind -M insert \el forward-char
-bind -M insert \eh backward-char
-bind -M insert \ew forward-word
-bind -M insert \eb backward-word
-

Functions

-

A small function to open the file with $EDITOR.

-
function e
-    eval $EDITOR $argv
-end
-

Nushell

- - - - - - - - - - - -
Guix dependency
nushell-bin
-

A structured shell. I don’t use it as of now, but perhaps one day.

-

Set starship prompt

-
startup = [
-    <<nu-aliases>>,
-    "mkdir ~/.cache/starship",
-    "starship init nu | save ~/.cache/starship/init.nu",
-    "source ~/.cache/starship/init.nu",
-]
-prompt = "starship_prompt"
-

Skip welcome message

-
skip_welcome_message = true
-

Set table mode

-
table_mode = "rounded"
-

Aliases

-
"alias ll = ls -l",
-"alias c = clear",
-"alias q = exit"
-

Colors

-
[color_config]
-primitive_filesize="ub"
-primitive_boolean="yu"
-primitive_duration="g"
-primitive_path="y"
-primitive_date="r"
-primitive_int="c"
-primitive_decimal="c"
-

Starship prompt

- - - - - - - - - - - - - -
Guix dependencyDescription
rust-starshipmy prompt of choice
-

Starship is a nice cross-shell prompt, written in Rust.

-

References:

- - -
[character]
-success_symbol = "[➤ ](bold green)"
-error_symbol = "[ ](bold red)"
-vicmd_symbol = "[ᐊ ](bold green)"
-
-[aws]
-symbol = " "
-
-# [battery]
-# full_symbol = ""
-# charging_symbol = ""
-# discharging_symbol = ""
-
-[conda]
-symbol = " "
-
-[cmd_duration]
-min_time = 500
-format = " [$duration]($style) "
-
-[docker_context]
-symbol = " "
-
-[elixir]
-symbol = " "
-
-[elm]
-symbol = " "
-
-[git_branch]
-symbol = " "
-truncation_length = 20
-
-[golang]
-symbol = " "
-
-# [haskell]
-# symbol = " "
-
-[hg_branch]
-symbol = " "
-
-[java]
-symbol = " "
-
-[julia]
-symbol = " "
-
-[memory_usage]
-symbol = " "
-
-[nim]
-symbol = " "
-
-[nix_shell]
-symbol = " "
-
-[nodejs]
-symbol = " "
-
-[package]
-symbol = " "
-disabled = true
-
-[php]
-symbol = " "
-
-[python]
-symbol = " "
-
-[ruby]
-symbol = " "
-
-[rust]
-symbol = " "
-

Tmux

- - - - - - - - - - - - - - -
Guix dependency
tmux
python-tmuxp
-

tmux is my terminal multiplexer of choice.

-

It provides pretty sane defaults, so the config is not too large. I rebind the prefix to C-a though.

-

Term settings

-

I have no idea how and why these two work.

-
set -g default-terminal "screen-256color"
-set -ga terminal-overrides ",*256col*:Tc"
-

History limit.

-
set -g history-limit 20000
-

Keybindings

-

Enable vi keys and mouse.

-
set-window-option -g mode-keys vi
-set-option -g xterm-keys on
-set-option -g mouse on
-set -sg escape-time 10
-

Change prefix from C-b to C-a.

-
unbind C-b
-set -g prefix C-a
-bind C-a send-prefix
-

Vi-like keybindings to manage panes & windows.

-
bind h select-pane -L
-bind j select-pane -D
-bind k select-pane -U
-bind l select-pane -R
-
-bind s split-window
-bind v split-window -h
-
-bind-key n new-window
-bind-key t next-window
-bind-key T previous-window
-

Reload the config.

-
bind r source-file ~/.tmux.conf
-

Copy to clipboard

- - - - - - - - - - - -
Guix dependency
xclip
-

Make tmux copying copy to clipboard as well

-
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "xclip -selection clipboard -i"
-bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -selection clipboard -i"
-

UI

-

I generated the following with tmuxline.vim plugin and palenight theme for vim-airline

-
# This tmux statusbar config was created by tmuxline.vim
-# on Wed, 22 Jan 2020
-
-set -g status-justify "centre"
-set -g status "on"
-set -g status-left-style "none"
-set -g message-command-style "fg=#bfc7d5,bg=#474b59"
-set -g status-right-style "none"
-set -g pane-active-border-style "fg=#939ede"
-set -g status-style "none,bg=#333747"
-set -g message-style "fg=#bfc7d5,bg=#474b59"
-set -g pane-border-style "fg=#474b59"
-set -g status-right-length "100"
-set -g status-left-length "100"
-setw -g window-status-activity-style "none,fg=#939ede,bg=#333747"
-setw -g window-status-separator ""
-setw -g window-status-style "none,fg=#bfc7d5,bg=#333747"
-set -g status-left "#[fg=#292D3E,bg=#939ede] #S #[fg=#939ede,bg=#474b59,nobold,nounderscore,noitalics]#[fg=#bfc7d5,bg=#474b59] #W #[fg=#474b59,bg=#333747,nobold,nounderscore,noitalics]"
-set -g status-right "#[fg=#333747,bg=#333747,nobold,nounderscore,noitalics]#[fg=#bfc7d5,bg=#333747] %-H:%M #[fg=#474b59,bg=#333747,nobold,nounderscore,noitalics]#[fg=#bfc7d5,bg=#474b59] %a, %b %d #[fg=#939ede,bg=#474b59,nobold,nounderscore,noitalics]#[fg=#292D3E,bg=#939ede] #H "
-setw -g window-status-format "#[fg=#333747,bg=#333747,nobold,nounderscore,noitalics]#[default] #I #W #[align=left] #[fg=#333747,bg=#333747,nobold,nounderscore,noitalics]"
-setw -g window-status-current-format "#[fg=#333747,bg=#474b59,nobold,nounderscore,noitalics]#[fg=#bfc7d5,bg=#474b59] #I #W #[fg=#474b59,bg=#333747,nobold,nounderscore,noitalics]"
-

Source the line config:

-
source ~/.tmux.line.conf
-

Alacritty

- - - - - - - - - - - -
Guix dependency
alacritty
-

Alacritty is a GPU-accelerated terminal emulator. I haven’t found it to be an inch faster than st, but configuration the in yml format is way more convinient than patches.

-

Once again, we have an application which doesn’t support reading Xresources, so here goes noweb.

-

-
xrdb -query all | grep "$color:" | cut -f 2
-
(setq-local org-confirm-babel-evaluate nil)
-

References:

- - -
decorations: none
-
-font:
-  normal:
-    family: JetBrainsMono Nerd Font
-    style: Regular
-
-  size: 10
-
-env:
-  TERM: xterm-256color
-
-colors:
-  primary:
-    background: '<<get-xrdb(color="color0")>>'
-    foreground: '<<get-xrdb(color="color7")>>'
-  normal:
-    black: '<<get-xrdb(color="color0")>>'
-    red: '<<get-xrdb(color="color1")>>'
-    green: '<<get-xrdb(color="color2")>>'
-    yellow: '<<get-xrdb(color="color3")>>'
-    blue: '<<get-xrdb(color="color4")>>'
-    magenta: '<<get-xrdb(color="color5")>>'
-    cyan: '<<get-xrdb(color="color6")>>'
-    white: '<<get-xrdb(color="color7")>>'
-  bright:
-    Black: '<<get-xrdb(color="color8")>>'
-    Red: '<<get-xrdb(color="color9")>>'
-    Green: '<<get-xrdb(color="color10")>>'
-    Yellow: '<<get-xrdb(color="color11")>>'
-    Blue: '<<get-xrdb(color="color12")>>'
-    Magenta: '<<get-xrdb(color="color13")>>'
-    Cyan: '<<get-xrdb(color="color14")>>'
-    White: '<<get-xrdb(color="color15")>>'
-
-background_opacity: 0.80
-
-window:
-  padding:
-    x: 0
-    y: 0
-  dynamic_padding: true
-
-key_bindings:
-  - { key: Paste,                                       action: Paste          }
-  - { key: Copy,                                        action: Copy           }
-  - { key: L,         mods: Control,                    action: ClearLogNotice }
-  - { key: L,         mods: Control, mode: ~Vi|~Search, chars: "\x0c"          }
-  - { key: PageUp,    mods: Shift,   mode: ~Alt,        action: ScrollPageUp,  }
-  - { key: PageDown,  mods: Shift,   mode: ~Alt,        action: ScrollPageDown }
-  - { key: Home,      mods: Shift,   mode: ~Alt,        action: ScrollToTop,   }
-  - { key: End,       mods: Shift,   mode: ~Alt,        action: ScrollToBottom }
-
-  #  Turn off vi mode
-  - { key: Space,  mods: Shift|Control, mode: ~Search,    action: ReceiveChar             }
-
-  # (Windows, Linux, and BSD only)
-  - { key: V,              mods: Control|Shift, mode: ~Vi,        action: Paste            }
-  - { key: C,              mods: Control|Shift,                   action: Copy             }
-  - { key: F,              mods: Control|Shift, mode: ~Search,    action: ReceiveChar    }
-  - { key: B,              mods: Control|Shift, mode: ~Search,    action: ReceiveChar   }
-  - { key: Insert,         mods: Shift,                           action: PasteSelection   }
-  - { key: Key0,           mods: Control,                         action: ResetFontSize    }
-  - { key: Equals,         mods: Control,                         action: IncreaseFontSize }
-  - { key: Plus,           mods: Control,                         action: IncreaseFontSize }
-  - { key: NumpadAdd,      mods: Control,                         action: IncreaseFontSize }
-  - { key: Minus,          mods: Control,                         action: DecreaseFontSize }
-  - { key: NumpadSubtract, mods: Control,                         action: DecreaseFontSize }
-

Various console applications

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Guix dependencyDescription
ncursesProvides stuff like clear
exals replacement, written in Rust
batcat clone with syntax highlighting
htopInteractive process viewer
nethogsA tool to group processed by used bandwidth
osyncrsync wrapper
neofetchFetch system info
fzffuzzy finder
p7ziparchiver
password-storeCLI password manager
unzip
jmtpfsA tool to mount MTP devices (e.g. Android)
tokeiCount lines of code
sshfsMount stuff over SSH
-

ripgrep config

-

Occasionally I can’t exclude certain files from ripgrep via the VCS settings, so here is a simple config to ignore certain files globally.

-
--ignore-file=/home/pavel/.config/ripgrep/ripgrepignore
-

The corresponding ignore file:

-
*.ts.snap
-

By default ripgrep doesn’t read any configs, so it is necessary to set the RIPGREP_CONFIG_PATH variable in the .profile.

-

Misc scripts

-

nt - exec command with a finished notification

-

Usage:

-
nt <command>
-
command="$@"
-if [ ! -z "$command" ]; then
-    start_time="$(date -u +%s)"
-    $command
-    end_time="$(date -u +%s)"
-    elapsed="$(($end_time-$start_time))"
-    notify-send "Terminal" "Command\n$command\nexecuted in $elapsed seconds"
-else
-    notify-send "Terminal" "Command execution complete"
-fi
-

autocommit

-

A script to autocommit files in a repository. I use it to sync my org directory and password store. I guess it’s not how git is intended to be used, but it works for me.

-

Usage:

-
autocommit <repository> [-F]
-

Environment:

- - - - - - - - - - - - - - - -
VariableDescriptionDefault value
TIMEOUT_MINDefault timeout60
-

Here’s more or less what the script is doing:

- - -
TIMEOUT_MIN=${TIMEOUT_MIN:-60}
-
-export DISPLAY=:0
-cd "$1"
-
-TIMESTAMP=$(date +%s)
-LAST_COMMIT_TIMESTAMP=$(git log -1 --format="%at" | xargs -I{} date -d @{} +%s)
-RECENTLY_CHANGED_NUM=$(find . -not -path '*/\.*' -mmin -$TIMEOUT_MIN | wc -l)
-CHANGED_NUM=$(git status --porcelain | wc -l)
-COMMITED="No"
-PUSHED="No"
-FETCHED="No"
-MERGED="No"
-
-if [[ $(git ls-files -u | wc -l) -gt 0 ]]; then
-    notify-send -u critical "Autocommit $(pwd)" "Merge conflict!"
-    exit
-fi
-
-if [[ ($RECENTLY_CHANGED_NUM -eq 0 || $2 = "-F") && $CHANGED_NUM -gt 0 ]]; then
-    read -r -d '' MESSAGE << EOM
-Autocommit $(date -Iminutes)
-
-Hostname: $(hostname)
-EOM
-    git add -A
-    git commit -m "$MESSAGE"
-    COMMITED="Yes"
-fi
-
-NEED_TO_PUSH=$(git log origin/master..HEAD | wc -l)
-
-git fetch && FETCHED="Yes" || FETCHED="No"
-if [[ $RECENTLY_CHANGED_NUM -gt 0 && $2 != '-F' ]]; then
-    MERGED="Waiting"
-fi
-
-if [[ ($RECENTLY_CHANGED_NUM -eq 0 || $2 = "-F") && $FETCHED = "Yes" ]]; then
-    MERGE_OUT=$(git merge origin/master) && MERGED="Yes" || MERGED="No"
-fi
-
-if [[ $NEED_TO_PUSH -gt 0 && ($MERGED = "Yes" || $MERGED = "Waiting") ]]; then
-    git push origin && PUSHED="Yes" || PUSHED="No"
-fi
-
-if [[ $PUSHED = "Yes" || $COMMITED = "Yes" || ($MERGED = "Yes" &&  $MERGE_OUT != "Already up to date.")]]; then
-    read -r -d '' NOTIFICATION << EOM
-Commited: $COMMITED
-Fetched: $FETCHED
-Merged: $MERGED
-Pushed: $PUSHED
-EOM
-    notify-send "Autocommit $(pwd)" "$NOTIFICATION"
-fi
-
-if [[ $(git ls-files -u | wc -l) -gt 0 ]]; then
-    notify-send -u critical "Autocommit $(pwd)" "Merge conflict!"
-fi
-

mcron job:

-
(job "0 * * * *" "autocommit ~/Documents/org-mode")
-(job "0,15,30,45 * * * *" "autocommit ~/.password-store")
-

Guix settings

-

-
(my/format-guix-dependencies)
-
(specifications->manifest
- '(
-   <<packages()>>))
-

Android notes

-

SSH instructions: https://wiki.termux.com/wiki/Remote%5FAccess

-

Don’t forget to install the following termux packages:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Termux package
vim
tmux
starship
fish
exa
bat
git
-

Also:

- -

Installation of DT’s colorscripts:

-
git clone https://gitlab.com/dwt1/shell-color-scripts.git
-cd shell-color-scripts
-

Apply a patch:

-
--- a/colorscript.sh
-+++ b/colorscript.sh
-@@ -2,7 +2,7 @@
-
- # Simple CLI for shell-color-scripts
-
--DIR_COLORSCRIPTS="/opt/shell-color-scripts/colorscripts"
-+DIR_COLORSCRIPTS="$PREFIX/opt/shell-color-scripts/colorscripts"
- LS_CMD="$(command -v ls)"
- fmt_help="  %-20s\t%-54s\n"
- list_colorscripts="$($LS_CMD "${DIR_COLORSCRIPTS}" | cut -d ' ' -f 1 | nl)"
-
sudo mkdir -p $PREFIX/opt/shell-color-scripts/colorscripts || return 1
-sudo cp -rf colorscripts/* $PREFIX/opt/shell-color-scripts/colorscripts
-sudo cp colorscript.sh $PREFIX/bin/colorscript
-
-
- -
- - diff --git a/public/configs/desktop/index.html b/public/configs/desktop/index.html deleted file mode 100644 index 6654ac6..0000000 --- a/public/configs/desktop/index.html +++ /dev/null @@ -1,3682 +0,0 @@ - - - - - - Desktop - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

My general desktop environment configuration.

-

Parts prefixed with (OFF) are not used, but kept for historic purposes. For some reason GitHub’s org renderer ignores TODO status, hence such a prefix. Round brackets instead of square ones to prevent GitHub’s org renderer from screwing up.

-
-
-
Table of Contents
- -
- -

Global customization

-

Colors

-

Most of the colors are from the Palenight theme. Colorcodes are taken from this repo:

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
colorkeyvalue
blackcolor0#292d3e
redcolor1#f07178
greencolor2#c3e88d
yellowcolor3#ffcb6b
bluecolor4#82aaff
magentacolor5#c792ea
cyancolor6#89ddff
whitecolor7#d0d0d0
light-blackcolor8#434758
light-redcolor9#ff8b92
light-greencolor10#ddffa7
light-yellowcolor11#ffe585
light-bluecolor12#9cc4ff
light-magentacolor13#e1acff
light-cyancolor14#a3f7ff
light-whitecolor15#ffffff
-

The table above is the only source of truth for colors in this config.

-

The first way to get colors of it is to use the following noweb:

-

-
(let ((color (seq-some (lambda (e) (and (string= name (car e)) (nth 2 e))) table)))
-  (if (> quote 0)
-      (concat "\"" color "\"")
-    color))
-

Also, run the following to disable configuration for noweb evaluations:

-
(setq-local org-confirm-babel-evaluate nil)
-

Test:

-
<<get-color(name="red", quote=1)>>
-

Xresources

-

Colors in Xresources

-

However, I’d rather use the Xresources file wherever possible. Here is the code to generate an Xresources file from this table:

-

-
(apply
- #'concat
- (mapcar
-  (lambda (elem)
-    (concat "*" (nth 1 elem) ": " (nth 2 elem) "\n"))
-  (seq-filter
-   (lambda (elem) (nth 1 elem))
-   table)))
-
<<get-xresources()>>
-
-*background: <<get-color(name="black")>>
-*foreground: <<get-color(name="white")>>
-

Fonts

-

Also, Xresources are used to set Xft settings. Unfortunately, the DPI setting has to be unique for each machine, which means I cannot commit Xresources to the repo.

-

-
(let ((hostname (system-name)))
-  (cond ((string-equal hostname "azure") 120)
-	((string-equal hostname "eminence") 120)
-	((string-equal hostname "indigo") 120)
-	(t 96)))
-
Xft.dpi: <<get-dpi()>>
-

Themes

-

A few programs I use to customize the apperance are listed below.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Guix dependencyDescription
matcha-themeMy preferred GTK theme
papirus-icon-themeMy preferred Icon theme
gnome-themes-standard
xsettingsdX11 settings daemon
gnome-themes-extra
-

xsettingsd is a lightweight daemon which configures X11 applications. It is launched with shepherd in the Services section.

-
Net/ThemeName "Matcha-dark-azul"
-Net/IconThemeName "Papirus-Dark"
-Gtk/DecorationLayout "menu:minimize,maximize,close"
-Gtk/FontName "Sans 10"
-Gtk/MonospaceFontName "JetBrainsMono Nerd Mono 12"
-Gtk/CursorThemeName "Adwaita"
-Xft/Antialias 1
-Xft/Hinting 0
-Xft/HintStyle "hintnone"
-

Device-specific settings

- - - - - - - - - - - - - - - - - - - - - -
Guix dependencyDescription
xrandrX11 CLI to RandR
xgammaA tool to alter monitor’s gamma correction
xinputConfigure input devices
-

Set screen layout & other params depending on hostname

-
hostname=$(hostname)
-if [ "$hostname" = "indigo" ]; then
-    xrandr --output DisplayPort-0 --off --output HDMI-A-0 --mode 1920x1080 --pos 0x0 --rotate normal --output DVI-D-0 --mode 1920x1080 --pos 1920x0 --rotate normal
-elif [ "$hostname" = "eminence" ]; then
-    xgamma -gamma 1.25
-fi
-

EXWM

-

Settings for Emacs X Window Manager, a tiling WM implemented in Emacs Lisp.

-

References:

- -

Xsession

-

First things first, Emacs has to be launched as a window manager. On a more conventional system I’d create a .desktop file in some system folder that can be seen by a login manager, but in the case of Guix it’s a bit more complicated, because all such folders are not meant to be changed manually.

-

However, GDM, the login manager that seems to be the default on Guix, launches ~/.xsession on the startup if it’s present, which is just fine for my purposes.

-
# Source .profile
-. ~/.profile
-
-# Disable access control for the current user
-xhost +SI:localuser:$USER
-
-# Fix for Java applications
-export _JAVA_AWT_WM_NONREPARENTING=1
-
-# Apply XResourses
-xrdb -merge ~/.Xresources
-
-# Turn off the system bell
-xset -b
-
-# Use i3lock as a screen locker
-xss-lock -- i3lock &
-
-# Some apps that have to be launched only once.
-picom &
-# nm-applet &
-dunst &
-copyq &
-
-# Run the Emacs startup script as a session.
-# exec dbus-launch --exit-with-session ~/.emacs.d/run-exwm.sh
-exec dbus-launch --exit-with-session emacs -mm --debug-init -l ~/.emacs.d/desktop.el
-

Startup apps

-

Now that Emacs is launched, it is necessary to set up the EXWM-specific parts of config.

-

I want to launch some apps from EXWM instead of the Xsession file for two purposes:

- -

As of now, these are polybar, feh and, shepherd:

-
(defun my/exwm-run-polybar ()
-  (call-process "~/bin/polybar.sh"))
-
-(defun my/exwm-set-wallpaper ()
-  (call-process-shell-command "feh --bg-fill ~/Pictures/wallpaper.jpg"))
-
-(defun my/exwm-run-shepherd ()
-  (when (string-empty-p (shell-command-to-string "pgrep -u pavel shepherd"))
-    (call-process "shepherd")))
-

Moving windows

-

My functions for managing windows. I initially wrote these to mimic the i3 behavior for my Emacs + i3 integration, but I want to try to keep them for the EXWM config as well to make the transition less painful.

-

A predicate which checks whether there is space in the given direction:

-
(defun my/exwm-direction-exists-p (dir)
-  (cl-some (lambda (dir)
-	  (let ((win (windmove-find-other-window dir)))
-	    (and win (not (window-minibuffer-p win)))))
-	(pcase dir
-	  ('width '(left right))
-	  ('height '(up down)))))
-

And a function to move windows with the following behavior:

- - -
(defun my/exwm-move-window (dir)
-  (let ((other-window (windmove-find-other-window dir))
-	(other-direction (my/exwm-direction-exists-p
-			  (pcase dir
-			    ('up 'width)
-			    ('down 'width)
-			    ('left 'height)
-			    ('right 'height)))))
-    (cond
-     ((and other-window (not (window-minibuffer-p other-window)))
-      (window-swap-states (selected-window) other-window))
-     (other-direction
-      (evil-move-window dir)))))
-

Resizing windows

-

Something like this also goes for resizing windows. I’m used to the i3 “mode” for this functionality, and this seems to be a sensible approach.

-
(setq my/exwm-resize-value 5)
-
-(defun my/exwm-resize-window (dir kind &optional value)
-  (unless value
-    (setq value my/exwm-resize-value))
-  (pcase kind
-    ('shrink
-     (pcase dir
-       ('width
-	(evil-window-decrease-width value))
-       ('height
-	(evil-window-decrease-height value))))
-    ('grow
-     (pcase dir
-       ('width
-	(evil-window-increase-width value))
-       ('height
-	(evil-window-increase-height value))))))
-
-(defhydra my/exwm-resize-hydra (:color pink :hint nil :foreign-keys run)
-  "
-^Resize^
-_l_: Increase width   _h_: Decrease width   _j_: Increase height   _k_: Decrease height
-
-_=_: Balance          "
-  ("h" (lambda () (interactive) (my/exwm-resize-window 'width 'shrink)))
-  ("j" (lambda () (interactive) (my/exwm-resize-window 'height 'grow)))
-  ("k" (lambda () (interactive) (my/exwm-resize-window 'height 'shrink)))
-  ("l" (lambda () (interactive) (my/exwm-resize-window 'width 'grow)))
-  ("=" balance-windows)
-  ("q" nil "quit" :color blue))
-

App shortcuts

-

Also, a transient for shortcuts for the most frequent apps.

-

I wanted to make the interactive lambda a macro, but this doesn’t seem to work the way I expect, so the code has a bit of duplication.

-
(use-package transient
-  :straight t)
-
-(defun my/run-in-background (command)
-  (let ((command-parts (split-string command "[ ]+")))
-    (apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts)))))
-
-(transient-define-prefix my/exwm-apps ()
-  ["Apps"
-   ("t" "Termnial (Alacritty)" (lambda () (interactive) (my/run-in-background "alacritty")))
-   ("b" "Browser (Firefox)" (lambda () (interactive) (my/run-in-background "firefox")))
-   ("v" "VK" (lambda () (interactive) (my/run-in-background "vk")))
-   ("s" "Slack" (lambda () (interactive) (my/run-in-background "slack-wrapper")))
-   ("d" "Discord" (lambda () (interactive) (my/run-in-background "flatpak run com.discordapp.Discord")))
-   ("q" "Quit" transient-quit-one)])
-

Move workspace to another monitor

-

A function to move the current workspace to another monitor.

-
(defun my/exwm-workspace-switch-monitor ()
-  (interactive)
-  (if (plist-get exwm-randr-workspace-monitor-plist exwm-workspace-current-index)
-      (setq exwm-randr-workspace-monitor-plist
-	    (map-delete exwm-randr-workspace-monitor-plist exwm-workspace-current-index))
-    (setq exwm-randr-workspace-monitor-plist
-	  (plist-put exwm-randr-workspace-monitor-plist
-		     exwm-workspace-current-index
-		     my/exwm-another-monitor)))
-  (exwm-randr-refresh))
-

Switch to the opposite monitor

-

Store the information about which workspace is available on which monitor.

-
(setq my/exwm-monitor-workspace '())
-
-(defun my/exwm-get-current-monitor ()
-  (if (plist-get exwm-randr-workspace-monitor-plist exwm-workspace-current-index)
-      1 0))
-
-(defun my/exwm-update-current-monitor ()
-  (setf (alist-get (my/exwm-get-current-monitor) my/exwm-monitor-workspace)
-	exwm-workspace-current-index))
-
-(add-hook 'exwm-workspace-switch-hook
-	  #'my/exwm-update-current-monitor)
-

Switch to the opposite monitor. For now, this works only for two monitors because I don’t have more.

-
(defun my/exwm-switch-to-other-monitor ()
-  (interactive)
-  (let* ((current (my/exwm-get-current-monitor))
-	 (other (seq-some
-		 (lambda (m)
-		   (and (not (= (car m) current)) (cdr m)))
-		 my/exwm-monitor-workspace))
-	 (focus-follows-mouse nil)
-	 (mouse-autoselect-window nil))
-    (exwm-workspace-switch other)))
-

Switching buffers

-

A single perspective usually has only a handful of EXWM buffers, so here is a function to cycle them.

-

Those buffers that are visible in another window are highlighted blue and skipped. The current buffer is highlighted yellow.

-
(defun my/cycle-persp-exwm-buffers (dir)
-  (let* ((current (current-buffer))
-	 (ignore-rx (persp--make-ignore-buffer-rx))
-	 (visible-buffers '())
-	 (exwm-data
-	  (cl-loop for buf in (persp-current-buffers)
-		   for is-another = (and (get-buffer-window buf) (not (eq current buf)))
-		   if (and (buffer-live-p buf)
-			   (eq 'exwm-mode (buffer-local-value 'major-mode buf))
-			   (not (string-match-p ignore-rx (buffer-name buf))))
-		   collect buf into all-buffers
-		   and if (not is-another) collect buf into cycle-buffers
-		   finally (return (list all-buffers cycle-buffers))))
-	 (all-buffers (nth 0 exwm-data))
-	 (cycle-buffers (nth 1 exwm-data))
-	 (current-pos (or (cl-position current cycle-buffers) -1)))
-    (if (seq-empty-p cycle-buffers)
-	(message "No EXWM buffers to cycle!")
-      (let* ((next-pos (% (+ current-pos (length cycle-buffers)
-			     (if (eq dir 'forward) 1 -1))
-			  (length cycle-buffers)))
-	     (next-buffer (nth next-pos cycle-buffers)))
-	(switch-to-buffer next-buffer)
-	(message
-	 "%s"
-	 (mapconcat
-	  (lambda (buf)
-	    (let ((name (string-replace "EXWM :: " "" (buffer-name buf))))
-	      (cond
-	       ((eq (current-buffer) buf)
-		(concat
-		 "["
-		 (propertize name 'face `(foreground-color . ,(doom-color 'yellow)))
-		 "]"))
-	       ((not (member buf cycle-buffers))
-		(concat
-		 "["
-		 (propertize name 'face `(foreground-color . ,(doom-color 'blue)))
-		 "]"))
-	       (t (format " %s " name)))))
-	  all-buffers
-	  " "))))))
-

Add all EXWM buffers to current perspective

-
(defun my/add-exwm-buffers-to-current-perspective ()
-  (interactive)
-  (let ((ignore-rx (persp--make-ignore-buffer-rx)))
-    (cl-loop for buf in (buffer-list)
-	     if (and (buffer-live-p buf)
-		     (eq 'exwm-mode (buffer-local-value 'major-mode buf))
-		     (not (string-match-p ignore-rx (buffer-name buf))))
-	     do (persp-add-buffer (buffer-name buf)))))
-

Revive perspectives

-

Occasionally the current perspective gets screwed up after a popup. This function attempts to fix it.

-
(defun my/exwm-revive-perspectives ()
-  "Make perspectives in the current frame not killed."
-  (interactive)
-  (let ((to-switch nil))
-    (maphash
-     (lambda (_ v)
-       (setf (persp-killed v) nil)
-       (unless to-switch
-	 (setq to-switch v)))
-     (frame-parameter nil 'persp--hash))
-    (when to-switch
-      (persp-switch (persp-name to-switch)))))
-

Locking up

-

Run i3lock.

-
(defun my/exwm-lock ()
-  (interactive)
-  (my/run-in-background "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png"))
-

Keybindings

-

Setting keybindings for EXWM. This actually has to be in the :config block of the use-package form, that is it has to be run after EXWM is loaded, so I use noweb to put this block in the correct place.

-

First, some prefixes for keybindings that are always passed to EXWM instead of the X application in line-mode:

-
(setq exwm-input-prefix-keys
-      `(?\C-x
-	?\C-w
-	?\M-x
-	?\M-u))
-

Also other local keybindings, that are also available only in line-mode:

-
(defmacro my/app-command (command)
-  `(lambda () (interactive) (my/run-in-background ,command)))
-
-(general-define-key
- :keymaps '(exwm-mode-map)
- "C-q" 'exwm-input-send-next-key
- "<print>" (my/app-command "flameshot gui")
- "M-x" 'counsel-M-x
- "M-SPC" (general-key "SPC"))
-

Simulation keys.

-
(setq exwm-input-simulation-keys `((,(kbd "M-w") . ,(kbd "C-w"))
-				   (,(kbd "M-c") . ,(kbd "C-c"))))
-

And keybindings that are available in both char-mode and line-mode:

-
(setq exwm-input-global-keys
-      `(
-	;; Reset to line-mode
-	(,(kbd "s-R") . exwm-reset)
-
-	;; Switch windows
-	(,(kbd "s-<left>"). windmove-left)
-	(,(kbd "s-<right>") . windmove-right)
-	(,(kbd "s-<up>") . windmove-up)
-	(,(kbd "s-<down>") . windmove-down)
-
-	(,(kbd "s-h"). windmove-left)
-	(,(kbd "s-l") . windmove-right)
-	(,(kbd "s-k") . windmove-up)
-	(,(kbd "s-j") . windmove-down)
-
-	;; Moving windows
-	(,(kbd "s-H") . (lambda () (interactive) (my/exwm-move-window 'left)))
-	(,(kbd "s-L") . (lambda () (interactive) (my/exwm-move-window 'right)))
-	(,(kbd "s-K") . (lambda () (interactive) (my/exwm-move-window 'up)))
-	(,(kbd "s-J") . (lambda () (interactive) (my/exwm-move-window 'down)))
-
-	;; Fullscreen
-	(,(kbd "s-f") . exwm-layout-toggle-fullscreen)
-
-	;; Quit
-	(,(kbd "s-Q") . evil-quit)
-
-	;; Split windows
-	(,(kbd "s-s") . evil-window-vsplit)
-	(,(kbd "s-v") . evil-window-hsplit)
-
-	;; Switch perspectives
-	(,(kbd "s-,") . persp-prev)
-	(,(kbd "s-.") . persp-next)
-
-	;; Switch buffers
-	(,(kbd "s-e") . persp-ivy-switch-buffer)
-
-	;; Resize windows
-	(,(kbd "s-r") . my/exwm-resize-hydra/body)
-
-	;; Apps & stuff
-	(,(kbd "s-p") . ,(my/app-command "rofi -modi drun,run -show drun"))
-	(,(kbd "s-;") . my/exwm-apps)
-	(,(kbd "s--") . ,(my/app-command "rofi-pass"))
-	(,(kbd "s-=") . ,(my/app-command "rofimoji"))
-
-	;; Basic controls
-	(,(kbd "<XF86AudioRaiseVolume>") . ,(my/app-command "ponymix increase 5 --max-volume 150"))
-	(,(kbd "<XF86AudioLowerVolume>") . ,(my/app-command "ponymix decrease 5 --max-volume 150"))
-	(,(kbd "<XF86MonBrightnessUp>") . ,(my/app-command "light -A 5"))
-	(,(kbd "<XF86MonBrightnessDown>") . ,(my/app-command "light -U 5"))
-	(,(kbd "<XF86AudioMute>") . ,(my/app-command "ponymix toggle"))
-
-	(,(kbd "<XF86AudioPlay>") . ,(my/app-command "mpc toggle"))
-	(,(kbd "<XF86AudioPause>") . ,(my/app-command "mpc pause"))
-	(,(kbd "<print>") . ,(my/app-command "flameshot gui"))
-
-	;; Switch workspace
-	(,(kbd "s-q") . my/exwm-switch-to-other-monitor)
-	(,(kbd "s-w") . exwm-workspace-switch)
-	(,(kbd "s-W") . exwm-workspace-move-window)
-	(,(kbd "s-<tab>") . my/exwm-workspace-switch-monitor)
-
-	;; Cycle EXWM windows in the current perspective
-	(,(kbd "s-[") . (lambda () (interactive) (my/cycle-persp-exwm-buffers 'backward)))
-	(,(kbd "s-]") . (lambda () (interactive) (my/cycle-persp-exwm-buffers 'forward)))
-	(,(kbd "s-o") . ,(my/app-command "rofi -show window"))
-
-	;; 's-N': Switch to certain workspace with Super (Win) plus a number key (0 - 9)
-	,@(mapcar (lambda (i)
-		    `(,(kbd (format "s-%d" i)) .
-		      (lambda ()
-			(interactive)
-			(exwm-workspace-switch-create ,i))))
-		  (number-sequence 0 9))))
-

Pinentry

-

The GUI pinentry doesn’t work too well with EXWM because of issues with popup windows, so we will use the Emacs one.

-
(use-package pinentry
-  :straight t
-  :after (exwm)
-  :config
-  (setenv "GPG_AGENT_INFO" nil) ;; use emacs pinentry
-  (setq auth-source-debug t)
-
-  (setq epg-gpg-program "gpg2") ;; not necessary
-  (require 'epa-file)
-  (epa-file-enable)
-  (setq epa-pinentry-mode 'loopback)
-  (setq epg-pinentry-mode 'loopback)
-  (pinentry-start)
-  (my/run-in-background "gpgconf --reload gpg-agent"))
-
default-cache-ttl 3600
-max-cache-ttl 3600
-allow-emacs-pinentry
-allow-loopback-pinentry
-

Modeline

-

Show current workspace in the modeline.

-
(defvar my/exwm-mode-line-info "")
-
-(add-to-list 'mode-line-misc-info
-	     '(:eval my/exwm-mode-line-info))
-
-(defun my/exwm-mode-line-info-update ()
-  (setq my/exwm-mode-line-info
-	(concat
-	 "["
-	 (propertize (funcall exwm-workspace-index-map exwm-workspace-current-index)
-		     'face
-		     `(foreground-color . ,(doom-color 'yellow)))
-	 "]"))
-  (setq my/exwm-mode-line-info-no-props (funcall exwm-workspace-index-map exwm-workspace-current-index))
-  (force-mode-line-update))
-
-(add-hook 'exwm-workspace-switch-hook #'my/exwm-mode-line-info-update)
-

EXWM config

-

And the EXWM config itself.

-
(defun my/exwm-init ()
-  (exwm-workspace-switch 1)
-
-  (my/exwm-run-polybar)
-  (my/exwm-set-wallpaper)
-  (my/exwm-run-shepherd)
-  ;; (with-eval-after-load 'perspective
-  ;;   (my/exwm-setup-perspectives))
-  )
-
-(defun my/exwm-update-class ()
-  (exwm-workspace-rename-buffer (format "EXWM :: %s" exwm-class-name)))
-
-(use-package exwm
-  :straight t
-  :config
-  (setq exwm-workspace-number 5)
-  (add-hook 'exwm-init-hook #'my/exwm-init)
-  (add-hook 'exwm-update-class-hook #'my/exwm-update-class)
-
-  (require 'exwm-randr)
-  (exwm-randr-enable)
-  (start-process-shell-command "xrandr" nil "~/bin/scripts/screen-layout")
-  (when (string= (system-name) "indigo")
-    (setq my/exwm-another-monitor "DVI-D-0")
-    (setq exwm-randr-workspace-monitor-plist `(2 ,my/exwm-another-monitor 3 ,my/exwm-another-monitor)))
-
-  (setq exwm-workspace-warp-cursor t)
-  (setq mouse-autoselect-window t)
-  (setq focus-follows-mouse t)
-
-  <<exwm-monitor-config>>
-  <<exwm-keybindings>>
-  <<exwm-mode-line-config>>
-
-  (set-frame-parameter (selected-frame) 'alpha '(90 . 90))
-  (add-to-list 'default-frame-alist '(alpha . (90 . 90)))
-
-  (exwm-enable))
-

i3wm

- - - - - - - - - - - - - - - - - -
Guix dependencyDisabled
i3-gaps
i3locktrue
-

i3lock is disabled because the global one has to be used.

-

i3wm is a manual tiling window manager, which is currently my window manager of choice. I’ve tried several alternatives, including xmonad & EXWM, but i3 seems to fit my workflow best.

-

i3-gaps is an i3 fork with a few features like window gaps. I like to enable inner gaps when there is at least one container in a workspace.

-

References:

- -

General settings

-
set $mod Mod4
-font pango:monospace 10
-
-# Use Mouse+$mod to drag floating windows to their wanted position
-floating_modifier $mod
-
-# Move cursor between monitors
-mouse_warping output
-
-# Apply XFCE Settings
-# exec xfsettingsd
-# exec xiccd
-
-# Set screen layout
-exec ~/bin/scripts/screen-layout
-
-# Most needed keybindigs
-# reload the configuration file
-bindsym $mod+Shift+c reload
-
-# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
-bindsym $mod+Shift+r restart
-
-# exit i3 (logs you out of your X session)
-bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
-

Managing windows

- - - - - - - - - - - -
Guix dependency
rust-i3-switch-tabs
-

Some keybindings for managing windows.

-

emacs-i3-integration is a script to pass some command to Emacs to get a consistent set of keybindings in both i3 and Emacs. Check out the section in Emacs.org for details.

-

Kill focused windows

-
bindsym $mod+Shift+q exec emacs-i3-integration kill
-

Change focus

-
bindsym $mod+h exec emacs-i3-integration focus left
-bindsym $mod+j exec emacs-i3-integration focus down
-bindsym $mod+k exec emacs-i3-integration focus up
-bindsym $mod+l exec emacs-i3-integration focus right
-
-bindsym $mod+Left exec emacs-i3-integration focus left
-bindsym $mod+Down exec emacs-i3-integration focus down
-bindsym $mod+Up exec emacs-i3-integration focus up
-bindsym $mod+Right exec emacs-i3-integration focus right
-

Move windows around

-
bindsym $mod+Shift+h exec emacs-i3-integration move left
-bindsym $mod+Shift+j exec emacs-i3-integration move down
-bindsym $mod+Shift+k exec emacs-i3-integration move up
-bindsym $mod+Shift+l exec emacs-i3-integration move right
-
-bindsym $mod+Shift+Left exec emacs-i3-integration move left
-bindsym $mod+Shift+Down exec emacs-i3-integration move down
-bindsym $mod+Shift+Up exec emacs-i3-integration move up
-bindsym $mod+Shift+Right exec emacs-i3-integration move right
-

Split windows

-
bindsym $mod+s exec emacs-i3-integration split h
-bindsym $mod+v exec emacs-i3-integration split v
-

Switch tabs

-
bindsym $mod+period exec i3-switch-tabs right
-bindsym $mod+comma exec i3-switch-tabs left
-

Enter fullscreen mode

-
# enter fullscreen mode for the focused container
-bindsym $mod+f fullscreen toggle
-bindsym $mod+c fullscreen toggle global
-

Changing layout

-
bindsym $mod+w layout stacking
-bindsym $mod+t layout tabbed
-bindsym $mod+e exec emacs-i3-integration layout toggle split
-

Toggle tiling/floating, switch between tiled and floating windows

-
bindsym $mod+Shift+f floating toggle
-bindsym $mod+z focus mode_toggle
-

Switching outputs

-
bindsym $mod+Tab move workspace to output right
-bindsym $mod+q focus output right
-

Focus parent and child container

-
bindsym $mod+a focus parent
-bindsym $mod+Shift+A focus child
-

Toggle sticky

-
bindsym $mod+i sticky toggle
-

Set windows as floating and sticky, move to the top right.

-
bindsym $mod+x floating enable; sticky enable; move position 1220 0; resize set width 700 px
-

Workspaces

-
set $w1 "1 🚀"
-set $w2 "2 🌍"
-set $w3 "3 💬"
-set $w4 "4 🛠️️"
-set $w7 "7 🛰️"
-set $w8 "8 📝"
-set $w9 "9 🎵"
-set $w10 "10 📦"
-
-bindsym $mod+1 workspace $w1
-bindsym $mod+2 workspace $w2
-bindsym $mod+3 workspace $w3
-bindsym $mod+4 workspace $w4
-bindsym $mod+5 workspace 5
-bindsym $mod+6 workspace 6
-bindsym $mod+7 workspace $w7
-bindsym $mod+8 workspace $w8
-bindsym $mod+9 workspace $w9
-bindsym $mod+0 workspace $w10
-
-# move focused container to workspace
-bindsym $mod+Shift+1 move container to workspace $w1
-bindsym $mod+Shift+2 move container to workspace $w2
-bindsym $mod+Shift+3 move container to workspace $w3
-bindsym $mod+Shift+4 move container to workspace $w4
-bindsym $mod+Shift+5 move container to workspace 5
-bindsym $mod+Shift+6 move container to workspace 6
-bindsym $mod+Shift+7 move container to workspace $w7
-bindsym $mod+Shift+8 move container to workspace $w8
-bindsym $mod+Shift+9 move container to workspace $w9
-bindsym $mod+Shift+0 move container to workspace $w10
-

Rules

-

Rules to automatically assign applications to workspaces and do other stuff, like enable floating.

-

Most apps can be distinguished by a WM class (you can get one with xprop), but in some cases it doesn’t work, e.g. for terminal applications. In that case rules can be based on a window title, for instance.

-

However, watch out for the following: rule such as for_window [title="ncmpcpp.*"] move to workspace $w9 will move any window with a title starting with ncmpcpp to workspace $w9. For instance, it moves your browser when you google “ncmpcpp”.

-
assign [class="Emacs"] $w1
-assign [class="qutebrowser"] $w2
-assign [class="firefox"] $w2
-assign [class="VK"] $w3
-assign [class="Slack"] $w3
-assign [class="discord"] $w3
-assign [class="TelegramDesktop"] $w3
-assign [class="Postman"] $w4
-assign [class="Chromium-browse"] $w4
-assign [class="chromium"] $w4
-assign [class="google-chrome"] $w4
-assign [title="Vue Developer Tools"] $w4
-assign [class="Google Play Music Desktop Player"] $w9
-assign [class="jetbrains-datagrip"] $w4
-assign [class="zoom"] $w7
-assign [class="skype"] $w7
-assign [class="Mailspring"] $w8
-assign [class="Thunderbird"] $w8
-assign [class="Joplin"] $w8
-assign [class="keepassxc"] $w10
-
-for_window [title="VirtScreen"] floating enable
-
-for_window [title="ncmpcpp.*"] move to workspace $w9
-for_window [title="newsboat.*"] move to workspace $w9
-for_window [title=".*run_wego"] move to workspace $w9
-for_window [class="cinnamon-settings*"] floating enable
-for_window [title="Picture-in-Picture"] sticky enable
-for_window [window_role="GtkFileChooserDialog"] resize set width 1000 px height 800 px
-for_window [window_role="GtkFileChooserDialog"] move position center
-

Scratchpad

-

Scratch terminal, inspired by this Luke Smith’s video.

-

Launch script

-

First of all, we have to distinguish a scratchpad terminal from a normal one. To do that, one can create st with a required classname.

-

Then, it would be cool not to duplicate scratchpads, so the following script first looks for a window with a created classname. If it exists, the script just toggles the scratchpad visibility. Otherwise, a new instance of a window is created.

-
CLASSNAME="dropdown_tmux"
-COMMAND="alacritty --class $CLASSNAME -e tmux new-session -s $CLASSNAME"
-pid=$(xdotool search --classname "dropdown_tmux")
-if [[ ! -z $pid  ]]; then
-    i3-msg scratchpad show
-else
-    setsid -f ${COMMAND}
-fi
-

i3 config

-
# Scratchpad
-for_window [instance="dropdown_*"] floating enable
-for_window [instance="dropdown_*"] move scratchpad
-for_window [instance="dropdown_*"] sticky enable
-for_window [instance="dropdown_*"] scratchpad show
-for_window [instance="dropdown_*"] move position center
-
-bindsym $mod+u exec ~/bin/scripts/dropdown
-

Gaps & borders

-

The main reason to use i3-gaps

-
# Borders
-# for_window [class=".*"] border pixel 0
-default_border pixel 3
-hide_edge_borders both
-
-# Gaps
-set $default_inner 10
-set $default_outer 0
-
-gaps inner $default_inner
-gaps outer $default_outer
-
-smart_gaps on
-

Keybindings

-
mode "inner gaps" {
-    bindsym plus gaps inner current plus 5
-    bindsym minus gaps inner current minus 5
-    bindsym Shift+plus gaps inner all plus 5
-    bindsym Shift+minus gaps inner all minus 5
-    bindsym 0 gaps inner current set 0
-    bindsym Shift+0 gaps inner all set 0
-
-    bindsym r gaps inner current set $default_inner
-    bindsym Shift+r gaps inner all set $default_inner
-
-    bindsym Return mode "default"
-    bindsym Escape mode "default"
-}
-
-mode "outer gaps" {
-    bindsym plus gaps outer current plus 5
-    bindsym minus gaps outer current minus 5
-    bindsym Shift+plus gaps outer all plus 5
-    bindsym Shift+minus gaps outer all minus 5
-    bindsym 0 gaps outer current set 0
-    bindsym Shift+0 gaps outer all set 0
-
-    bindsym r gaps outer current set $default_outer
-    bindsym Shift+r gaps outer all set $default_outer
-
-    bindsym Return mode "default"
-    bindsym Escape mode "default"
-}
-
-bindsym $mod+g mode "inner gaps"
-bindsym $mod+Shift+g mode "outer gaps"
-

Move & resize windows

- - - - - - - - - - - -
Guix dependency
python-i3-balance-workspace
-

A more or less standard set of keybindings to move & resize floating windows. Just be careful to always make a way to return from these new modes, otherwise you’d end up in a rather precarious situation.

-

i3-balance-workspace is a small Python package to balance the i3 windows, but for the Emacs integration I also want this button to balance the Emacs windows, so here is a small script to do just that.

-
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
-    emacsclient -e "(balance-windows)" &
-fi
-i3_balance_workspace
-
mode "resize" {
-
-    bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt
-    bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt
-    bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt
-    bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt
-
-    bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt
-    bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt
-    bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt
-    bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt
-
-    # same bindings, but for the arrow keys
-    bindsym Left  exec emacs-i3-integration resize shrink width 10 px or 10 ppt
-    bindsym Down  exec emacs-i3-integration resize grow height 10 px or 10 ppt
-    bindsym Up    exec emacs-i3-integration resize shrink height 10 px or 10 ppt
-    bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt
-
-    bindsym Shift+Left  exec emacs-i3-integration resize shrink width 100 px or 100 ppt
-    bindsym Shift+Down  exec emacs-i3-integration resize grow height 100 px or 100 ppt
-    bindsym Shift+Up    exec emacs-i3-integration resize shrink height 100 px or 100 ppt
-    bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt
-
-    bindsym equal exec i3-emacs-balance-windows
-
-    # back to normal: Enter or Escape
-    bindsym Return mode "default"
-    bindsym Escape mode "default"
-}
-
-bindsym $mod+r mode "resize"
-
-mode "move" {
-    bindsym $mod+Tab focus right
-
-    bindsym Left  move left
-    bindsym Down  move down
-    bindsym Up    move up
-    bindsym Right move right
-
-    bindsym h     move left
-    bindsym j     move down
-    bindsym k     move up
-    bindsym l     move right
-
-    # back to normal: Enter or Escape
-    bindsym Return mode "default"
-    bindsym Escape mode "default"
-}
-
-bindsym $mod+m mode "move" focus floating
-

OFF (OFF) Intergration with dmenu

-

dmenu is a dynamic menu program for X. I’ve opted out of using it in favour of rofi, but here is a relevant bit of config.

-

Scripts are located in the bin/scripts folder.

-
# dmenu
-bindsym $mod+d exec i3-dmenu-desktop --dmenu="dmenu -l 10"
-bindsym $mod+apostrophe mode "dmenu"
-
-mode "dmenu" {
-    bindsym d exec i3-dmenu-desktop --dmenu="dmenu -l 10"; mode default
-    bindsym p exec dmenu_run -l 10; mode default
-    bindsym m exec dmenu-man; mode default
-    bindsym b exec dmenu-buku; mode default
-    bindsym f exec dmenu-explore; mode default
-    bindsym t exec dmenu-tmuxp; mode default
-    bindsym Escape mode "default"
-}
-
-bindsym $mod+b exec --no-startup-id dmenu-buku
-

Integration with rofi

-

Keybindings to launch rofi. For more detail, look the Rofi section.

-
bindsym $mod+d exec "rofi -modi 'drun,run' -show drun"
-bindsym $mod+b exec --no-startup-id rofi-buku-mine
-bindsym $mod+minus exec rofi-pass
-bindsym $mod+equal exec rofimoji
-
-bindsym $mod+apostrophe mode "rofi"
-
-mode "rofi" {
-    bindsym d exec "rofi -modi 'drun,run' -show drun"
-    bindsym m exec rofi-man; mode default
-    bindsym b exec rofi-buku-mine; mode default
-    bindsym k exec rofi-pass; mode default
-    bindsym Escape mode "default"
-}
-

Launching apps & misc keybindings

-

I prefer to use a separate mode to launch most of my apps, with some exceptions.

-

Apps

-
# Launch apps
-# start a terminal at workspace 1
-bindsym $mod+Return exec "i3-msg 'workspace 1 🚀; exec alacritty'"
-
-bindsym $mod+p exec "copyq menu"
-bindsym $mod+Shift+x exec "i3lock -f -i /home/pavel/Pictures/lock-wallpaper.png"
-
-bindsym $mod+semicolon mode "apps"
-
-mode "apps" {
-    bindsym Escape mode "default"
-    bindsym b exec firefox; mode default
-    bindsym v exec vk; mode default
-    bindsym s exec slack-wrapper; mode default;
-    bindsym d exec "flatpak run com.discordapp.Discord"; mode default;
-    bindsym m exec "alacritty -e ncmpcpp"; mode default
-    bindsym c exec "copyq toggle"; mode default
-    bindsym k exec "keepassxc"; mode default
-    # bindsym e exec mailspring; mode default
-    bindsym a exec emacs; mode default
-    bindsym n exec "alacritty -e newsboat"; mode default
-    bindsym w exec "alacritty /home/pavel/bin/scripts/run_wego"; mode default
-    # bindsym a exec emacsclient -c; mode default
-    # bindsym Shift+a exec emacs; mode default
-}
-

Media controls & brightness

-
# Pulse Audio controls
-bindsym XF86AudioRaiseVolume exec --no-startup-id "ponymix increase 5 --max-volume 150"
-bindsym XF86AudioLowerVolume exec --no-startup-id "ponymix decrease 5 --max-volume 150"
-bindsym XF86AudioMute exec --no-startup-id "ponymix toggle"
-
-exec --no-startup-id xmodmap -e 'keycode 135 = Super_R' && xset -r 135
-bindsym $mod+F2 exec --no-startup-id "ponymix increase 5"
-bindsym $mod+F3 exec --no-startup-id "ponymix decrease 5"
-
-# Media player controls
-bindsym XF86AudioPlay exec mpc toggle
-bindsym XF86AudioPause exec mpc pause
-bindsym XF86AudioNext exec mpc next
-bindsym XF86AudioPrev exec mpc prev
-
-# Screen brightness
-bindsym XF86MonBrightnessUp exec light -A 5
-bindsym XF86MonBrightnessDown exec light -U 5
-

Screenshots

-
# Screenshots
-bindsym --release Print exec "flameshot gui"
-bindsym --release Shift+Print exec "xfce4-screenshooter"
-

Colors

-

Application of the XResources theme to the WM.

-
exec xrdb -merge $HOME/.Xresources
-
-# Colors
-set_from_resource $bg-color            background
-set_from_resource $active-color        color4
-set_from_resource $inactive-bg-color   color8
-set_from_resource $text-color          foreground
-set_from_resource $inactive-text-color color7
-set_from_resource $urgent-bg-color     color1
-set_from_resource $urgent-text-color   color0
-
-# window colors
-#                       border              background         text                 indicator       child border
-client.focused          $active-color       $bg-color          $text-color          $bg-color       $active-color
-client.unfocused        $bg-color           $inactive-bg-color $inactive-text-color $bg-color       $bg-color
-client.focused_inactive $active-color       $inactive-bg-color $inactive-text-color $bg-color       $bg-color
-client.urgent           $urgent-bg-color    $urgent-bg-color   $urgent-text-color   $bg-color       $urgent-bg-color
-

OFF (OFF) i3blocks

-

I’ve opted out of i3bar & i3blocks for polybar

-
bar {
-    status_command i3blocks -c ~/.config/i3/i3blocks.conf
-    i3bar_command i3bar
-    font pango:monospace 12
-    output HDMI-A-0
-    tray_output none
-    colors {
-	background $bg-color
-	separator #757575
-	#                  border             background         text
-	focused_workspace  $bg-color          $bg-color          $text-color
-	inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color
-	urgent_workspace   $urgent-bg-color   $urgent-bg-color   $urgent-text-color
-    }
-}
-
-bar {
-    status_command i3blocks -c ~/.config/i3/i3blocks.conf
-    i3bar_command i3bar
-    font pango:monospace 10
-    output DVI-D-0
-    colors {
-	background $bg-color
-	separator #757575
-	#                  border             background         text
-	focused_workspace  $bg-color          $bg-color          $text-color
-	inactive_workspace $inactive-bg-color $inactive-bg-color $inactive-text-color
-	urgent_workspace   $urgent-bg-color   $urgent-bg-color   $urgent-text-color
-    }
-}
-

Keyboard Layout

-

A script to set Russian-English keyboard layout:

-
setxkbmap -layout us,ru
-setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle'
-

A script to toggle the layout

-
if setxkbmap -query | grep -q us,ru; then
-    setxkbmap -layout us
-    setxkbmap -option
-else
-    setxkbmap -layout us,ru
-    setxkbmap -model pc105 -option 'grp:win_space_toggle' -option 'grp:alt_shift_toggle'
-fi
-

And the relevant i3 settings:

-
# Layout
-exec_always --no-startup-id set_layout
-bindsym $mod+slash exec toggle_layout
-

Autostart

-
# Polybar
-exec_always --no-startup-id "bash /home/pavel/bin/polybar.sh"
-
-# Wallpaper
-exec_always "feh --bg-fill ~/Pictures/wallpaper.jpg"
-
-# Picom
-exec picom
-
-# Keynav
-exec keynav
-
-# Applets
-exec --no-startup-id nm-applet
-# exec --no-startup-id /usr/bin/blueman-applet
-
-exec shepherd
-exec dunst
-exec copyq
-exec "xmodmap ~/.Xmodmap"
-# exec "xrdb -merge ~/.Xresources"
-# exec "bash ~/bin/autostart.sh"
-

Polybar

- - - - - - - - - - - - - - - -
CategoryGuix dependencyDescription
desktop-polybarpolybarstatusbar
-

Polybar is a nice-looking, WM-agnostic statusbar program.

-

I switched to polybar because I wanted to try out some WMs other than i3, but decided to stick with i3 for now.

-

Don’t forget to install the Google Noto Color Emoji font. Guix package with all Noto fonts is way too large.

-

References:

- -

General settings

-

Colors

-

First, let’s use xrdb colors in polybar. To avoid code duplication, I generate them via noweb.

-

-
(mapconcat
-  (lambda (elem)
-    (format "%s = ${xrdb:%s}" (nth 0 elem) (nth 1 elem)))
-  (seq-filter
-   (lambda (elem) (nth 1 elem))
-   table)
-   "\n")
-
[colors]
-<<get-polybar-colors()>>
-
-background = ${xrdb:background}
-; foreground = ${xrdb:foreground}
-

Glyphs

-

Also, let’s try to use some glyphs. The polybar-themes repository can give some inspiration on what is possible, here I am replicating a powerline-ish look.

-

Although polybar makes it a bit more awkward than it could’ve been. The approach is to put a glyph between two blocks like this:

-
block1  block2
-

And set the colors like that:

- - - - - - - - - - - - - - - - - - - - - - - -
block1glyphblock 2
foregroundF1B2F2
backgroundB1B1B2
-

So, let’s define the glyph symbols:

-
[glyph]
-gleft = 
-gright = 
-

Modules

-

To make life a bit easier, I’ll define a single source of truth for modules and their colors here.

-

So, here is a table with all modules.

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IndexModuleColorGlyph
1pulseaudiolight-magenta+
2mpdmagenta+
3cpucyan+
4ram-memorylight-green+
5swap-memorygreen+
6networklight-red+
7openvpnlight-red
8xkeyboardred+
9batterygreen+
10weatherlight-yellow+
12sunyellow+
13aw-afklight-blue+
14dateblue+
-

Some functions to use colors in the individual modules:

-

-
(format
- "${colors.%s}"
- (nth
-  2
-  (seq-find
-   (lambda (el) (string-equal (nth 1 el) module))
-   table)))
-

-
"${colors.black}"
-

Also, I want to exclude some modules from certain monitors and machines. For now this concerns just the battery module, so I exclude it from the monitors of my desktop PC. In future I may need to rework this to include hostname, but as long as all my machines have separate monitor names, it works fine.

-

- - - - - - - - - - - - - - - - - -
MonitorExclude
DVI-D-0battery
HDMI-A-0battery
-

Now, we need to generate a set of glyphs. The code below generates all the required glyhps so that every combination of neighoring colors in the bar had one.

-

- - - - - - - - - - - - - - - - - - - - - -
Color 1Color 2
backgroundwhite
backgroundlight-magenta
bluebackground
-

-
(let* ((monitors
-	(thread-last
-	  exclude-table
-	  (seq-map (lambda (el) (nth 0 el)))
-	  (seq-uniq)))
-       (exclude-combinations
-	(seq-map
-	 (lambda (monitor)
-	   (seq-map
-	    (lambda (el) (nth 1 el))
-	    (seq-filter
-	     (lambda (el) (and (string-equal (nth 0 el) monitor)
-			       (nth 1 el)))
-	     exclude-table)))
-	 `(,@monitors "")))
-       (module-glyph-combinations
-	(thread-last
-	  exclude-combinations
-	  (seq-map
-	   (lambda (exclude)
-	     (thread-last
-	       table
-	       (seq-filter
-		(lambda (elt)
-		  (not (or
-			(member (nth 1 elt) exclude)
-			(not (string-equal (nth 3 elt) "+"))))))
-	       ;; (seq-map (lambda (elt) (nth 1 elt)))
-	       )))
-	  (seq-uniq)))
-       (color-changes nil))
-  (dolist (e extra)
-    (add-to-list
-     'color-changes
-     (concat (nth 0 e) "--" (nth 1 e))))
-  (dolist (comb module-glyph-combinations)
-    (dotimes (i (1- (length comb)))
-      (add-to-list
-       'color-changes
-       (concat (nth 2 (nth i comb))
-	       "--"
-	       (nth 2 (nth (1+ i) comb))))))
-  (mapconcat
-   (lambda (el)
-     (let ((colors (split-string el "--")))
-       (format "
-[module/glyph-%s--%s]
-type = custom/text
-content-background = ${colors.%s}
-content-foreground = ${colors.%s}
-content = ${glyph.gright}
-content-font = 5"
-	       (nth 0 colors)
-	       (nth 1 colors)
-	       (nth 0 colors)
-	       (nth 1 colors))))
-   color-changes
-   "\n"))
-
<<polybar-generate-glyphs()>>
-

And a set of modules interweaved with corresponding glyphs for each monitor:

-

-
(let* ((exclude-modules
-	(thread-last
-	  exclude-table
-	  (seq-filter (lambda (el) (string-equal (nth 0 el) monitor)))
-	  (seq-map (lambda (el) (nth 1 el)))))
-       (modules
-	(thread-last
-	  table
-	  (seq-filter (lambda (el) (not (member (nth 1 el) exclude-modules))))))
-       (prev-color first-color)
-       (ret nil))
-  (concat
-   (mapconcat
-    (lambda (el)
-      (apply
-       #'concat
-       (list
-	(when (string-equal (nth 3 el) "+")
-	  (setq ret (format "glyph-%s--%s " prev-color (nth 2 el)))
-	  (setq prev-color (nth 2 el))
-	  ret)
-	(nth 1 el))))
-    modules
-    " ")
-   (unless (string-empty-p last-color) (format " glyph-%s--%s " prev-color last-color))))
-

Global bar config

-

Global bar configuration.

-

Monitor config and base colors.

-
[bar/mybar]
-monitor = ${env:MONITOR:}
-width = 100%
-height = ${env:HEIGHT:27}
-fixed-center = false
-bottom=true
-
-background = ${colors.background}
-foreground = ${colors.black}
-

Some geometry settings. These are set this way to make glyphs look the way they should

-
; line-size = 3
-line-color = #f00
-
-padding = 0
-
-module-margin-left = 0
-module-margin-right = 0
-margin-bottom = 0
-margin-top = 0
-
-; underline-size = 0
-border-size = 0
-
-offset-x = 0
-offset-y = 0
-radius = 0.0
-

Fonts

-
; font-0 = ${env:FONT0:pango:monospace:size=10;1}
-; font-1 = ${env:FONT1:NotoEmoji:scale=10:antialias=false;0}
-; font-2 = ${env:FONT2:fontawesome:pixelsize=10;1}
-; font-3 = ${env:FONT3:JetBrains Mono Nerd Font:monospace:size=10;1}
-
-font-0 = pango:monospace:size=13;2
-font-1 = NotoEmoji:scale=10:antialias=false;1
-font-2 = fontawesome:pixelsize=13;3
-font-3 = JetBrains Mono Nerd Font:monospace:size=13;4
-font-4 = JetBrains Mono Nerd Font:monospace:size=17;4
-

Modules. Because I sometimes set up different blocks on different monitors, they are set via environment variables.

-
; modules-left = i3
-; modules-center = test
-modules-right = ${env:RIGHT_BLOCKS}
-
-tray-position = ${env:TRAY:right}
-tray-padding = 0
-tray-maxsize = 16
-tray-background = ${colors.background}
-
-wm-restack = i3
-; override-redirect = true
-
-scroll-up = i3wm-wsnext
-scroll-down = i3wm-wsprev
-
-; cursor-click = pointer
-; cursor-scroll = ns-resize
-

Misc settings.

-
[settings]
-screenchange-reload = true
-compositing-background = source
-compositing-foreground = over
-compositing-overline = over
-compositing-underline = over
-compositing-border = over
-
-[global/wm]
-margin-top = 0
-margin-bottom = 0
-

Launch script

-

The script below allows me to:

- - -
hostname=$(hostname)
-# Settings varying on the hostname
-export WLAN_INTERFACE=$(nmcli -f DEVICE con show | grep -Ev "(.*docker.*|DEVICE|br.*|tun.*|veth.*|--)" | xargs)
-if [ "$hostname" = "azure" ]; then
-    TRAY_MONITOR="eDP-1"
-    # export WLAN_INTERFACE="wlp3s0"
-elif [ "$hostname" = "eminence" ]; then
-    TRAY_MONITOR="eDP"
-    # export WLAN_INTERFACE="wlo1"
-else
-    TRAY_MONITOR="HDMI-A-0"
-    # export WLAN_INTERFACE="wlp35s0f3u2"
-fi
-
-# Setting varying on the monitor
-declare -A FONT_SIZES=(
-    ["eDP"]="13"
-    ["eDP-1"]="13"
-    ["DVI-D-0"]="13"
-    ["HDMI-A-0"]="13"
-)
-declare -A EMOJI_SCALE=(
-    ["eDP"]="9"
-    ["eDP-1"]="9"
-    ["DVI-D-0"]="10"
-    ["HDMI-A-0"]="10"
-)
-declare -A BAR_HEIGHT=(
-    ["eDP"]="29"
-    ["eDP-1"]="29"
-    ["DVI-D-0"]="29"
-    ["HDMI-A-0"]="29"
-)
-declare -A BLOCKS=(
-    ["eDP"]="<<polybar-generate-modules(monitor="eDP")>>"
-    ["eDP-1"]="<<polybar-generate-modules(monitor="eDP-1")>>"
-    ["DVI-D-0"]="<<polybar-generate-modules(monitor="DVI-D-0")>>"
-    ["HDMI-A-0"]="<<polybar-generate-modules(monitor="HDMI-A-0")>>"
-)
-
-# Geolocation for some modules
-export LOC="SPB"
-
-# export IPSTACK_API_KEY=$(pass show My_Online/APIs/ipstack | head -n 1)
-
-pkill polybar
-for m in $(xrandr --query | grep " connected" | cut -d" " -f1); do
-    export MONITOR=$m
-    if [ "$MONITOR" = "$TRAY_MONITOR" ]; then
-	export TRAY="right"
-    else
-	export TRAY="none"
-    fi
-    SIZE=${FONT_SIZES[$MONITOR]}
-    SCALE=${EMOJI_SCALE[$MONITOR]}
-    if [[ -z "$SCALE" ]]; then
-	continue
-    fi
-    # export FONT0="pango:monospace:size=$SIZE;1"
-    # export FONT1="NotoEmoji:scale=$SCALE:antialias=false;1"
-    # export FONT2="fontawesome:pixelsize=$SIZE;1"
-    # export FONT3="JetBrains Mono Nerd Font:monospace:size=15;1"
-    export HEIGHT=${BAR_HEIGHT[$MONITOR]}
-    export RIGHT_BLOCKS=${BLOCKS[$MONITOR]}
-    polybar mybar &
-done
-

Individual modules

-

Some of the custom modules below use Org mode noweb to evaluate colors, because it’s faster than querying xrdb at runtime. I wish I could reference polybar values there, but it looks like this is impossible.

-

If you want to copy something, you can go to the bin/polybar folder.

-

pulseaudio

-

PulseAudio status

-
[module/pulseaudio]
-type = internal/pulseaudio
-use-ui-max = true
-
-bar-volume-width = 7
-; bar-volume-foreground-0 = ${colors.white}
-; bar-volume-foreground-1 = ${colors.yellow}
-; bar-volume-foreground-2 = ${colors.yellow}
-; bar-volume-foreground-3 = ${colors.blue}
-; bar-volume-foreground-4 = ${colors.blue}
-; bar-volume-foreground-5 = ${colors.green}
-; bar-volume-foreground-6 = ${colors.green}
-bar-volume-gradient = false
-bar-volume-indicator = |
-bar-volume-indicator-font = 2
-bar-volume-fill = 
-bar-volume-fill-font = 2
-bar-volume-empty = 
-bar-volume-empty-font = 2
-; bar-volume-empty-foreground = ${colors.light-white}
-
-format-volume = ♪ <ramp-volume> <label-volume>
-label-volume = %percentage%%
-
-ramp-volume-0 = 
-ramp-volume-1 = 
-ramp-volume-2 = 
-ramp-volume-3 = 
-ramp-volume-4 = 
-ramp-volume-5 = 
-ramp-volume-6 = 
-ramp-volume-7 = 
-
-format-muted = ♪ <label-muted>
-label-muted = MUTE
-
-format-volume-background = <<get-polybar-bg(module="pulseaudio")>>
-format-muted-background = <<get-polybar-bg(module="pulseaudio")>>
-
-; format-volume-underline = ${colors.white}
-; format-muted-underline = ${colors.light-black}
-

mpd

-

Music Player Daemon status

-
[module/mpd]
-type = internal/mpd
-
-format-playing = <toggle> <label-time> <label-song>
-format-paused = <toggle> <label-time> <label-song>
-format-stopped = " "
-label-song = [%album-artist%] %title%
-label-time = %elapsed%/%total%
-
-label-song-maxlen = 30
-label-song-ellipsis = true
-
-; format-playing-underline = ${colors.yellow}
-; format-paused-underline = ${colors.yellow}
-; format-stopped-underline = ${colors.yellow}
-
-format-playing-background = <<get-polybar-bg(module="mpd")>>
-format-paused-background = <<get-polybar-bg(module="mpd")>>
-format-stopped-background = <<get-polybar-bg(module="mpd")>>
-
-label-separator = 0
-separator-foreground = ${colors.red}
-
-icon-pause = 
-icon-play = 
-icon-stop = 
-icon-prev = 1
-icon-next = 2
-

cpu

-

CPU usage

-
[module/cpu]
-type = internal/cpu
-format = " <label>"
-label = %percentage%%
-format-background = <<get-polybar-bg(module="cpu")>>
-

ram-memory

-

RAM usage

-
[module/ram-memory]
-type = internal/memory
-interval = 10
-
-ramp-used-0 = 
-ramp-used-1 = 
-ramp-used-2 = 
-ramp-used-3 = 
-ramp-used-4 = 
-ramp-used-5 = 
-ramp-used-6 = 
-ramp-used-7 = 
-
-format =  <label>
-label=%gb_used:.1f%
-
-; format-underline = ${colors.blue}
-format-background = <<get-polybar-bg(module="ram-memory")>>
-

swap-memory

-

Swap usage

-
[module/swap-memory]
-type = internal/memory
-interval = 10
-
-label= %gb_swap_used:.1f%
-format-background = <<get-polybar-bg(module="swap-memory")>>
-

network

-

Upload/download speed

-
[module/network]
-type = internal/network
-interval = 1
-
-interface = ${env:WLAN_INTERFACE}
-
-; format-connected = [<ramp-signal>] <label-connected>
-
-label-connected = ↓ %downspeed% ↑ %upspeed%
-label-disconnected = X
-
-; format-connected-underline = ${colors.green}
-; format-disconnected-underline = ${colors.red}
-format-connected-background = <<get-polybar-bg(module="network")>>
-format-disconnected-background = <<get-polybar-bg(module="network")>>
-
-ramp-signal-0 = 0
-ramp-signal-1 = 1
-ramp-signal-2 = 2
-ramp-signal-3 = 3
-ramp-signal-4 = 4
-ramp-signal-5 = 5
-

ipstack-vpn

- - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependencyDescription
desktop-polybarbind:utilsProvides dig
desktop-polybarcurl
desktop-polybarjqutil to work with JSONs
-

A module to get a country of the current IP and openvpn status. Uses ipstack API.

-
ip=$(dig +short +timeout=1 myip.opendns.com @resolver1.opendns.com 2> /dev/null)
-# API_KEY="$(pass show My_Online/APIs/ipstack | head -n 1)"
-API_KEY=$IPSTACK_API_KEY
-if [[ -z $ip || $ip == *"timed out"* ]]; then
-    echo "%{u<<get-color(name="red")>>}%{+u} ?? %{u-}"
-    exit
-fi
-ip_info=$(curl -s http://api.ipstack.com/${ip}?access_key=${API_KEY})
-# emoji=$(echo $ip_info | jq -r '.location.country_flag_emoji')
-code=$(echo $ip_info | jq -r '.country_code' 2> /dev/null)
-vpn=$(pgrep -a openvpn$ | head -n 1 | awk '{print $NF }' | cut -d '.' -f 1)
-
-if [[ -z $code ]]; then
-    code="??"
-fi
-
-if [ -n "$vpn" ]; then
-    echo "%{u<<get-color(name="blue")>>}%{+u}  $code %{u-}"
-else
-    echo "%{u<<get-color(name="red")>>}%{+u}  $code %{u-}"
-fi
-
[module/ipstack-vpn]
-type = custom/script
-exec = /home/pavel/bin/polybar/ipstack-vpn.sh
-interval = 1200
-

openvpn

-

A module to check if openvpn is running.

-
vpn=$(pgrep -a openvpn$ | head -n 1 | awk '{print $NF }' | cut -d '.' -f 1)
-if [ -n "$vpn" ]; then
-    echo "  "
-else
-    echo "  "
-fi
-
[module/openvpn]
-type = custom/script
-exec = /home/pavel/bin/polybar/openvpn.sh
-format-background = <<get-polybar-bg(module="openvpn")>>
-interval = 1200
-

xkeyboard

-

Current keyboard layout

-
[module/xkeyboard]
-type = internal/xkeyboard
-format = <label-layout>
-
-; format-underline = ${colors.magenta}
-format-background = <<get-polybar-bg(module="xkeyboard")>>
-label-layout = %icon%
-layout-icon-0 = ru;RU
-layout-icon-1 = us;US
-

battery

-
[module/battery]
-type = internal/battery
-battery = BAT0
-adapter = ADP0
-
-time-format = %H:%M
-format-discharging = <ramp-capacity> <label-discharging>
-format-discharging-background = <<get-polybar-bg(module="battery")>>
-format-charging-background = <<get-polybar-bg(module="battery")>>
-format-full-background = <<get-polybar-bg(module="battery")>>
-label-discharging = %percentage%% %time%
-label-charging =  %percentage%% %time%
-
-ramp-capacity-0 = 
-ramp-capacity-1 = 
-ramp-capacity-2 = 
-ramp-capacity-3 = 
-ramp-capacity-4 = 
-

weather

-

Gets current weather from wttr.in

-
bar_format="${BAR_FORMAT:-"%t"}"
-location="${LOCATION:-"Saint-Petersburg"}"
-format_1=${FORMAT_1:-"qF"}
-format_2=${FORMAT_1:-"format=v2n"}
-
-bar_weather=$(curl -s wttr.in/${location}?format=${bar_format} || echo "??")
-if [ -z "$bar_weather" ]; then
-    exit 1
-elif [[ "$bar_weather" == *"Unknown"* || "$bar_weather" == *"Sorry"* || "$bar_weather" == *"Bad Gateway"* ]]; then
-    echo "??"
-    exit 1
-else
-    echo ${bar_weather}
-fi
-
[module/weather]
-type = custom/script
-exec = /home/pavel/bin/polybar/weather.sh
-; format-underline = ${colors.red}
-format-background = <<get-polybar-bg(module="weather")>>
-interval = 1200
-

sun

- - - - - - - - - - - - - -
CategoryGuix dependency
desktop-polybarsunwait
-

Prints out the time of sunrise/sunset. Uses sunwait

-
declare -A LAT_DATA=(
-    ["TMN"]="57.15N"
-    ["SPB"]="59.9375N"
-)
-declare -A LON_DATA=(
-    ["TMN"]="65.533333E"
-    ["SPB"]="30.308611E"
-)
-if [ -z "$LOC" ]; then
-    echo "LOC?"
-    exit -1
-fi
-LAT=${LAT_DATA[$LOC]}
-LON=${LON_DATA[$LOC]}
-
-time=$(sunwait poll daylight rise ${LAT} $LON)
-
-if [[ ${time} == 'DAY' ]]; then
-    sunset=$(sunwait list daylight set ${LAT} ${LON})
-    # echo "%{u<<get-color(name="yellow")>>}%{+u} $sunset %{u-}"
-    echo $sunset
-else
-    sunrise=$(sunwait list daylight rise ${LAT} ${LON})
-    # echo "%{u<<get-color(name="red")>>}%{+u} $sunrise %{u-}"
-    echo $sunrise
-fi
-
[module/sun]
-type = custom/script
-exec = /home/pavel/bin/polybar/sun.sh
-format-background = <<get-polybar-bg(module="sun")>>
-interval = 60
-

aw-afk

-

Prints out a current uptime and non-AFK time from ActivityWatch server

- - - - - - - - - - - - - -
CategoryGuix dependency
desktop-polybardateutils
-
afk_event=$(curl -s -X GET "http://localhost:5600/api/0/buckets/aw-watcher-afk_$(hostname)/events?limit=1" -H "accept: application/json")
-status=$(echo ${afk_event} | jq -r '.[0].data.status')
-afk_time=$(echo "${afk_event}" | jq -r '.[0].duration' | xargs -I !  date -u -d @! +"%H:%M")
-
-uptime=$(uptime | awk '{ print substr($3, 0, length($3) - 1) }' | xargs -I ! date -d ! +"%H:%M")
-res="${afk_time} / ${uptime}"
-if [[ $status == 'afk' ]]; then
-    # echo "%{u<<get-color(name="red")>>}%{+u} [AFK] $res %{u-}"
-    echo "[AFK] $res"
-else
-    # echo "%{u<<get-color(name="blue")>>}%{+u} $res %{u-}"
-    echo "$res"
-fi
-
[module/aw-afk]
-type = custom/script
-exec = /home/pavel/bin/polybar/aw_afk.sh
-interval = 60
-format-background = <<get-polybar-bg(module="aw-afk")>>
-

date

-

Current date

-
[module/date]
-type = internal/date
-interval = 5
-
-date =
-date-alt = "%Y-%m-%d"
-
-time = %H:%M
-time-alt = %H:%M:%S
-
-format-background = <<get-polybar-bg(module="date")>>
-label = "%date% %time%"
-

pomm

-

Pomodoro module.

-
if ps -e | grep emacs >> /dev/null; then
-    emacsclient --eval "(if (boundp 'pomm-current-mode-line-string) pomm-current-mode-line-string \"\") " | xargs echo -e
-fi
-
[module/pomm]
-type = custom/script
-exec = /home/pavel/bin/polybar/pomm.sh
-interval = 1
-format-underline = ${colors.light-green}
-

SEP

-

A simple separator

-
[module/SEP]
-type = custom/text
-content = "|"
-content-foreground = ${colors.magenta}
-content-padding = 0
-content-margin = 0
-interval = 100000
-

TSEP

-

A separator, which appears only if monitor is set to have a tray in the launch script

-
if [ ! -z "$TRAY" ] && [ "$TRAY" != "none" ]; then
-    echo "| "
-fi
-
[module/TSEP]
-type = custom/script
-exec = /home/pavel/bin/polybar/tray-sep.sh
-format-foreground = ${colors.magenta}
-interval = 100000
-

i3

-

Show i3wm workspaces

-
[module/i3]
-type = internal/i3
-format = <label-state> <label-mode>
-index-sort = true
-wrapping-scroll = false
-
-; Only show workspaces on the same output as the bar
-pin-workspaces = true
-
-label-mode-padding = 1
-label-mode-foreground = #000
-label-mode-background = ${colors.blue}
-
-; focused = Active workspace on focused monitor
-label-focused = %
-; label-focused-background = ${colors.background-alt}
-label-focused-underline= ${colors.blue}
-label-focused-padding = 1
-
-; unfocused = Inactive workspace on any monitor
-label-unfocused = %
-label-unfocused-padding = 1
-
-; visible = Active workspace on unfocused monitor
-label-visible = %
-; label-visible-background = ${self.label-focused-background}
-label-visible-underline = ${self.label-focused-underline}
-label-visible-padding = ${self.label-focused-padding}
-
-; urgent = Workspace with urgency hint set
-label-urgent = %
-label-urgent-background = ${colors.red}
-label-urgent-foreground = ${colors.black}
-label-urgent-padding = 1
-

Rofi

- - - - - - - - - - - - - -
CategoryGuix dependency
desktop-rofirofi
-

rofi is another dynamic menu generator. It can act as dmenu replacement but offers a superset of dmenu’s features.

-

Theme

-

A theme, based on dracula theme for rofi, but with palenight colorscheme.

-

-
(apply
- #'concat
- (mapcar
-  (lambda (elem)
-    (concat (nth 0 elem) ": " (nth 2 elem) ";\n"))
-  table))
-
/* Generated from [[file:../../Desktop.org::*Theme][Theme:1]] */
- * {
-    <<get-rofi-colors()>>
-
-    foreground:                  @white;
-    background:                  @black;
-    background-color:            @black;
-    separatorcolor:              @blue;
-    border-color:                @blue;
-    selected-normal-foreground:  @black;
-    selected-normal-background:  @blue;
-    selected-active-foreground:  @black;
-    selected-active-background:  @blue;
-    selected-urgent-foreground:  @foreground;
-    selected-urgent-background:  @red;
-    normal-foreground:           @foreground;
-    normal-background:           @background;
-    active-foreground:           @blue;
-    active-background:           @background;
-    urgent-foreground:           @red;
-    urgent-background:           @background;
-    alternate-normal-foreground: @foreground;
-    alternate-normal-background: @light-black;
-    alternate-active-foreground: @blue;
-    alternate-active-background: @light-black;
-    alternate-urgent-foreground: @red;
-    alternate-urgent-background: @light-black;
-    spacing:                     2;
-}
-window {
-    background-color: @background;
-    border:           1;
-    padding:          5;
-}
-mainbox {
-    border:           0;
-    padding:          0;
-}
-message {
-    border:           1px dash 0px 0px ;
-    border-color:     @separatorcolor;
-    padding:          1px ;
-}
-textbox {
-    text-color:       @foreground;
-}
-listview {
-    fixed-height:     0;
-    border:           2px dash 0px 0px ;
-    border-color:     @separatorcolor;
-    spacing:          2px ;
-    scrollbar:        true;
-    padding:          2px 0px 0px ;
-}
-element {
-    border:           0;
-    padding:          1px ;
-}
-element normal.normal {
-    background-color: @normal-background;
-    text-color:       @normal-foreground;
-}
-element normal.urgent {
-    background-color: @urgent-background;
-    text-color:       @urgent-foreground;
-}
-element normal.active {
-    background-color: @active-background;
-    text-color:       @active-foreground;
-}
-element selected.normal {
-    background-color: @selected-normal-background;
-    text-color:       @selected-normal-foreground;
-}
-element selected.urgent {
-    background-color: @selected-urgent-background;
-    text-color:       @selected-urgent-foreground;
-}
-element selected.active {
-    background-color: @selected-active-background;
-    text-color:       @selected-active-foreground;
-}
-element alternate.normal {
-    background-color: @alternate-normal-background;
-    text-color:       @alternate-normal-foreground;
-}
-element alternate.urgent {
-    background-color: @alternate-urgent-background;
-    text-color:       @alternate-urgent-foreground;
-}
-element alternate.active {
-    background-color: @alternate-active-background;
-    text-color:       @alternate-active-foreground;
-}
-scrollbar {
-    width:            4px ;
-    border:           0;
-    handle-color:     @normal-foreground;
-    handle-width:     8px ;
-    padding:          0;
-}
-sidebar {
-    border:           2px dash 0px 0px ;
-    border-color:     @separatorcolor;
-}
-button {
-    spacing:          0;
-    text-color:       @normal-foreground;
-}
-button selected {
-    background-color: @selected-normal-background;
-    text-color:       @selected-normal-foreground;
-}
-inputbar {
-    spacing:          0px;
-    text-color:       @normal-foreground;
-    padding:          1px ;
-    children:         [ prompt,textbox-prompt-colon,entry,case-indicator ];
-}
-case-indicator {
-    spacing:          0;
-    text-color:       @normal-foreground;
-}
-entry {
-    spacing:          0;
-    text-color:       @normal-foreground;
-}
-prompt {
-    spacing:          0;
-    text-color:       @normal-foreground;
-}
-textbox-prompt-colon {
-    expand:           false;
-    str:              ":";
-    margin:           0px 0.3000em 0.0000em 0.0000em ;
-    text-color:       inherit;
-}
-

Scripts

-

Buku bookmarks

-

Inspired by the knatsakis/rofi-buku script.

-
if [ $(hostname) = 'pdsk' ]; then
-    BUKU="/home/pavel/.local/bin/buku"
-else
-    BUKU="/home/pavel/Programs/miniconda3/bin/buku"
-fi
-
-# COMMAND="$BUKU -o %"
-# COMMAND="qutebrowser $(buku -f 10 -p %)"
-COMMAND="firefox %"
-if [[ $1 == '-e' ]]; then
-    COMMAND="$BUKU -w %"
-fi
-$BUKU -f 4 -p | awk -F'\t' -v OFS='\t' '{
-    split($4, tags, ",")
-    joined = sep = ""
-    for (i = 1; i in tags; i++) {
-	joined = joined sep "[" tags[i] "]"
-	sep = " "
-    }
-    url = substr($2, 1, 40)
-    if (length($2) > 40) {
-	url = url "..."
-    }
-    if ($1 != "waiting for input") {
-	printf "%-5s %-60s %-45s %s\n", $1, $3, url, joined
-    }
-}' | sort -k 2 | rofi -dmenu -matching normal -sort -sorting-method fzf -width 80 -l 20 | cut -d ' ' -f 1 | {
-    read index;
-    if [[ -z "$index" ]]; then
-	exit 0
-    fi
-    url=$($BUKU -f 10 -p $index)
-    echo ${url#"waiting for input"} | cut -d ' ' -f 1 | xargs -I % $COMMAND
-}
-

Man pages

-

Inspired by this Luke Smith’s video.

-

A script to open a man page with zathura. There is no particular reason why one should look through man pages in pdf viewer rather than in console, but why not.

-
SELECTED=$(man -k . | rofi -dmenu -l 20 | awk '{print $1}')
-if [[ ! -z $SELECTED ]]; then
-    man -Tpdf $SELECTED | zathura -
-fi
-

Emojis

- - - - - - - - - - - - - -
CategoryGuix dependency
desktop-rofipython-rofimoji
-

pass

- - - - - - - - - - - - - - - - - -
CategoryGuix dependency
desktop-rofirofi-pass
desktop-rofixset
-

A nice pass frontend for Rofi, which is even packaged for Guix.

-
USERNAME_field='username'
-EDITOR=vim
-default_autotype='username :tab pass'
-clip=both
-

Flameshot

- - - - - - - - - - - -
Guix dependency
flameshot
-

flameshot is my program of choice to make screenshots.

-

As it overwrites its own config all the time, I do not keep the file in VC.

-
[General]
-disabledTrayIcon=false
-drawColor=#ff0000
-drawThickness=0
-saveAfterCopyPath=/home/pavel/Pictures
-savePath=/home/pavel/Pictures
-savePathFixed=false
-showStartupLaunchMessage=false
-uiColor=<<get-color(name="blue")>>
-
-[Shortcuts]
-TYPE_ARROW=A
-TYPE_CIRCLE=C
-TYPE_CIRCLECOUNT=
-TYPE_COMMIT_CURRENT_TOOL=Ctrl+Return
-TYPE_COPY=Ctrl+C
-TYPE_DRAWER=D
-TYPE_EXIT=Ctrl+Q
-TYPE_IMAGEUPLOADER=Return
-TYPE_MARKER=M
-TYPE_MOVESELECTION=Ctrl+M
-TYPE_MOVE_DOWN=Down
-TYPE_MOVE_LEFT=Left
-TYPE_MOVE_RIGHT=Right
-TYPE_MOVE_UP=Up
-TYPE_OPEN_APP=Ctrl+O
-TYPE_PENCIL=P
-TYPE_PIN=
-TYPE_PIXELATE=B
-TYPE_RECTANGLE=R
-TYPE_REDO=Ctrl+Shift+Z
-TYPE_RESIZE_DOWN=Shift+Down
-TYPE_RESIZE_LEFT=Shift+Left
-TYPE_RESIZE_RIGHT=Shift+Right
-TYPE_RESIZE_UP=Shift+Up
-TYPE_SAVE=Ctrl+S
-TYPE_SELECTION=S
-TYPE_SELECTIONINDICATOR=
-TYPE_SELECT_ALL=Ctrl+A
-TYPE_TEXT=T
-TYPE_TOGGLE_PANEL=Space
-TYPE_UNDO=Ctrl+Z
-

dunst

- - - - - - - - - - - - - - -
Guix dependency
dunst
libnotify
- - - - - - - - - - - - - -
TypeNote
TODOCleanup default config comments
-

dunst is a lightweight notification daemon.

-

My customizations of the original config consist mostly of changing colors.

-

References:

- - -
[global]
-    monitor = 0
-
-    follow = mouse
-
-    # The geometry of the window:
-    #   [{width}]x{height}[+/-{x}+/-{y}]
-    # The geometry of the message window.
-    # The height is measured in number of notifications everything else
-    # in pixels.  If the width is omitted but the height is given
-    # ("-geometry x2"), the message window expands over the whole screen
-    # (dmenu-like).  If width is 0, the window expands to the longest
-    # message displayed.  A positive x is measured from the left, a
-    # negative from the right side of the screen.  Y is measured from
-    # the top and down respectively.
-    # The width can be negative.  In this case the actual width is the
-    # screen width minus the width defined in within the geometry option.
-    geometry = "300x5-30+20"
-
-    # Show how many messages are currently hidden (because of geometry).
-    indicate_hidden = yes
-
-    # Shrink window if its smaller than the width.  Will be ignored if
-    # width is 0.
-    shrink = no
-
-    # The transparency of the window.  Range: [0; 100].
-    # This option will only work if a compositing window manager is
-    # present (e.g. xcompmgr, compiz, etc.).
-    transparency = 15
-
-    # The height of the entire notification.  If the height is smaller
-    # than the font height and padding combined, it will be raised
-    # to the font height and padding.
-    notification_height = 0
-
-    # Draw a line of "separator_height" pixel height between two
-    # notifications.
-    # Set to 0 to disable.
-    separator_height = 2
-
-    # Padding between text and separator.
-    padding = 8
-
-    # Horizontal padding.
-    horizontal_padding = 8
-
-    # Defines width in pixels of frame around the notification window.
-    # Set to 0 to disable.
-    frame_width = 1
-
-    # Defines color of the frame around the notification window.
-    frame_color = <<get-color(name="white", quote=1)>>
-
-    # Define a color for the separator.
-    # possible values are:
-    #  * auto: dunst tries to find a color fitting to the background;
-    #  * foreground: use the same color as the foreground;
-    #  * frame: use the same color as the frame;
-    #  * anything else will be interpreted as a X color.
-    separator_color = frame
-
-    # Sort messages by urgency.
-    sort = yes
-
-    # Don't remove messages, if the user is idle (no mouse or keyboard input)
-    # for longer than idle_threshold seconds.
-    # Set to 0 to disable.
-    # A client can set the 'transient' hint to bypass this. See the rules
-    # section for how to disable this if necessary
-    idle_threshold = 120
-
-    ### Text ###
-
-    font = DejaVu Sans 9
-
-    # The spacing between lines.  If the height is smaller than the
-    # font height, it will get raised to the font height.
-    line_height = 0
-
-    # Possible values are:
-    # full: Allow a small subset of html markup in notifications:
-    #        <b>bold</b>
-    #        <i>italic</i>
-    #        <s>strikethrough</s>
-    #        <u>underline</u>
-    #
-    #        For a complete reference see
-    #        <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>.
-    #
-    # strip: This setting is provided for compatibility with some broken
-    #        clients that send markup even though it's not enabled on the
-    #        server. Dunst will try to strip the markup but the parsing is
-    #        simplistic so using this option outside of matching rules for
-    #        specific applications *IS GREATLY DISCOURAGED*.
-    #
-    # no:    Disable markup parsing, incoming notifications will be treated as
-    #        plain text. Dunst will not advertise that it has the body-markup
-    #        capability if this is set as a global setting.
-    #
-    # It's important to note that markup inside the format option will be parsed
-    # regardless of what this is set to.
-    markup = full
-
-    # The format of the message.  Possible variables are:
-    #   %a  appname
-    #   %s  summary
-    #   %b  body
-    #   %i  iconname (including its path)
-    #   %I  iconname (without its path)
-    #   %p  progress value if set ([  0%] to [100%]) or nothing
-    #   %n  progress value if set without any extra characters
-    #   %%  Literal %
-    # Markup is allowed
-    format = "<b>%s</b>\n%b"
-
-    # Alignment of message text.
-    # Possible values are "left", "center" and "right".
-    alignment = left
-
-    # Show age of message if message is older than show_age_threshold
-    # seconds.
-    # Set to -1 to disable.
-    show_age_threshold = 60
-
-    # Split notifications into multiple lines if they don't fit into
-    # geometry.
-    word_wrap = yes
-
-    # When word_wrap is set to no, specify where to make an ellipsis in long lines.
-    # Possible values are "start", "middle" and "end".
-    ellipsize = middle
-
-    # Ignore newlines '\n' in notifications.
-    ignore_newline = no
-
-    # Stack together notifications with the same content
-    stack_duplicates = true
-
-    # Hide the count of stacked notifications with the same content
-    hide_duplicate_count = false
-
-    # Display indicators for URLs (U) and actions (A).
-    show_indicators = yes
-
-    ### Icons ###
-
-    # Align icons left/right/off
-    icon_position = left
-
-    # Scale larger icons down to this size, set to 0 to disable
-    max_icon_size = 32
-
-    # Paths to default icons.
-    icon_path = /usr/share/icons/Mint-Y/status/32/;/usr/share/icons/Mint-Y/devices/32
-
-    ### History ###
-
-    # Should a notification popped up from history be sticky or timeout
-    # as if it would normally do.
-    sticky_history = yes
-
-    # Maximum amount of notifications kept in history
-    history_length = 20
-
-    ### Misc/Advanced ###
-
-    # dmenu path.
-    dmenu = /usr/bin/dmenu -p dunst:
-
-    # Browser for opening urls in context menu.
-    browser = /usr/bin/sensible-browser
-
-    # Always run rule-defined scripts, even if the notification is suppressed
-    always_run_script = true
-
-    # Define the title of the windows spawned by dunst
-    title = Dunst
-
-    # Define the class of the windows spawned by dunst
-    class = Dunst
-
-    # Print a notification on startup.
-    # This is mainly for error detection, since dbus (re-)starts dunst
-    # automatically after a crash.
-    startup_notification = false
-
-    # Manage dunst's desire for talking
-    # Can be one of the following values:
-    #  crit: Critical features. Dunst aborts
-    #  warn: Only non-fatal warnings
-    #  mesg: Important Messages
-    #  info: all unimportant stuff
-    # debug: all less than unimportant stuff
-    verbosity = mesg
-
-    # Define the corner radius of the notification window
-    # in pixel size. If the radius is 0, you have no rounded
-    # corners.
-    # The radius will be automatically lowered if it exceeds half of the
-    # notification height to avoid clipping text and/or icons.
-    corner_radius = 0
-
-    ### Legacy
-
-    # Use the Xinerama extension instead of RandR for multi-monitor support.
-    # This setting is provided for compatibility with older nVidia drivers that
-    # do not support RandR and using it on systems that support RandR is highly
-    # discouraged.
-    #
-    # By enabling this setting dunst will not be able to detect when a monitor
-    # is connected or disconnected which might break follow mode if the screen
-    # layout changes.
-    force_xinerama = false
-
-    ### mouse
-
-    # Defines action of mouse event
-    # Possible values are:
-    # * none: Don't do anything.
-    # * do_action: If the notification has exactly one action, or one is marked as default,
-    #              invoke it. If there are multiple and no default, open the context menu.
-    # * close_current: Close current notification.
-    # * close_all: Close all notifications.
-    mouse_left_click = close_current
-    mouse_middle_click = do_action
-    mouse_right_click = close_all
-
-# Experimental features that may or may not work correctly. Do not expect them
-# to have a consistent behaviour across releases.
-[experimental]
-    # Calculate the dpi to use on a per-monitor basis.
-    # If this setting is enabled the Xft.dpi value will be ignored and instead
-    # dunst will attempt to calculate an appropriate dpi value for each monitor
-    # using the resolution and physical size. This might be useful in setups
-    # where there are multiple screens with very different dpi values.
-    per_monitor_dpi = false
-
-[shortcuts]
-
-    # Shortcuts are specified as [modifier+][modifier+]...key
-    # Available modifiers are "ctrl", "mod1" (the alt-key), "mod2",
-    # "mod3" and "mod4" (windows-key).
-    # Xev might be helpful to find names for keys.
-
-    # Close notification.
-    close = ctrl+space
-
-    # Close all notifications.
-    close_all = ctrl+shift+space
-
-    # Redisplay last message(s).
-    # On the US keyboard layout "grave" is normally above TAB and left
-    # of "1". Make sure this key actually exists on your keyboard layout,
-    # e.g. check output of 'xmodmap -pke'
-    history = ctrl+grave
-
-    # Context menu.
-    context = ctrl+shift+period
-
-[urgency_low]
-    # IMPORTANT: colors have to be defined in quotation marks.
-    # Otherwise the "#" and following would be interpreted as a comment.
-    background = <<get-color(name="light-black", quote=1)>>
-    frame_color = <<get-color(name="white", quote=1)>>
-    foreground = <<get-color(name="light-white", quote=1)>>
-    timeout = 10
-    # Icon for notifications with low urgency, uncomment to enable
-    #icon = /path/to/icon
-
-[urgency_normal]
-    background = <<get-color(name="black", quote=1)>>
-    frame_color = <<get-color(name="white", quote=1)>>
-    foreground = <<get-color(name="light-white", quote=1)>>
-    timeout = 10
-    # Icon for notifications with normal urgency, uncomment to enable
-    #icon = /path/to/icon
-
-[urgency_critical]
-    background = <<get-color(name="red", quote=1)>>
-    foreground = <<get-color(name="light-white", quote=1)>>
-    frame_color = <<get-color(name="red", quote=1)>>
-    timeout = 0
-    # Icon for notifications with critical urgency, uncomment to enable
-    #icon = /path/to/icon
-

keynav

- - - - - - - - - - - -
Guix dependency
keynav
- - - - - - - - - - - - - -
TypeNote
SYMLINK./config/keynavrc -> .keynavrc
-

How many times you have been working with keyboard-driven programs and had to use a mouse just to press some pesky little button in a modal window?

-

keynav is a program that allows you to control the mouse with the keyboard with the general idea of bisecting the screen to get to the required point. I’m still not sure if there is any point in using it, but it’s rather funny. Unfortunately, the colors seem to be hardcoded.

-

One of the usecases I found so far is to use the program to scroll webpages when tridactyl’s scroll captures the wrong scroll area.

-

References:

- -

Config

-
# clear all previous keybindings
-clear
-
-# Start & stop
-ctrl+semicolon start
-Super_L+bracketright start
-Super_R+bracketright start
-Escape end
-ctrl+bracketleft end
-
-# Macros
-q record ~/.keynav_macros
-shift+at playback
-
-# Bisecting
-a history-back
-Left cut-left
-Right cut-right
-Down cut-down
-Up cut-up
-h cut-left
-j cut-down
-k cut-up
-l cut-right
-t windowzoom                          # Zoom to the current window
-c cursorzoom 300 300                  # Limit the bisection area by 300x300
-
-# Move the bisecting area
-shift+h move-left
-shift+j move-down
-shift+k move-up
-shift+l move-right
-shift+Left move-left
-shift+Right move-right
-shift+Up move-up
-shift+Down move-down
-
-# Actions
-space warp,click 3,end                # Right click
-Return warp,click 1,end               # Left click
-Shift+Return warp,doubleclick 1,end   # Double left click
-semicolon warp,end                    # Move the cursor and exit
-w warp                                # Just move the cursor
-e end                                 # exit
-u warp,click 4                        # scroll up
-d warp,click 5                        # scroll down
-1 click 1
-2 click 2
-3 click 3
-4 click 4
-5 click 5
-

Using with picom

-

I’ve noticed that the program does not play nice with picom’s fade effect. To fix that, add the following to you config:

-
fade-exclude = [
-  "class_i = 'keynav'",
-  "class_g = 'keynav'",
-]
-

Picom

- - - - - - - - - - - -
Guix dependency
picom
-

picom is a compositor for X11. It allows effects such as transparency, blurring, etc.

-

Sample configuration is a good resource for getting an overview of the available settings. I have only a bunch of necessary settings in mine.

-

There are a bunch of forks for picom (e.g. ibhagwan/picom adds rounded corners) which seem to have some popularity, but I use the base one.

-

References:

- -

Shadows

-
shadow = true;
-shadow-radius = 2;
-shadow-offset-x = -2;
-shadow-offset-y = -2;
-
-shadow-exclude = [
-  "name = 'Notification'",
-  "class_g = 'Conky'",
-  "name ?= 'cpt_frame_window'",
-  "class_g ?= 'Notify-osd'",
-  "class_g = 'Cairo-clock'",
-  "_GTK_FRAME_EXTENTS@:c"
-];
-

Fading

-
fading = true
-
-fade-in-step = 0.03;
-fade-out-step = 0.03;
-fade-delta = 10
-
-fade-exclude = [
-  "class_i = 'keynav'",
-  "class_g = 'keynav'",
-]
-

Opacity

-

I don’t use stuff like transparency for inactive windows.

-

The first 5 lines of opacity-rule make i3wm’s hidden windows 100% transparent, so I see the background behind the semi-transparent windows in i3wm’s stacked and tabbed layout. Here is StackExchange question about that.

-

I also noticed that for some reason it doesn’t play well with Emacs’s built-in transparency, so the last line sets up Emacs transparency at 90%.

-
inactive-opacity = 1;
-
-frame-opacity = 1.0;
-inactive-opacity-override = false;
-focus-exclude = [ "class_g = 'Cairo-clock'" ];
-
-opacity-rule = [
-  "0:_NET_WM_STATE@[0]:32a = '_NET_WM_STATE_HIDDEN'",
-  "0:_NET_WM_STATE@[1]:32a = '_NET_WM_STATE_HIDDEN'",
-  "0:_NET_WM_STATE@[2]:32a = '_NET_WM_STATE_HIDDEN'",
-  "0:_NET_WM_STATE@[3]:32a = '_NET_WM_STATE_HIDDEN'",
-  "0:_NET_WM_STATE@[4]:32a = '_NET_WM_STATE_HIDDEN'",
-  "90:class_g = 'Emacs'"
-];
-

General settings

-

Default general settings. Editing some of these may be neeeded in case of performance issues.

-
backend = "xrender";
-vsync = true
-mark-wmwin-focused = true;
-mark-ovredir-focused = true;
-detect-rounded-corners = true;
-detect-client-opacity = true;
-refresh-rate = 0
-detect-transient = true
-detect-client-leader = true
-use-damage = true
-log-level = "warn";
-
-wintypes:
-{
-  tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; };
-  dock = { shadow = false; }
-  dnd = { shadow = false; }
-  popup_menu = { opacity = 1; }
-  dropdown_menu = { opacity = 1; }
-};
-

Zathura

- - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
officezathura
officezathura-ps
officezathura-pdf-mupdf
officezathura-djvu
-

Zathura is a pdf viewer with vim-like keybindings. One of my favorite features is an ability to invert the document colors.

-
set abort-clear-search false
-set show-scrollbars true
-set show-h-scrollbar true
-set show-v-scrollbar true
-set selection-clipboard clipboard
-set recolor-lightcolor <<get-color(name="black", quote=1)>>
-set recolor true
-map <C-r> set recolor false
-map <C-R> set recolor true
-

For some reason zathura doesn’t pick up the plugin directory, so I make a wrapper that sets the directory up:

-
zathura -p ~/.guix-extra-profiles/office/office/lib/zathura $@
-
[Desktop Entry]
-Version=1.0
-Type=Application
-Name=Zathura
-Exec=/home/pavel/bin/zathura-wrapper %U
-

Add the following like to the mimeapps.list

-
application/pdf=zathura-wrapper.desktop
-

Various software

-

This section generates manifests for various desktop software that I’m using.

-

Browsers

- - - - - - - - - - - - - - - - - -
CategoryGuix dependency
browsersungoogled-chromium
browsersfirefox
-

Office & Multimedia

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
officelibreoffice
officegimp
officekrita
officeffmpeg
officekdenlive
officeinkscape
-

LaTeX

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
latextexlive
latextexlab-bin
latexbiber
latexpython-pygments
latexfont-microsoft-web-core-fonts
-

Dev

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
devconda
devdocker-compose
devpostgresql
devvirt-manager
devgit-filter-repo
devnode
devopenjdk
devgo
devgcc-toolchain
devlua
devlibfaketime
devhugo-extended
devmake
devsbcl
devgit-lfs
devmysql
devgource
-

Manifests

-

-
(my/format-guix-dependencies category)
-

Dev

-
(specifications->manifest
- '(
-   <<packages("dev")>>))
-

Browsers

-
(specifications->manifest
- '(
-   <<packages("browsers")>>))
-

Music

-
(specifications->manifest
- '(
-   <<packages("music")>>))
-

Office

-
(specifications->manifest
- '(
-   <<packages("office")>>))
-

LaTeX

-
(specifications->manifest
- '(
-   <<packages("latex")>>))
-

Desktop Misc

-
(specifications->manifest
- '(
-   <<packages("desktop-misc")>>))
-

Desktop polybar

-
(specifications->manifest
- '(
-   <<packages("desktop-polybar")>>))
-

Desktop rofi

-
(specifications->manifest
- '(
-   <<packages("desktop-rofi")>>))
-

Flatpak

-

A lot of proprietary desktop applications can be installed most easily with flatpak & flathub.

- - - - - - - - - - - - - - -
Guix dependency
flatpak
xdg-desktop-portal
-

After installation, add the following repositories:

-
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-flatpak remote-add --user --if-not-exists flathub-beta https://flathub.org/beta-repo/flathub-beta.flatpakrepo
-

Installation syntax is as follows:

-
flatpak install --user <remote> <package>
-

Packages to install:

-

- - - - - - - - - - - - - - - - - - - - - - - - - -
Flatpak dependencyChannel
com.github.wwmm.pulseeffectsflathub
com.discordapp.Discordflathub
us.zoom.Zoomflathub
com.slack.Slackflathub
-
(mapconcat
- (lambda (c) (concat "flatpak install -y --user " (nth 1 c) " " (nth 0 c)))
- table
- "\n")
-

Nix

- - - - - - - - - - - - - -
TypeDescription
TODOMake nix manifest?
-

I probably should’ve used nix, as almost every program I packaged so far exists in the Nix repo.

-

But it’s easy enough to use Nix on Guix.

-
https://nixos.org/channels/nixpkgs-unstable nixpkgs
-

Don’t forget to run the following after the first installation:

-
nix-channel --update
-

Installing packages:

-
nix-env -i vk-messenger slack
-

Services

-

GNU Shepherd is a service management system for GNU Guix.

-

I previously used supervisor, but shepherd also seems pretty capable.

-

Music

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
musicmpd
musicncmpcpp
musicpicard
musicmpd-watcher
musicmpd-mpc
musicshntool
musiccuetools
musicflac
-

Music player daemon

-
(define mpd
-  (make <service>
-    #:provides '(mpd)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("mpd" "--no-daemon"))
-    #:stop (make-kill-destructor)))
-

MPD watcher

-
(define mpd-watcher
-  (make <service>
-    #:provides '(mpd-watcher)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("mpd_watcher"))
-    #:stop (make-kill-destructor)
-    #:requires '(mpd)))
-

GNU Mcron

-

GNU Mcron is a replacement for cron, written in Scheme.

-
(define mcron
-  (make <service>
-    #:provides '(mcron)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("mcron"))
-    #:stop (make-kill-destructor)))
-

ActivityWatch

-

ActivityWatch is a FOSS time tracker. It tracks screen and application usage and has integrations with browsers, Emacs, etc.

- - - - - - - - - - - -
Guix dependency
activitywatch-bin
-

aw-server

-
(define aw-server
-  (make <service>
-    #:provides '(aw-server)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("aw-server"))
-    #:stop (make-kill-destructor)))
-

aw-watcher-afk has some problems with statup, so there is a wrapper script

-
sleep 5
-aw-watcher-afk
-

aw-watcher-afk

-
(define aw-watcher-afk
-  (make <service>
-    #:provides '(aw-watcher-afk)
-    #:requires '(aw-server)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("/home/pavel/bin/scripts/aw-watcher-afk-wrapper"))
-    #:stop (make-kill-destructor)))
-

aw-watcher-window

-
(define aw-watcher-window
-  (make <service>
-    #:provides '(aw-watcher-window)
-    #:requires '(aw-server)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("aw-watcher-window"))
-    #:stop (make-kill-destructor)))
-

PulseEffects

-
(define pulseeffects
-  (make <service>
-    #:provides '(pulseeffects)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("flatpak" "run" "com.github.wwmm.pulseeffects" "--gapplication-service"))
-    #:stop (make-kill-destructor)))
-

xsettingsd

-
(define xsettingsd
-  (make <service>
-    #:provides '(xsettingsd)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("xsettingsd"))
-    #:stop (make-kill-destructor)))
-

nm-applet

-
(define nm-applet
-  (make <service>
-    #:provides '(nm-applet)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("nm-applet"))
-    #:stop (make-kill-destructor)))
-

Discord rich presence

-

References:

- - -
(define discord-rich-presence
-  (make <service>
-    #:provides '(discord-rich-presence)
-    #:one-shot? #t
-    #:start (make-system-constructor "ln -sf {app/com.discordapp.Discord,$XDG_RUNTIME_DIR}/discord-ipc-0")))
-

Polkit Authentication agent

-

Launch an authentication agent. Necessary for stuff like pkexec. I suspect I’m not doing that the intended way, but it seems to work.

-
(define polkit-gnome
-  (make <service>
-    #:provides '(polkit-gnome)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("/home/pavel/.guix-extra-profiles/desktop-misc/desktop-misc/libexec/polkit-gnome-authentication-agent-1"))
-    #:stop (make-kill-destructor)))
-

Xmodmap

-
(define xmodmap
-  (make <service>
-    #:provides '(xmodmap)
-    #:one-shot? #t
-    #:start (make-system-constructor "xmodmap /home/pavel/.Xmodmap")))
-

VPN

-

Run my OpenVPN setup. Not lauching this automatially, as it requires an active connection.

-
(define vpn
-  (make <service>
-    #:provides '(vpn)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("/home/pavel/bin/scripts/vpn-start"))
-    #:stop (make-kill-destructor)))
-

Davmail

-
(define davmail
-  (make <service>
-    #:provides '(davmail)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("/home/pavel/bin/davmail"))
-    #:stop (make-kill-destructor)))
-

Shepherd config

-

Register services

-
(register-services
- mpd
- mpd-watcher
- mcron
- aw-server
- aw-watcher-afk
- aw-watcher-window
- pulseeffects
- xsettingsd
- discord-rich-presence
- polkit-gnome
- vpn
- davmail
- xmodmap
- nm-applet)
-

Daemonize shepherd

-
(action 'shepherd 'daemonize)
-

Run services

-
(for-each start '(mpd mpd-watcher mcron aw-server aw-watcher-afk aw-watcher-window pulseeffects xsettingsd discord-rich-presence polkit-gnome davmail xmodmap nm-applet))
-

Sync

- - - - - - - - - - - -
Guix dependency
megacmd-1.4
-

Guix settings

-

Other desktop programs I use are listed below.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependencyDescription
desktop-miscxpropTool to display properties of X windows
desktop-miscarandrGUI to xrandr
desktop-misclightControl screen brightness
desktop-miscponymixControl PulseAudio CLI
desktop-miscpavucontrolControl PulseAudio GUI
desktop-miscnetwork-manager-appletApplet to manage network connections
desktop-miscxmodmapProgram to modify keybindings on X server
desktop-miscfontconfig
desktop-miscpolkit-gnomePolkit authentication agent
desktop-miscfehImage viewer. Used to set background
desktop-misccopyqClipboard manager
desktop-miscthunarMy preferred GUI file manager
desktop-misctelegram-desktoptelegram client
desktop-miscxdg-utilsgives xdg-open and stuff
desktop-miscgnome-font-viewerview fonts
desktop-miscqbittorrenttorrent client
desktop-miscanydeskRemote desktop software
desktop-miscgnome-disk-utilityManage disks
desktop-miscgpartedManage partitions
desktop-miscxevTest input
-

-
(my/format-guix-dependencies)
-
(specifications->manifest
- '(
-   <<packages()>>))
-
-
- -
- - diff --git a/public/configs/emacs/index.html b/public/configs/emacs/index.html deleted file mode 100644 index df621b7..0000000 --- a/public/configs/emacs/index.html +++ /dev/null @@ -1,5715 +0,0 @@ - - - - - - Emacs config - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

#+begin_quote -One day we won’t hate one another, no young boy will march to war and I will clean up my Emacs config. But that day isn'.

-
-
-
Table of Contents
- -
- -

Primary setup

-

Measure startup speed

-

A small function to print out the loading time and number of GCs during the loading. Can be useful as a point of data for optimizing Emacs startup time.

-
(add-hook 'emacs-startup-hook
-	  (lambda ()
-	    (message "*** Emacs loaded in %s with %d garbage collections."
-		     (format "%.2f seconds"
-			     (float-time
-			      (time-subtract after-init-time before-init-time)))
-		     gcs-done)))
-

Set the following to t to print debug information during the startup. This will include the order in which the packages are loaded and the loading time of individual packages.

-
;; (setq use-package-verbose t)
-

straight.el

-

Straight.el is my Emacs package manager of choice. Its advantages & disadvantages over other options are listed pretty thoroughly in the README file in the repo.

-

The following is a straight.el bootstrap script.

-

References:

- - -
(defvar bootstrap-version)
-(let ((bootstrap-file
-       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
-      (bootstrap-version 5))
-  (unless (file-exists-p bootstrap-file)
-    (with-current-buffer
-	(url-retrieve-synchronously
-	 "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
-	 'silent 'inhibit-cookies)
-      (goto-char (point-max))
-      (eval-print-last-sexp)))
-      (load bootstrap-file nil 'nomessage))
-

use-package

-

A macro to simplify package specification & configuration. Integrates with straight.el.

-

Set use-package-verbose to t to print out individual package loading time.

-

References:

- - -
(straight-use-package 'use-package)
-(eval-when-compile (require 'use-package))
-

Config variants & environment

-

This section is about optionating the Emacs config.

-

The following variable is true when my machine is not powerful enough for some resource-heavy packages.

-
(setq my/lowpower (string= (system-name) "azure"))
-

The following is true if Emacs is meant to be used with TRAMP over slow ssh. Take a look at the TRAMP section for more details.

-
(setq my/slow-ssh
-      (or
-       (string= (getenv "IS_TRAMP") "true")
-       (string= (system-name) "dev-digital")
-       (string= (system-name) "violet")))
-

The following is true is Emacs is ran on a remote server where I don’t need stuff like my org workflow

-
(setq my/remote-server
-      (or (string= (getenv "IS_REMOTE") "true")
-	  (string= (system-name) "dev-digital")
-	  (string= (system-name) "violet")))
-

And the following is true if Emacs is run from termux on Android.

-
(setq my/is-termux (string-match-p (rx (* nonl) "com.termux" (* nonl)) (getenv "HOME")))
-

Also, I sometimes need to know if a program is running inside Emacs (say, inside a terminal emulator). To do that, I set the following environment variable:

-
(setenv "IS_EMACS" "true")
-

Finally, I want to have a minimal Emacs config for debugging purposes. This has just straight.el, use-packages and evil.

-
<<minimal>>
-

To launch Emacs with this config, run

-
emacs -q -l ~/.emacs.d/init-minimal.el
-

Performance

-

Garbage collection

-

Just setting gc-cons-treshold to a larger value.

-
(setq gc-cons-threshold 80000000)
-(setq read-process-output-max (* 1024 1024))
-

Run garbage collection when Emacs is unfocused

-

Run GC when Emacs loses focus. Time will tell if that’s a good idea.

-

Some time has passed, and I still don’t know if there is any quantifiable advantage to this, but it doesn’t hurt.

-
(add-hook 'emacs-startup-hook
-	  (lambda ()
-	    (if (boundp 'after-focus-change-function)
-		(add-function :after after-focus-change-function
-			      (lambda ()
-				(unless (frame-focus-state)
-				  (garbage-collect))))
-	      (add-hook 'after-focus-change-function 'garbage-collect))))
-

Native compilation

-

Set the number of native compilation jobs to 1 on low-power machines.

-
(when my/lowpower
-  (setq comp-async-jobs-number 1))
-

Anaconda

-

Anaconda is a free package and environment manager. I currently use it to manage multiple versions of Python and Node.js. Take a look at the corresponding entry in the Guix config for details about using it on Guix.

-

The following code uses the conda package to activate the base environment on startup if Emacs is launched outside the environment.

-

Also, some strange things are happening if vterm is launched with conda activated from Emacs, so I advise conda-env-activate to set an auxililary environment variable. This variable is used in the shell config.

-

References:

- - -
(use-package conda
-  :straight t
-  :if (executable-find "conda")
-  :config
-  (setq conda-anaconda-home (string-replace "/bin/conda" "" (executable-find "conda")))
-  (setq conda-env-home-directory (expand-file-name "~/.conda/"))
-  (setq conda-env-subdirectory "envs")
-  (setenv "INIT_CONDA" "true")
-  (advice-add 'conda-env-activate :after
-	      (lambda (&rest _) (setenv "EMACS_CONDA_ENV" conda-env-current-name)))
-  (unless (getenv "CONDA_DEFAULT_ENV")
-    (conda-env-activate "general")))
-

Custom file location

-

By default, custom writes stuff to init.el, which is somewhat annoying. The following makes it write to a separate file custom.el

-
(setq custom-file (concat user-emacs-directory "custom.el"))
-(load custom-file 'noerror)
-

Private config

-

I have some variables which I don’t commit to the repo, e.g. my current location. They are stored in private.el

-
(let ((private-file (expand-file-name "private.el" user-emacs-directory)))
-
-    (load-file private-file))
-

No littering

-

By default Mmacs and its packages create a lot files in .emacs.d and in other places. no-littering is a collective effort to redirect all of that to two folders in user-emacs-directory.

-
(use-package no-littering
-  :straight t)
-

Prevent Emacs from closing

-

This adds a confirmation to avoid accidental Emacs closing.

-
(setq confirm-kill-emacs 'y-or-n-p)
-

Global editing configuration

-

General keybindings stuff

-

general.el

-

general.el provides a convenient interface to manage Emacs keybindings.

-

References:

- - -
(use-package general
-  :straight t
-  :config
-  (general-evil-setup))
-

which-key

-

A package that displays the available keybindings in a popup. The package is pretty useful, as Emacs seems to have more keybindings than I can remember at any given point.

-

References:

- - -
(use-package which-key
-  :config
-  (setq which-key-idle-delay (if my/lowpower 1 0.3))
-  (setq which-key-popup-type 'frame)
-  (which-key-mode)
-  (which-key-setup-side-window-bottom)
-  (set-face-attribute 'which-key-local-map-description-face nil
-		      :weight 'bold)
-  :straight t)
-
dump keybindings
-

A function to dump keybindings starting with a prefix to a buffer in tree-like form.

-
(defun my/dump-bindings-recursive (prefix &optional level)
-  (dolist (key (which-key--get-bindings (kbd prefix)))
-    (when level
-      (insert (make-string level ? )))
-    (insert (apply #'format "%s%s%s\n" key))
-    (when (string-match-p
-	   (rx bos "+" (* nonl))
-	   (substring-no-properties (elt key 2)))
-      (my/dump-bindings-recursive
-       (concat prefix " " (substring-no-properties (car key)))
-       (+ 2 (or level 0))))))
-
-(defun my/dump-bindings (prefix)
-  "Dump keybindings starting with PREFIX in tree-like form."
-  (interactive "sPrefix: ")
-  (with-current-buffer (get-buffer-create "bindings")
-    (point-max)
-    (erase-buffer)
-    (save-excursion
-      (my/dump-bindings-recursive prefix)))
-  (switch-to-buffer-other-window "bindings"))
-

Evil mode

-

An entire ecosystem of packages that emulates the main features of Vim. Probably the best vim emulator out there.

-

The only problem is that the package name makes it hard to google anything by just typing “evil”.

-

References:

- -

evil

-

Basic evil configuration.

-
(use-package evil
-  :straight t
-  :init
-  (setq evil-want-integration t)
-  (setq evil-want-C-u-scroll t)
-  (setq evil-want-keybinding nil)
-  (setq evil-search-module 'evil-search)
-  (setq evil-split-window-below t)
-  (setq evil-vsplit-window-right t)
-  :config
-  (evil-mode 1)
-  ;; (setq evil-respect-visual-line-mode t)
-  (evil-set-undo-system 'undo-tree))
-

Addons

-

evil-surround emulates one of my favorite vim plugins, surround.vim. Adds a lot of parentheses management options.

-
(use-package evil-surround
-  :straight t
-  :after evil
-  :config
-  (global-evil-surround-mode 1))
-

evil-commentary emulates commentary.vim. It gives actions for quick insertion and deletion of comments.

-
(use-package evil-commentary
-  :straight t
-  :after evil
-  :config
-  (evil-commentary-mode))
-

evil-quickscope emulates quickscope.vim. It highlights the important target characters for f, F, t, T keys.

-
(use-package evil-quickscope
-  :straight t
-  :after evil
-  :config
-  :hook ((prog-mode . turn-on-evil-quickscope-mode)
-	 (LaTeX-mode . turn-on-evil-quickscope-mode)
-	 (org-mode . turn-on-evil-quickscope-mode)))
-

evil-numbers allows incrementing and decrementing numbers at the point.

-
(use-package evil-numbers
-  :straight t
-  :commands (evil-numbers/inc-at-pt evil-numbers/dec-at-pt)
-  :init
-  (general-nmap
-    "g+" 'evil-numbers/inc-at-pt
-    "g-" 'evil-numbers/dec-at-pt))
-

evil-lion provides alignment operators, somewhat similar to vim-easyalign.

-
(use-package evil-lion
-  :straight t
-  :config
-  (setq evil-lion-left-align-key (kbd "g a"))
-  (setq evil-lion-right-align-key (kbd "g A"))
-  (evil-lion-mode))
-

evil-matchit makes “%” to match things like tags. It doesn’t work perfectly, so I occasionally turn it off.

-
(use-package evil-matchit
-  :straight t
-  :config
-  (global-evil-matchit-mode 1))
-

evil-collection

-

evil-collection is a package that provides evil bindings for a lot of different packages. One can see the complete list in the modes folder.

-
(use-package evil-collection
-  :straight t
-  :after evil
-  :config
-  (evil-collection-init
-   '(eww
-     devdocs
-     proced
-     emms
-     pass
-     calendar
-     dired
-     ivy
-     debug
-     guix
-     calc
-     docker
-     ibuffer
-     geiser
-     pdf
-     info
-     elfeed
-     edebug
-     bookmark
-     company
-     vterm
-     flycheck
-     profiler
-     cider
-     explain-pause-mode
-     notmuch
-     custom
-     xref
-     eshell
-     helpful
-     compile
-     comint
-     git-timemachine
-     magit
-     prodigy
-     slime)))
-

More keybindings

-

The main keybindings setup is positioned after evil mode to take the latter into account.

-

Escape key

-

Use the escape key instead of C-g whenever possible.

-

I must have copied it from somewhere, but as I googled to find out the source, I discovered quite a number of variations of the following code over time. I wonder if Richard Dawkins was inspired by something like this a few decades ago.

-
(defun minibuffer-keyboard-quit ()
-  "Abort recursive edit.
-In Delete Selection mode, if the mark is active, just deactivate it;
-then it takes a second \\[keyboard-quit] to abort the minibuffer."
-  (interactive)
-  (if (and delete-selection-mode transient-mark-mode mark-active)
-      (setq deactivate-mark  t)
-    (when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
-    (abort-recursive-edit)))
-
-(general-define-key
- :keymaps '(normal visual global)
- [escape] 'keyboard-quit)
-
-(general-define-key
- :keymaps '(minibuffer-local-map
-	    minibuffer-local-ns-map
-	    minibuffer-local-completion-map
-	    minibuffer-local-must-match-map
-	    minibuffer-local-isearch-map)
- [escape] 'minibuffer-keyboard-quit)
-

Home & end

-
(general-def :states '(normal insert visual)
-  "<home>" 'beginning-of-line
-  "<end>" 'end-of-line)
-

My leader

-

Using the SPC key as a leader key, like in Doom Emacs or Spacemacs.

-
(general-create-definer my-leader-def
-  :keymaps 'override
-  :prefix "SPC"
-  :states '(normal motion emacs))
-
-(general-def :states '(normal motion emacs)
-  "SPC" nil
-  "M-SPC" (general-key "SPC"))
-
-(general-def :states '(insert)
-  "M-SPC" (general-key "SPC" :state 'normal))
-
-(my-leader-def "?" 'which-key-show-top-level)
-(my-leader-def "E" 'eval-expression)
-

general.el has a nice integration with which-key, so I use that to show more descriptive annotations for certain groups of keybindings (the default annotation is just prefix).

-
(my-leader-def
-  "a" '(:which-key "apps"))
-

Universal argument

-

Change the universal argument to M-u. I use C-u to scroll up, as I’m used to from vim.

-
(general-def
-  :keymaps 'universal-argument-map
-  "M-u" 'universal-argument-more)
-(general-def
-  :keymaps 'override
-  :states '(normal motion emacs insert visual)
-  "M-u" 'universal-argument)
-

Profiler

-

The built-in profiler is a magnificent tool to troubleshoot performance issues.

-
(my-leader-def
-  :infix "P"
-  "" '(:which-key "profiler")
-  "s" 'profiler-start
-  "e" 'profiler-stop
-  "p" 'profiler-report)
-

Buffer switching

-

Some keybindings I used in vim to switch buffers and can’t let go of. But I think I started to use these less since I made an attempt in i3 integration.

-
(general-define-key
-  :keymaps 'override
-  "C-<right>" 'evil-window-right
-  "C-<left>" 'evil-window-left
-  "C-<up>" 'evil-window-up
-  "C-<down>" 'evil-window-down
-  "C-h" 'evil-window-left
-  "C-l" 'evil-window-right
-  "C-k" 'evil-window-up
-  "C-j" 'evil-window-down
-  "C-x h" 'previous-buffer
-  "C-x l" 'next-buffer)
-
-(general-define-key
- :keymaps 'evil-window-map
- "x" 'kill-buffer-and-window
- "d" 'kill-current-buffer)
-

winner-mode to keep the history of window states.

-

It doesn’t play too well with perspective.el, that is it has a single history list for all of the perspectives. But it is still quite usable.

-
(winner-mode 1)
-
-(general-define-key
- :keymaps 'evil-window-map
- "u" 'winner-undo
- "U" 'winner-redo)
-

Buffer management

-
(my-leader-def
-  :infix "b"
-  "" '(:which-key "buffers")
-  "s" '((lambda () (interactive) (switch-to-buffer (persp-scratch-buffer)))
-	:which-key "*scratch*")
-  "m" '((lambda () (interactive) (persp-switch-to-buffer "*Messages*"))
-	:which-key "*Messages*")
-  "l" 'next-buffer
-  "h" 'previous-buffer
-  "k" 'kill-buffer
-  "b" 'persp-ivy-switch-buffer
-  "r" 'revert-buffer
-  "u" 'ibuffer)
-

xref

-

Some keybindings for xref and go to definition.

-
(general-nmap
-  "gD" 'xref-find-definitions-other-window
-  "gr" 'xref-find-references
-  "gd" 'evil-goto-definition)
-
-(my-leader-def
-  "fx" 'xref-find-apropos)
-

Folding

-

There are multiple ways to fold text in Emacs.

-

The most versatile is the built-in hs-minor-mode, which seems to work out of the box for Lisps, C-like languages and Python. outline-minor-mode works for org-mode, LaTeX and the like. There is a 3rd-party solution origami.el, but I don’t use it at the moment.

-

Evil does a pretty good job of abstracting the first two with a set of vim-like keybindings. I was using SPC in vim, but as now this isn’t an option, I set TAB to toggle folding.

-
(general-nmap :keymaps '(hs-minor-mode-map outline-minor-mode-map)
-  "ze" 'hs-hide-level
-  "TAB" 'evil-toggle-fold)
-

Zoom

-
(defun my/zoom-in ()
-  "Increase font size by 10 points"
-  (interactive)
-  (set-face-attribute 'default nil
-		      :height
-		      (+ (face-attribute 'default :height) 10)))
-
-(defun my/zoom-out ()
-  "Decrease font size by 10 points"
-  (interactive)
-  (set-face-attribute 'default nil
-		      :height
-		      (- (face-attribute 'default :height) 10)))
-
-;; change font size, interactively
-(global-set-key (kbd "C-+") 'my/zoom-in)
-(global-set-key (kbd "C-=") 'my/zoom-out)
-

i3 integration

-

One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3.

-

But why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn’t feel snappy enough. Also, I really like i3’s tree-based layout structure; I feel like it fits my workflow much better than anything else I tried, including the master/stack paradigm of XMonad​, for instance.

-

One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs that are configured in an actual programing language, like the mentioned XMonad, Qtile, Awesome, etc. But I think i3’s extensibility is underappreciated, although the contents of this section may lie closer to the limits of how far one can go there.

-

The basic idea is to launch a normal i3 command with i3-msg in case the current window is not Emacs, otherwise pass that command to Emacs with emacsclient. In Emacs, execute the command if possible, otherwise pass the command back to i3.

-

This may seem like a lot of overhead, but I didn’t feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard, the interaction feels seamless. The only concern is that this command flow is vulnerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.

-

One interesting observation here is that Emacs windows and X windows are sort of one-level entities, so I can talk just about “windows”.

-

At any rate, we need a script to do the i3 -> Emacs part:

-
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
-    command="(my/emacs-i3-integration \"$@\")"
-    emacsclient -e "$command"
-else
-    i3-msg $@
-fi
-

This script is being run from the i3 configuration.

-

For this to work, we need to make sure that Emacs starts a server, so here is an expression to do just that:

-
(add-hook 'after-init-hook #'server-start)
-

And here is a simple macro to do the Emacs -> i3 part:

-
(defmacro i3-msg (&rest args)
-  `(start-process "emacs-i3-windmove" nil "i3-msg" ,@args))
-

Now we have to handle the required set of i3 commands. It is worth noting here that I’m not trying to implement a general mechanism to apply i3 commands to Emacs, rather I’m implementing a small subset that I use in my i3 configuration and that maps reasonably to the Emacs concepts.

-

Also, I use evil-mode and generally configure the software to have vim-style bindings where possible. So if you don’t use evil-mode you’d have to detangle the given functions from evil, but then, I guess, you do not use super+hjkl to manage windows either.

-

First, for the focus command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and windmove have the same names for directions, so the function is rather straightforward.

-

One caveat here is that the minibuffer is always the bottom-most Emacs window, so it is necessary to check for that as well.

-
(defun my/emacs-i3-windmove (dir)
-  (let ((other-window (windmove-find-other-window dir)))
-    (if (or (null other-window) (window-minibuffer-p other-window))
-	(i3-msg "focus" (symbol-name dir))
-      (windmove-do-window-select dir))))
-

For the move I want the following behavior:

- -

For the first part, window-swap-states with windmove-find-other-window do well enough.

-

evil-move-window works well for the second part. By itself it doesn’t behave quite like i3, for instance, (evil-move-window 'right) in a three-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described here.

-

So here is a simple predicate which checks whether there is space in the given direction.

-
(defun my/emacs-i3-direction-exists-p (dir)
-  (cl-some (lambda (dir)
-	  (let ((win (windmove-find-other-window dir)))
-	    (and win (not (window-minibuffer-p win)))))
-	(pcase dir
-	  ('width '(left right))
-	  ('height '(up down)))))
-

And the implementation of the move command.

-
(defun my/emacs-i3-move-window (dir)
-  (let ((other-window (windmove-find-other-window dir))
-	(other-direction (my/emacs-i3-direction-exists-p
-			  (pcase dir
-			    ('up 'width)
-			    ('down 'width)
-			    ('left 'height)
-			    ('right 'height)))))
-    (cond
-     ((and other-window (not (window-minibuffer-p other-window)))
-      (window-swap-states (selected-window) other-window))
-     (other-direction
-      (evil-move-window dir))
-     (t (i3-msg "move" (symbol-name dir))))))
-

Next on the line are resize grow and resize shrink. evil-window- functions do nicely for this task.

-

This function also checks whether there is space to resize in the given direction with the help of the predicate defined above. The command is forwarded back to i3 if there is not.

-
(defun my/emacs-i3-resize-window (dir kind value)
-  (if (or (one-window-p)
-	  (not (my/emacs-i3-direction-exists-p dir)))
-      (i3-msg "resize" (symbol-name kind) (symbol-name dir)
-	      (format "%s px or %s ppt" value value))
-    (setq value (/ value 2))
-    (pcase kind
-      ('shrink
-       (pcase dir
-	 ('width
-	  (evil-window-decrease-width value))
-	 ('height
-	  (evil-window-decrease-height value))))
-      ('grow
-       (pcase dir
-	 ('width
-	  (evil-window-increase-width value))
-	 ('height
-	  (evil-window-increase-height value)))))))
-

transpose-frame is a package to “transpose” the current frame layout, which behaves someone similar to the layout toggle split command in i3, so I’ll use it as well.

-
(use-package transpose-frame
-  :straight t
-  :commands (transpose-frame))
-

Finally, the entrypoint for the Emacs integration. In addition to the commands defined above, it processes split and kill commands and passes every other command back to i3.

-
(defun my/emacs-i3-integration (command)
-  (pcase command
-    ((rx bos "focus")
-     (my/emacs-i3-windmove
-      (intern (elt (split-string command) 1))))
-    ((rx bos "move")
-     (my/emacs-i3-move-window
-      (intern (elt (split-string command) 1))))
-    ((rx bos "resize")
-     (my/emacs-i3-resize-window
-       (intern (elt (split-string command) 2))
-       (intern (elt (split-string command) 1))
-       (string-to-number (elt (split-string command) 3))))
-    ("layout toggle split" (transpose-frame))
-    ("split h" (evil-window-split))
-    ("split v" (evil-window-vsplit))
-    ("kill" (evil-quit))
-    (- (i3-msg command))))
-

Editing helpers

-

Visual fill column mode

-
(use-package visual-fill-column
-  :straight t
-  :config
-  (add-hook 'visual-fill-column-mode-hook
-	    (lambda () (setq visual-fill-column-center-text t))))
-

smartparens

-

A minor mode to deal with pairs. Its functionality overlaps with evil-surround, but smartparens provides the most comfortable way to do stuff like automatically insert pairs.

-

References:

- - -
(use-package smartparens
-  :straight t)
-

Aggressive Indent

-

A package to keep the code intended.

-

Doesn’t work too well with many ecosystems because the LSP-based indentation is rather slow, but nice for Lisps.

-

References:

- - -
(use-package aggressive-indent
-  :commands (aggressive-indent-mode)
-  :straight t)
-

Delete trailing whitespace

-

Delete trailing whitespace on save, unless in particular modes where trailing whitespace is important, like Markdown.

-
(setq my/trailing-whitespace-modes '(markdown-mode))
-
-(require 'cl-extra)
-
-(add-hook 'before-save-hook
-	  (lambda ()
-	    (unless (cl-some #'derived-mode-p my/trailing-whitespace-modes)
-	      (delete-trailing-whitespace))))
-

Expand region

-
(use-package expand-region
-  :straight t
-  :commands (er/expand-region)
-  :init
-  (general-nmap "+" 'er/expand-region))
-

Various settings

-

Tabs

-

Some default settings to manage tabs.

-
(setq tab-always-indent nil)
-
-(setq-default default-tab-width 4)
-(setq-default tab-width 4)
-(setq-default evil-indent-convert-tabs nil)
-(setq-default indent-tabs-mode nil)
-(setq-default tab-width 4)
-(setq-default evil-shift-round nil)
-

Scrolling config

-
(setq scroll-conservatively scroll-margin)
-(setq scroll-step 1)
-(setq scroll-preserve-screen-position t)
-(setq scroll-error-top-bottom t)
-(setq mouse-wheel-progressive-speed nil)
-(setq mouse-wheel-inhibit-click-time nil)
-

Clipboard

-
(setq select-enable-clipboard t)
-(setq mouse-yank-at-point t)
-

Backups

-
(setq backup-inhibited t)
-(setq auto-save-default nil)
-

Undo Tree

-

Replaces Emacs build-in sequential undo system with a tree-based one. Probably one of the greatest features of Emacs as a text editor.

-

References:

- - -
(use-package undo-tree
-  :straight t
-  :config
-  (global-undo-tree-mode)
-  (setq undo-tree-visualizer-diff t)
-  (setq undo-tree-visualizer-timestamps t)
-
-  (my-leader-def "u" 'undo-tree-visualize)
-  (fset 'undo-auto-amalgamate 'ignore)
-  (setq undo-limit 6710886400)
-  (setq undo-strong-limit 100663296)
-  (setq undo-outer-limit 1006632960))
-

Help

-

helpful package improves the *help* buffer.

-
(use-package helpful
-  :straight t
-  :commands (helpful-callable
-	     helpful-variable
-	     helpful-key
-	     helpful-macro
-	     helpful-function
-	     helpful-command))
-
-

As I use C-h to switch buffers, I moved the help to SPC-h with the code below. Of course, I didn’t type it all by hand.

-
(my-leader-def
-  :infix "h"
-  "" '(:which-key "help")
-  "RET" 'view-order-manuals
-  "." 'display-local-help
-  "?" 'help-for-help
-  "C" 'describe-coding-system
-  "F" 'Info-goto-emacs-command-node
-  "I" 'describe-input-method
-  "K" 'Info-goto-emacs-key-command-node
-  "L" 'describe-language-environment
-  "P" 'describe-package
-  "S" 'info-lookup-symbol
-  "a" 'helm-apropos
-  "b" 'describe-bindings
-  "c" 'describe-key-briefly
-  "d" 'apropos-documentation
-  "e" 'view-echo-area-messages
-  "f" 'helpful-function
-  "g" 'describe-gnu-project
-  "h" 'view-hello-file
-  "i" 'info
-  "k" 'helpful-key
-  "l" 'view-lossage
-  "m" 'describe-mode
-  "n" 'view-emacs-news
-  "o" 'describe-symbol
-  "p" 'finder-by-keyword
-  "q" 'help-quit
-  "r" 'info-emacs-manual
-  "s" 'describe-syntax
-  "t" 'help-with-tutorial
-  "v" 'helpful-variable
-  "w" 'where-is
-  "<f1>" 'help-for-help)
-

Ivy, counsel, swiper

-

Minibuffer completion tools for Emacs.

-

References:

- - -
(use-package ivy
-  :straight t
-  :config
-  (setq ivy-use-virtual-buffers t)
-  (ivy-mode))
-
-(use-package counsel
-  :straight t
-  :after ivy
-  :config
-  (counsel-mode))
-
-(use-package swiper
-  :defer t
-  :straight t)
-

ivy-rich

-

ivy-rich provides a more informative interface for ivy.

-
(use-package ivy-rich
-  :straight t
-  :after ivy
-  :config
-  (ivy-rich-mode 1)
-  (setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line))
-

prescient

-

A package that enhances sorting & filtering of candidates. ivy-prescient adds integration with Ivy.

-

References:

- - -
(use-package ivy-prescient
-  :straight t
-  :after counsel
-  :config
-  (ivy-prescient-mode +1)
-  (setq ivy-prescient-retain-classic-highlighting t)
-  (prescient-persist-mode 1)
-  (setq ivy-prescient-sort-commands
-	'(:not swiper
-	       swiper-isearch
-	       ivy-switch-buffer
-	       ;; ivy-resume
-	       ;; ivy--restore-session
-	       lsp-ivy-workspace-symbol
-	       dap-switch-stack-frame
-	       my/dap-switch-stack-frame
-	       dap-switch-session
-	       dap-switch-thread
-	       counsel-grep
-	       ;; counsel-find-file
-	       counsel-git-grep
-	       counsel-rg
-	       counsel-ag
-	       counsel-ack
-	       counsel-fzf
-	       counsel-pt
-	       counsel-imenu
-	       counsel-yank-pop
-	       counsel-recentf
-	       counsel-buffer-or-recentf
-	       proced-filter-interactive
-	       proced-sort-interactive))
-  ;; Do not use prescient in find-file
-  (ivy--alist-set 'ivy-sort-functions-alist #'read-file-name-internal #'ivy-sort-file-function-default))
-

Keybindings

-
(my-leader-def
-  :infix "f"
-  "" '(:which-key "various completions")'
-  ;; "b" 'counsel-switch-buffer
-  "b" 'persp-ivy-switch-buffer
-  "e" 'conda-env-activate
-  "f" 'project-find-file
-  "c" 'counsel-yank-pop
-  "a" 'counsel-rg
-  "A" 'counsel-ag)
-
-(general-define-key
- :states '(insert normal)
- "C-y" 'counsel-yank-pop)
-
-(my-leader-def "SPC" 'ivy-resume)
-(my-leader-def "s" 'swiper-isearch
-  "S" 'swiper-all)
-
-(general-define-key
- :keymaps '(ivy-minibuffer-map swiper-map)
- "M-j" 'ivy-next-line
- "M-k" 'ivy-previous-line
- "<C-return>" 'ivy-call
- "M-RET" 'ivy-immediate-done
- [escape] 'minibuffer-keyboard-quit)
-

Treemacs

- - - - - - - - - - - - - -
TypeNote
TODOEnable modeline only for particular treemacs instances
-

Treemacs calls itself a tree layout file explorer, but looks more like a project and workspace management system.

-

Integrates with evil, magit, projectile and perspective. The latter is particularly great - each perspective can have its own treemacs workspace!

-
(use-package treemacs
-  :straight t
-  :commands (treemacs treemacs-switch-workspace treemacs-edit-workspace)
-  :config
-  (setq treemacs-follow-mode nil)
-  (setq treemacs-follow-after-init nil)
-  (setq treemacs-space-between-root-nodes nil)
-  (treemacs-git-mode 'extended)
-  (add-to-list 'treemacs-pre-file-insert-predicates #'treemacs-is-file-git-ignored?)
-  (general-define-key
-   :keymaps 'treemacs-mode-map
-   [mouse-1] #'treemacs-single-click-expand-action
-   "M-l" #'treemacs-root-down
-   "M-h" #'treemacs-root-up))
-
-(use-package treemacs-evil
-  :after (treemacs evil)
-  :straight t)
-
-(use-package treemacs-magit
-  :after (treemacs magit)
-  :straight t)
-
-(use-package treemacs-perspective
-  :after (treemacs perspective)
-  :straight t
-  :config
-  (treemacs-set-scope-type 'Perspectives))
-
-(general-define-key
- :keymaps '(normal override global)
- "C-n" 'treemacs)
-
-(general-define-key
- :keymaps '(treemacs-mode-map) [mouse-1] #'treemacs-single-click-expand-action)
-
-(my-leader-def
-  :infix "t"
-  "" '(:which-key "treemacs")
-  "w" 'treemacs-switch-workspace
-  "e" 'treemacs-edit-workspaces)
-

Helper functions

-

Function to open dired and vterm at given nodes.

-
(defun my/treemacs-open-dired ()
-  "Open dired at given treemacs node"
-  (interactive)
-  (let (path (treemacs--prop-at-point :path))
-    (dired path)))
-
-(defun my/treemacs-open-vterm ()
-  "Open vterm at given treemacs node"
-  (interactive)
-  (let ((default-directory (file-name-directory (treemacs--prop-at-point :path))))
-    (vterm)))
-
-(with-eval-after-load 'treemacs
-  (general-define-key
-   :keymaps 'treemacs-mode-map
-   :states '(treemacs)
-   "gd" 'my/treemacs-open-dired
-   "gt" 'my/treemacs-open-vterm
-   "`" 'my/treemacs-open-vterm))
-

Custom icons

-
;; (treemacs-define-custom-icon (concat " " (all-the-icons-fileicon "typescript")) "spec.ts")
-;; (setq treemacs-file-extension-regex (rx "." (or "spec.ts" (+ (not "."))) eos))
-

Projectile

-

Projectile gives a bunch of useful functions for managing projects, like finding files within a project, fuzzy-find, replace, etc.

-

defadvice is meant to speed projectile up with TRAMP a bit.

-
(use-package projectile
-  :straight t
-  :config
-  (projectile-mode +1)
-  (setq projectile-project-search-path '("~/Code" "~/Documents"))
-  (defadvice projectile-project-root (around ignore-remote first activate)
-    (unless (file-remote-p default-directory) ad-do-it)))
-
-(use-package counsel-projectile
-  :after (counsel projectile)
-  :straight t)
-
-(use-package treemacs-projectile
-  :after (treemacs projectile)
-  :straight t)
-
-(my-leader-def
-  "p" '(:keymap projectile-command-map :which-key "projectile"))
-
-(general-nmap "C-p" 'counsel-projectile-find-file)
-

Company

-

A completion framework for Emacs.

-

References:

- - -
(use-package company
-  :straight t
-  :config
-  (global-company-mode)
-  (setq company-idle-delay (if my/lowpower 0.5 0.125))
-  (setq company-dabbrev-downcase nil)
-  (setq company-show-numbers t))
-
-(general-imap "C-SPC" 'company-complete)
-

A company frontend with nice icons. Disabled since the base company got icons support and since company-box has some issues with spaceline.

-
(use-package company-box
-  :straight t
-  :disabled
-  :if (and (display-graphic-p) (not my/lowpower))
-  :after (company)
-  :hook (company-mode . company-box-mode))
-

Git & Magit

-

Magic is a git interface for Emacs. The closest non-Emacs alternative (sans actual clones) I know is lazygit, which I used before Emacs.

-

git-gutter is a package which shows git changes for each line (added/changed/deleted lines).

-

git-timemachine allows to visit previous versions of a file.

-
(use-package magit
-  :straight t
-  :commands (magit-status magit-file-dispatch)
-  :config
-  (setq magit-blame-styles
-	'((margin
-	   (margin-format    . ("%a %A %s"))
-	   (margin-width     . 42)
-	   (margin-face      . magit-blame-margin)
-	   (margin-body-face . (magit-blame-dimmed)))
-	  (headings
-	   (heading-format   . "%-20a %C %s\n"))
-	  (highlight
-	   (highlight-face   . magit-blame-highlight))
-	  (lines
-	   (show-lines       . t)
-	   (show-message     . t)))))
-
-(use-package git-gutter
-  :straight t
-  :if (not my/slow-ssh)
-  :config
-  (global-git-gutter-mode +1))
-
-(use-package git-timemachine
-  :straight t
-  :commands (git-timemachine))
-
-(my-leader-def
-  "m" 'magit
-  "M" 'magit-file-dispatch)
-

Editorconfig

-

Editorconfig support for Emacs.

-

References:

- - -
(use-package editorconfig
-  :straight t
-  :config
-  (unless my/slow-ssh (editorconfig-mode 1))
-  (add-to-list 'editorconfig-indentation-alist
-	       '(emmet-mode emmet-indentation)))
-

Snippets

-

A snippet system for Emacs and a collection of pre-built snippets.

-

yasnippet-snippets has to be loaded before yasnippet for user snippets to override the pre-built ones.

-

References:

- - -
(use-package yasnippet-snippets
-  :straight t)
-
-(use-package yasnippet
-  :straight t
-  :config
-  (setq yas-snippet-dirs
-	`(,(concat (expand-file-name user-emacs-directory) "snippets")
-	  yasnippet-snippets-dir))
-  (setq yas-triggers-in-field t)
-  (yas-global-mode 1))
-
-(general-imap "M-TAB" 'company-yasnippet)
-

Time trackers

-

A bunch of time trackers I use.

-

References:

- -

WakaTime

-

Before I figure out how to package this for Guix:

- - -
(use-package wakatime-mode
-  :straight (:host github :repo "SqrtMinusOne/wakatime-mode")
-  :if (not (or my/is-termux my/remote-server))
-  :config
-  (setq wakatime-ignore-exit-codes '(0 1 102))
-  (advice-add 'wakatime-init :after (lambda () (setq wakatime-cli-path "/home/pavel/bin/wakatime-cli")))
-  ;; (setq wakatime-cli-path (executable-find "wakatime"))
-  (global-wakatime-mode))
-

ActivityWatch

-
(use-package request
-  :straight t)
-
-(use-package activity-watch-mode
-  :straight t
-  :if (not (or my/is-termux my/remote-server))
-  :config
-  (global-activity-watch-mode))
-

UI

-

General UI & GUI Settings

-

Disable GUI elements

-
(unless my/is-termux
-  (tool-bar-mode -1)
-  (menu-bar-mode -1)
-  (scroll-bar-mode -1))
-

Transparency. Not setting it now, as I’m using picom.

-
;; (set-frame-parameter (selected-frame) 'alpha '(90 . 90))
-;; (add-to-list 'default-frame-alist '(alpha . (90 . 90)))
-

Prettify symbols. Also not setting it, ligatures seem to be enough for me.

-
;; (global-prettify-symbols-mode)
-

No start screen

-
(setq inhibit-startup-screen t)
-

Visual bell

-
(setq visible-bell 0)
-

y or n instead of yes or no

-
(defalias 'yes-or-no-p 'y-or-n-p)
-

Hide mouse cursor while typing

-
(setq make-pointer-invisible t)
-

Line numbers. There seems to be a catch with the relative number setting:

- -

visual option seems to be less of a problem in most cases.

-
(global-display-line-numbers-mode 1)
-(line-number-mode nil)
-(setq display-line-numbers-type 'visual)
-(column-number-mode)
-

Show pairs

-
(show-paren-mode 1)
-

Word wrapping. These settings aren’t too obvious compared to :set wrap from vim:

- - -
(setq word-wrap 1)
-(global-visual-line-mode 1)
-

Highlight current line

-
(global-hl-line-mode 1)
-

Theme & global stuff

-

Dim inactive buffers.

-
(use-package auto-dim-other-buffers
-  :straight t
-  :if (display-graphic-p)
-  :config
-  (auto-dim-other-buffers-mode t))
-

My colorscheme of choice.

-
(use-package doom-themes
-  :straight t
-  :if (not my/is-termux)
-  :config
-  (setq doom-themes-enable-bold t
-	doom-themes-enable-italic t)
-  (if my/remote-server
-      (load-theme 'doom-gruvbox t)
-    (load-theme 'doom-palenight t))
-  (doom-themes-visual-bell-config)
-  (setq doom-themes-treemacs-theme "doom-colors")
-  (doom-themes-treemacs-config))
-

Custom theme

-

A custom theme, dependent on Doom. I set all my custom variables there.

-

A custom theme is necessary because if one calls custom-set-faces and custom-set-variables in code, whenever a variable is changed and saved in a customize buffer, data from all calls of these functions is saved as well.

-

Also, a hook allows me to change doom-theme more or less at will, although I do that only to switch to a light theme once in a blue moon.

-
(unless my/is-termux
-  (deftheme my-theme-1)
-
-  (defun my/update-my-theme (&rest _)
-    (custom-theme-set-faces
-     'my-theme-1
-     `(tab-bar-tab ((t (
-			:background ,(doom-color 'bg)
-			:foreground ,(doom-color 'yellow)
-			:underline ,(doom-color 'yellow)))))
-     `(org-block ((t (:background ,(color-darken-name (doom-color 'bg) 3)))))
-     `(org-block-begin-line ((t (
-				 :background ,(color-darken-name (doom-color 'bg) 3)
-				 :foreground ,(doom-color 'grey)))))
-     `(auto-dim-other-buffers-face ((t (:background ,(color-darken-name (doom-color 'bg) 3)))))
-     `(aweshell-alert-buffer-face ((t (:foreground ,(doom-color 'red) :weight bold))))
-     `(aweshell-alert-command-face ((t (:foreground ,(doom-color 'yellow) :weight bold))))
-     `(epe-pipeline-delimiter-face ((t (:foreground ,(doom-color 'green)))))
-     `(epe-pipeline-host-face ((t (:foreground ,(doom-color 'blue)))))
-     `(epe-pipeline-time-face ((t (:foreground ,(doom-color 'yellow)))))
-     `(epe-pipeline-user-face ((t (:foreground ,(doom-color 'red)))))
-     `(elfeed-search-tag-face ((t (:foreground ,(doom-color 'yellow)))))
-     `(notmuch-wash-cited-text ((t (:foreground ,(doom-color 'yellow)))))
-     `(spaceline-evil-emacs ((t :background ,(doom-color 'bg)
-				:foreground ,(doom-color 'fg))))
-     `(spaceline-evil-insert ((t :background ,(doom-color 'green)
-				 :foreground ,(doom-color 'base0))))
-     `(spaceline-evil-motion ((t :background ,(doom-color 'magenta)
-				 :foreground ,(doom-color 'base0))))
-     `(spaceline-evil-normal ((t :background ,(doom-color 'blue)
-				 :foreground ,(doom-color 'base0))))
-     `(spaceline-evil-replace ((t :background ,(doom-color 'yellow)
-				  :foreground ,(doom-color 'base0))))
-     `(spaceline-evil-visual ((t :background ,(doom-color 'grey)
-				 :foreground ,(doom-color 'base0)))))
-    (custom-theme-set-variables
-     'my-theme-1
-     `(aweshell-invalid-command-color ,(doom-color 'red))
-     `(aweshell-valid-command-color ,(doom-color 'green)))
-    (enable-theme 'my-theme-1))
-
-  (advice-add 'load-theme :after #'my/update-my-theme)
-  (when (fboundp 'doom-color)
-    (my/update-my-theme)))
-

Font

-

To install a font, download the font and unpack it into the .local/share/fonts directory. Create one if it doesn’t exist.

-

As I use nerd fonts elsewhere, I use one in Emacs as well.

-

References:

- - -
(set-frame-font "JetBrainsMono Nerd Font 10" nil t)
-

To make the icons work (e.g. in the Doom Modeline), run M-x all-the-icons-install-fonts. The package definition is somewhere later in the config.

-

Custom frame title

-

Title format, which looks something like emacs:project@hostname.

-
(setq-default frame-title-format
-	      '(""
-		"emacs"
-		(:eval
-		 (let ((project-name (projectile-project-name)))
-		   (if (not (string= "-" project-name))
-		       (format ":%s@%s" project-name (system-name))
-		     (format "@%s" (system-name)))))))
-

perspective.el

-

perspective.el is a package which provides gives Emacs capacities to group buffers into “perspectives”, which are like workspaces in tiling WMs.

-

An advantage over tab-bar.el is that perspective.el has better capacities for managing buffers, e.g. gives an ibuffer-like interface inside a perspective.

-

However, I don’t like that list of workspaces is displayed inside the modeline rather than in an actual bar on the top of the frame. I may look into that later.

-
(use-package perspective
-  :straight t
-  :init
-  ;; (setq persp-show-modestring 'header)
-  (setq persp-sort 'created)
-  :config
-  (persp-mode)
-  (my-leader-def "x" '(:keymap perspective-map :which-key "perspective"))
-  (general-define-key
-   :keymaps 'override
-   :states '(normal emacs)
-   "gt" 'persp-next
-   "gT" 'persp-prev
-   "gn" 'persp-switch
-   "gN" 'persp-kill)
-  (general-define-key
-   :keymaps 'perspective-map
-   "b" 'persp-ivy-switch-buffer
-   "x" 'persp-ivy-switch-buffer
-   "u" 'persp-ibuffer))
-

Some functions

-

Move the current buffer to a perspective and switch to it.

-
(defun my/persp-move-window-and-switch ()
-  (interactive)
-  (let* ((buffer (current-buffer)))
-    (call-interactively #'persp-switch)
-    (persp-set-buffer (buffer-name buffer))
-    (switch-to-buffer buffer)))
-

Copy the current buffer to a perspective and switch to it.

-
(defun my/persp-copy-window-and-switch ()
-  (interactive)
-  (let* ((buffer (current-buffer)))
-    (call-interactively #'persp-switch)
-    (persp-add-buffer (buffer-name buffer))
-    (switch-to-buffer buffer)))
-

Add keybindings to the default map.

-
(with-eval-after-load 'perspective
-  (general-define-key
-   :keymaps 'perspective-map
-   "m" #'my/persp-move-window-and-switch
-   "f" #'my/persp-copy-window-and-switch))
-

OFF (OFF) Tab bar

-

I rely rather heavily on tab-bar in my workflow. I have a suspicion I’m not using it the intended way, but that works for me.

-

For now switched to perspective.el, so the following block is not tangled.

-

Setup

-
(general-define-key
- :keymaps 'override
- :states '(normal emacs)
- "gt" 'tab-bar-switch-to-next-tab
- "gT" 'tab-bar-switch-to-prev-tab
- "gn" 'tab-bar-new-tab)
-
-(setq tab-bar-show 1)
-(setq tab-bar-tab-hints t)
-(setq tab-bar-tab-name-function 'tab-bar-tab-name-current-with-count)
-
-;; Tabs
-(general-nmap "gn" 'tab-new)
-(general-nmap "gN" 'tab-close)
-

My title

-

Prepend tab name with the shortened projectile project title

-
(setq my/project-title-separators "[-_ ]")
-
-(setq my/project-names-override-alist
-      '((".password-store" . "pass")))
-
-(defun my/shorten-project-name-elem (elem crop)
-  (if (string-match "^\\[.*\\]$" elem)
-      (concat "["
-	      (my/shorten-project-name-elem (substring elem 1 (- (length elem) 1)) crop)
-	      "]")
-    (let* ((prefix (car (s-match my/project-title-separators elem)))
-	   (rest
-	    (substring
-	     (if prefix
-		 (substring elem (length prefix))
-	       elem)
-	     0 (if crop 1 nil))))
-      (concat prefix rest))))
-
-(defun my/shorten-project-name (project-name)
-  (or
-   (cdr (assoc project-name my/project-names-override-alist))
-   (let ((elems (s-slice-at my/project-title-separators project-name)))
-     (concat
-      (apply
-       #'concat
-       (cl-mapcar (lambda (elem) (my/shorten-project-name-elem elem t)) (butlast elems)))
-      (my/shorten-project-name-elem (car (last elems)) nil)))))
-
-(defun my/tab-bar-name-function ()
-  (let ((project-name (projectile-project-name)))
-    (if (string= "-" project-name)
-	(tab-bar-tab-name-current-with-count)
-      (concat "[" (my/shorten-project-name project-name) "] "
-	      (replace-regexp-in-string "<.*>" "" (tab-bar-tab-name-current-with-count))))))
-
-(setq tab-bar-tab-name-function #'my/tab-bar-name-function)
-

Doom Modeline

-

A modeline from Doom Emacs.

-

A big advantage of this package is that it just works out of the box and does not require much customization. For now I opted out of it in favour of spaceline because I want to have a more powerline-ish look.

-

References:

- - -
(use-package doom-modeline
-  :straight t
-  ;; :if (not (display-graphic-p))
-  :init
-  (setq doom-modeline-env-enable-python nil)
-  (setq doom-modeline-env-enable-go nil)
-  (setq doom-modeline-buffer-encoding 'nondefault)
-  (setq doom-modeline-hud t)
-  :config
-  (doom-modeline-mode 1)
-  (setq doom-modeline-minor-modes nil)
-  (setq doom-modeline-buffer-state-icon nil))
-

OFF (OFF) Spaceline

-

A modeline from Spacemacs. It provides a different look than Doom modeline, but also needs to be tuned more.

-
(use-package spaceline
-  :straight t
-  :disabled
-  :config
-  <<spaceline-conf>>)
-

Perspectives

-

Definining a bunch of custom powerline segments. First, a list of perspectives.

-
(defun my/format-perspective-list ()
-  (format "[%s]"
-	  (let ((curr (persp-current-name)))
-	    (mapconcat
-	     (lambda (name)
-	       (if (string-equal name curr)
-		   (propertize
-		    name
-		    'face
-		    'persp-selected-face)
-		 name))
-	     (persp-names)
-	     "|"))))
-
-(defvar my/spaceline-persp-list "")
-
-(defun my/persp-update-advice (&rest _)
-  (setq my/spaceline-persp-list (my/format-perspective-list)))
-
-(advice-add #'persp-update-modestring :after #'my/persp-update-advice)
-(add-hook 'buffer-list-update-hook #'my/persp-update-advice)
-(add-hook 'find-file-hook #'my/persp-update-advice)
-
-(spaceline-define-segment perspective
-  "Perspective.el"
-  my/spaceline-persp-list)
-

EXWM workspace

-

Current EXWM workspace. The variable is being set in the EXWM config, the segment just displays it.

-
(defvar my/exwm-mode-line-info-no-props "")
-
-(spaceline-define-segment exwm
-  my/exwm-mode-line-info-no-props)
-

Debug

-

Indicators for debug-on-error and debug-on-quit.

-
(spaceline-define-segment debug-on-error
-  (when debug-on-error
-    (propertize
-     ""
-     'face 'warning
-     'local-map (let ((map (make-sparse-keymap)))
-		  (define-key map
-		    [mode-line mouse-1]
-		    #'toggle-debug-on-error)
-		  map))))
-
-(spaceline-define-segment debug-on-quit
-  (when debug-on-quit
-    (propertize
-     ""
-     'face 'error
-     'local-map (let ((map (make-sparse-keymap)))
-		  (define-key map
-		    [mode-line mouse-1]
-		    #'toggle-debug-on-quit)
-		  map))))
-

My theme

-

And a custom spaceline config with segments I like.

-
(require 'spaceline-config)
-
-(spaceline-compile
-  "my"
-  '((evil-state :priority 100 :face (spaceline-highlight-face-evil-state))
-    (buffer-modified :priority 50)
-    (version-control :priority 25 :when active)
-    (buffer-id :priority 90))
-  '(;; (global)
-    (exwm :when active)
-    (perspective :when active)
-    (flycheck-error :when active)
-    (flycheck-warning :when active)
-    (debug-on-error :when active)
-    (debug-on-quit :when active)
-    (major-mode :when active :priority 90)
-    (selection-info :priority 95)
-    (line-column :when active  :priority 99)
-    (hud :when active :priority 99)))
-
-(spaceline-ml-my)
-
-(setq-default mode-line-format '("%e" (:eval (spaceline-ml-my))))
-(setq mode-line-format '("%e" (:eval (spaceline-ml-my))))
-

Font stuff

-

Ligatures

-

Ligature setup for the JetBrainsMono font.

-
(use-package ligature
-  :straight (:host github :repo "mickeynp/ligature.el")
-  :if (display-graphic-p)
-  :config
-  (ligature-set-ligatures
-   '(
-     typescript-mode
-     js2-mode
-     vue-mode
-     svelte-mode
-     scss-mode
-     php-mode
-     python-mode
-     js-mode
-     markdown-mode
-     clojure-mode
-     go-mode
-     sh-mode
-     haskell-mode
-     web-mode)
-   '("--" "---" "==" "===" "!=" "!==" "=!=" "=:=" "=/=" "<="
-     ">=" "&&" "&&&" "&=" "++" "+++" "***" ";;" "!!" "??"
-     "?:" "?." "?=" "<:" ":<" ":>" ">:" "<>" "<<<" ">>>"
-     "<<" ">>" "||" "-|" "_|_" "|-" "||-" "|=" "||=" "##"
-     "###" "####" "#{" "#[" "]#" "#(" "#?" "#_" "#_(" "#:"
-     "#!" "#=" "^=" "<$>" "<$" "$>" "<+>" "<+" "+>" "<*>"
-     "<*" "*>" "</" "</>" "/>" "<!--" "<#--" "-->" "->" "->>"
-     "<<-" "<-" "<=<" "=<<" "<<=" "<==" "<=>" "<==>" "==>" "=>"
-     "=>>" ">=>" ">>=" ">>-" ">-" ">--" "-<" "-<<" ">->" "<-<"
-     "<-|" "<=|" "|=>" "|->" "<->" "<~~" "<~" "<~>" "~~" "~~>"
-     "~>" "~-" "-~" "~@" "[||]" "|]" "[|" "|}" "{|" "[<"
-     ">]" "|>" "<|" "||>" "<||" "|||>" "<|||" "<|>" "..." ".."
-     ".=" ".-" "..<" ".?" "::" ":::" ":=" "::=" ":?" ":?>"
-     "//" "///" "/*" "*/" "/=" "//=" "/==" "@_" "__"))
-  (global-ligature-mode t))
-

Icons

-
(use-package all-the-icons
-  :if (display-graphic-p)
-  :straight t)
-

Highlight todo

-
(use-package hl-todo
-  :hook (prog-mode . hl-todo-mode)
-  :straight t)
-

Text highlight improvements

-

Highlight indent guides.

-
(use-package highlight-indent-guides
-  :straight t
-  :if (not (or my/lowpower my/remote-server))
-  :hook (
-	 (prog-mode . highlight-indent-guides-mode)
-	 (vue-mode . highlight-indent-guides-mode)
-	 (LaTeX-mode . highlight-indent-guides-mode))
-  :config
-  (setq highlight-indent-guides-method 'bitmap)
-  (setq highlight-indent-guides-bitmap-function 'highlight-indent-guides--bitmap-line))
-

Rainbow parentheses.

-
(use-package rainbow-delimiters
-  :straight t
-  :if (not my/lowpower)
-  :hook ((prog-mode . rainbow-delimiters-mode))
-  ;; :commands (rainbow-delimiters-mode)
-  ;; :init
-  ;; (add-hook 'prog-mode-hook
-  ;;           (lambda ()
-  ;;             (unless (org-in-src-block-p)
-  ;;               (rainbow-delimiters-mode))))
-  )
-

Highlight colors

-
(use-package rainbow-mode
-  :commands (rainbow-mode)
-  :straight t)
-

Dired

-

Dired is a built-in file manager. I use it as my primary file manager, hence the top level of config.

-

Basic config & keybindings

-

My config mostly follows ranger’s and vifm’s keybindings which I’m used to.

-
(use-package dired
-  :ensure nil
-  :custom ((dired-listing-switches "-alh --group-directories-first"))
-  :commands (dired)
-  :config
-  (setq dired-dwim-target t)
-  (setq wdired-allow-to-change-permissions t)
-  (setq wdired-create-parent-directories t)
-  (setq dired-recursive-copies 'always)
-  (setq dired-recursive-deletes 'always)
-  (setq dired-kill-when-opening-new-dired-buffer t)
-  (add-hook 'dired-mode-hook
-	    (lambda ()
-	      (setq truncate-lines t)
-	      (visual-line-mode nil)))
-  (general-define-key
-   :states '(normal)
-   :keymaps 'dired-mode-map
-   "h" 'dired-up-directory
-   "l" 'dired-find-file
-   "=" 'dired-narrow
-   "-" 'dired-create-empty-file
-   "~" 'vterm
-   "<left>" 'dired-up-directory
-   "<right>" 'dired-find-file
-   "M-<return>" 'dired-open-xdg))
-
-(defun my/dired-home ()
-  "Open dired at $HOME"
-  (interactive)
-  (dired (expand-file-name "~")))
-
-(my-leader-def
-  "ad" #'dired
-  "aD" #'my/dired-home)
-

Addons

-

I used to use dired+, which provides a lot of extensions for dired functionality, but it also creates some new problems, so I opt out of it. Fortunately, the one feature I want from this package - adding more colors to dired buffers - is available as a separate package.

-
(use-package diredfl
-  :straight t
-  :after dired
-  :config
-  (diredfl-global-mode 1))
-

Reuse the current dired buffer instead of spamming new ones. Looks like not neccesary with Emacs 28.1

-
(use-package dired-single
-  :after dired
-  :disabled
-  :straight t)
-

Display icons for files.

- - - - - - - - - - - - - -
NoteType
ACHTUNGThis plugin is slow as hell with TRAMP or in gnu/store
-
(use-package all-the-icons-dired
-  :straight t
-  :if (not (or my/lowpower my/slow-ssh (not (display-graphic-p))))
-  :hook (dired-mode . (lambda ()
-			(unless (string-match-p "/gnu/store" default-directory)
-			  (all-the-icons-dired-mode))))
-  :config
-  (advice-add 'dired-add-entry :around #'all-the-icons-dired--refresh-advice)
-  (advice-add 'dired-remove-entry :around #'all-the-icons-dired--refresh-advice)
-  (advice-add 'dired-kill-subdir :around #'all-the-icons-dired--refresh-advice))
-

Provides stuff like dired-open-xdg

-
(use-package dired-open
-  :straight t
-  :commands (dired-open-xdg))
-

vifm-like filter

-
(use-package dired-narrow
-  :straight t
-  :commands (dired-narrow)
-  :config
-  (general-define-key
-   :keymaps 'dired-narrow-map
-   [escape] 'keyboard-quit))
-

Display git info, such as the last commit for file and stuff. It’s pretty useful but also slows down Dired a bit, hence I don’t turn it out by default.

-
(use-package dired-git-info
-  :straight t
-  :after dired
-  :if (not my/slow-ssh)
-  :config
-  (general-define-key
-   :keymap 'dired-mode-map
-   :states '(normal emacs)
-   ")" 'dired-git-info-mode))
-

Subdirectories

-

Subdirectories are one of the interesting features of Dired. It allows displaying multiple folders on the same window.

-

I add my own keybindings and some extra functionality.

-
(defun my/dired-open-this-subdir ()
-  (interactive)
-  (dired (dired-current-directory)))
-
-(defun my/dired-kill-all-subdirs ()
-  (interactive)
-  (let ((dir dired-directory))
-    (kill-buffer (current-buffer))
-    (dired dir)))
-
-(with-eval-after-load 'dired
-  (general-define-key
-   :states '(normal)
-   :keymaps 'dired-mode-map
-   "s" nil
-   "ss" 'dired-maybe-insert-subdir
-   "sl" 'dired-maybe-insert-subdir
-   "sq" 'dired-kill-subdir
-   "sk" 'dired-prev-subdir
-   "sj" 'dired-next-subdir
-   "sS" 'my/dired-open-this-subdir
-   "sQ" 'my/dired-kill-all-subdirs
-   (kbd "TAB") 'dired-hide-subdir))
-

TRAMP

-

TRAMP is a package that provides remote editing capacities. It is particularly useful for remote server management.

-

One of the reasons why TRAMP may be slow is that some plugins do too many requests to the filesystem. To debug these issues, set the following variable to 6:

-
(setq tramp-verbose 1)
-

To check if a file is remote, you can use file-remote-p. E.g. (file-remote-p default-directory) for a current buffer. The problem with this approach is that it’s rather awkward to add these checks in every hook, especially for global modes, so for now, I just set an environment variable for Emacs which disables these modes.

-

So far I have found the following problematic plugins:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PluginNoteSolution
editorconfiglooks for .editorconfig in the file treedo not enable globally
all-the-icons-diredruns test on every file in the directorydisable
projectilelooks for .git, .svn, etcadvice projectile-file-name
lspdoes a whole lot of stuffdisable
git-gutterruns gitdisable
vtermno proper TRAMP integrationuse eshell or shell
-

At any rate, it’s usable, although not perfect.

-

Some other optimization settings:

-
(setq remote-file-name-inhibit-cache nil)
-(setq vc-ignore-dir-regexp
-      (format "\\(%s\\)\\|\\(%s\\)"
-	      vc-ignore-dir-regexp
-	      tramp-file-name-regexp))
-

Set the default shell to bin/bash for TRAMP or on a remote server.

-
(when (or my/remote-server my/slow-ssh)
-  (setq explicit-shell-file-name "/bin/bash"))
-

Also, here is a hack to make TRAMP find ls on Guix:

-
(with-eval-after-load 'tramp
-  (setq tramp-remote-path
-	(append tramp-remote-path
-		'(tramp-own-remote-path))))
-

Bookmarks

-

A simple bookmark list for Dired, mainly to use with TRAMP. I may look into a proper bookmarking system later.

-

Bookmarks are listed in the private.el file, which has an expression like this:

-
(setq my/dired-bookmarks
-      '(("sudo" . "/sudo::/")))
-

The file itself is encrypted with yadm.

-
(defun my/dired-bookmark-open ()
-  (interactive)
-  (let ((bookmarks
-	 (mapcar
-	  (lambda (el) (cons (format "%-30s %s" (car el) (cdr el)) (cdr el)))
-	  my/dired-bookmarks)))
-    (dired
-     (cdr
-      (assoc
-       (completing-read "Dired: " bookmarks nil nil "^")
-       bookmarks)))))
-

Shells

-

vterm

-

My terminal emulator of choice.

-

References:

- -

Configuration

-

I use the package from the Guix repository to avoid building libvterm.

-
(use-package vterm
-  ;; :straight t
-  :commands (vterm vterm-other-window)
-  :config
-  (setq vterm-kill-buffer-on-exit t)
-
-  (add-hook 'vterm-mode-hook
-	    (lambda ()
-	      (setq-local global-display-line-numbers-mode nil)
-	      (display-line-numbers-mode 0)))
-
-
-  (advice-add 'evil-collection-vterm-insert
-	      :before (lambda (&rest args)
-			(ignore-errors
-			  (apply #'vterm-reset-cursor-point args))))
-
-  (general-define-key
-   :keymaps 'vterm-mode-map
-   "M-q" 'vterm-send-escape
-
-   "C-h" 'evil-window-left
-   "C-l" 'evil-window-right
-   "C-k" 'evil-window-up
-   "C-j" 'evil-window-down
-
-   "C-<right>" 'evil-window-right
-   "C-<left>" 'evil-window-left
-   "C-<up>" 'evil-window-up
-   "C-<down>" 'evil-window-down
-
-   "M-<left>" 'vterm-send-left
-   "M-<right>" 'vterm-send-right
-   "M-<up>" 'vterm-send-up
-   "M-<down>" 'vterm-send-down)
-
-  (general-define-key
-   :keymaps 'vterm-mode-map
-   :states '(normal insert)
-   "<home>" 'vterm-beginning-of-line
-   "<end>" 'vterm-end-of-line)
-
-  (general-define-key
-   :keymaps 'vterm-mode-map
-   :states '(insert)
-   "C-r" 'vterm-send-C-r
-   "C-k" 'vterm-send-C-k
-   "C-j" 'vterm-send-C-j
-   "M-l" 'vterm-send-right
-   "M-h" 'vterm-send-left
-   "M-k" 'vterm-send-up
-   "M-j" 'vterm-send-down))
-

Subterminal

-

Open a terminal in the lower third of the frame with the ` key.

-
(add-to-list 'display-buffer-alist
-	     `(,"vterm-subterminal.*"
-	       (display-buffer-reuse-window
-		display-buffer-in-side-window)
-	       (side . bottom)
-	       (reusable-frames . visible)
-	       (window-height . 0.33)))
-
-(defun my/toggle-vterm-subteminal ()
-  "Toogle subteminal."
-  (interactive)
-  (let
-      ((vterm-window
-	(seq-find
-	 (lambda (window)
-	   (string-match
-	    "vterm-subterminal.*"
-	    (buffer-name (window-buffer window))))
-	 (window-list))))
-    (if vterm-window
-	(if (eq (get-buffer-window (current-buffer)) vterm-window)
-	    (kill-buffer (current-buffer))
-	  (select-window vterm-window))
-      (vterm-other-window "vterm-subterminal"))))
-
-(unless my/slow-ssh
-  (general-nmap "`" 'my/toggle-vterm-subteminal)
-  (general-nmap "~" 'vterm))
-

Dired integration

-

A function to get pwd for vterm. Couldn’t find a built-in function for some reason, but this seems to be working fine:

-
(defun my/vterm-get-pwd ()
-  (if vterm--process
-      (file-truename (format "/proc/%d/cwd" (process-id vterm--process)))
-    default-directory))
-

Now we can open dired for vterm pwd:

-
(defun my/vterm-dired-other-window ()
-  "Open dired in vterm pwd in other window"
-  (interactive)
-  (dired-other-window (my/vterm-get-pwd)))
-
-(defun my/vterm-dired-replace ()
-  "Replace vterm with dired"
-  (interactive)
-  (let ((pwd (my/vterm-get-pwd)))
-    (kill-process vterm--process)
-    (dired pwd)))
-

The second function is particularly handy because that way I can alternate between vterm and dired.

-

Keybindings:

-
(with-eval-after-load 'vterm
-  (general-define-key
-   :keymaps 'vterm-mode-map
-   :states '(normal)
-   "gd" #'my/vterm-dired-other-window
-   "gD" #'my/vterm-dired-replace))
-

With-editor integration

-

A package used by Magit to use the current Emacs instance as the $EDITOR.

-

That is, with the help of this function, I can just write e <filename>, edit the file, and then return to the same vterm buffer. No more running vim inside Emacs.

-
(use-package with-editor
-  :straight t
-  :after (vterm)
-  :config
-  (add-hook 'vterm-mode-hook 'with-editor-export-editor))
-

Eshell

-

A shell written in Emacs lisp. I don’t use it as of now, but keep the config just in case.

-
(defun my/configure-eshell ()
-  (add-hook 'eshell-pre-command-hook 'eshell-save-some-history)
-  (add-to-list 'eshell-output-filter-functions 'eshell-truncate-buffer)
-  (setq eshell-history-size 10000)
-  (setq eshell-hist-ingnoredups t)
-  (setq eshell-buffer-maximum-lines 10000)
-
-  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "<home>") 'eshell-bol)
-  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "C-r") 'counsel-esh-history)
-  (general-define-key
-   :states '(normal)
-   :keymaps 'eshell-mode-map
-   (kbd "C-h") 'evil-window-left
-   (kbd "C-l") 'evil-window-right
-   (kbd "C-k") 'evil-window-up
-   (kbd "C-j") 'evil-window-down))
-
-(use-package eshell
-  :ensure nil
-  :after evil-collection
-  :commands (eshell)
-  :config
-  (add-hook 'eshell-first-time-mode-hook 'my/configure-eshell 90)
-  (when my/slow-ssh
-    (add-hook 'eshell-mode-hook
-	      (lambda ()
-		(setq-local company-idle-delay 1000))))
-  (setq eshell-banner-message ""))
-
-(use-package aweshell
-  :straight (:repo "manateelazycat/aweshell" :host github)
-  :after eshell
-  :config
-  (setq eshell-highlight-prompt nil)
-  (setq eshell-prompt-function 'epe-theme-pipeline))
-
-(use-package eshell-info-banner
-  :defer t
-  :if (not my/slow-ssh)
-  :straight (eshell-info-banner :type git
-				:host github
-				:repo "phundrak/eshell-info-banner.el")
-  :hook (eshell-banner-load . eshell-info-banner-update-banner))
-
-(when my/slow-ssh
-  (general-nmap "`" 'aweshell-dedicated-toggle)
-  (general-nmap "~" 'eshell))
-

Org Mode

-

The best feature of Emacs. Just after every other best feature of Emacs, probably.

-

References:

- -

Installation & basic settings

-

Use the built-in org mode.

-
(use-package org
-  :straight t
-  :if (not my/remote-server)
-  :defer t
-  :init
-  (setq org-directory (expand-file-name "~/Documents/org-mode"))
-  :config
-  (setq org-startup-indented t)
-  (setq org-return-follows-link t)
-  (setq org-src-tab-acts-natively nil)
-  (add-hook 'org-mode-hook 'smartparens-mode)
-  (add-hook 'org-agenda-mode-hook
-	    (lambda ()
-	      (visual-line-mode -1)
-	      (toggle-truncate-lines 1)
-	      (display-line-numbers-mode 0)))
-  (add-hook 'org-mode-hook
-	    (lambda ()
-	      (rainbow-delimiters-mode -1)))
-  (require 'org-tempo)
-  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
-  (add-to-list 'org-structure-template-alist '("py" . "src python"))
-  (add-to-list 'org-structure-template-alist '("sq" . "src sql"))
-  <<org-crypt-setup>>
-  (unless my/is-termux
-    <<org-lang-setup>>)
-  <<org-ui-setup>>
-  <<org-keys-setup>>
-  <<org-productivity-setup>>)
-

Encryption

-
(require 'org-crypt)
-(org-crypt-use-before-save-magic)
-(setq org-tags-exclude-from-inheritance (quote ("crypt")))
-(setq org-crypt-key "C1EC867E478472439CC82410DE004F32AFA00205")
-

org-contrib

-

org-contrib is a package with various additions to Org. I use the following:

- - -
(use-package org-contrib
-  :straight (org-contrib
-	     :type git
-	     :host nil
-	     :repo "https://git.sr.ht/~bzg/org-contrib"
-	     :build t)
-  :after (org)
-  :config
-  (require 'ox-extra)
-  (require 'ol-notmuch)
-  (ox-extras-activate '(latex-header-blocks ignore-headlines)))
-

Integration with evil

-
(use-package evil-org
-  :straight t
-  :hook (org-mode . evil-org-mode)
-  :config
-  (add-hook 'evil-org-mode-hook
-	    (lambda ()
-	      (evil-org-set-key-theme '(navigation insert textobjects additional calendar todo))))
-  (add-to-list 'evil-emacs-state-modes 'org-agenda-mode)
-  (require 'evil-org-agenda)
-  (evil-org-agenda-set-keys))
-

Literate programing

-

Python & Jupyter

-

Use jupyter kernels for Org Mode.

-

References:

- - -
(use-package jupyter
-  :straight t
-  :if (not my/is-termux)
-  :init
-  (my-leader-def "ar" 'jupyter-run-repl))
-

Refresh kernelspecs.

-

Kernelspecs by default are hashed, so even switching Anaconda environments doesn’t change the kernel (i.e. kernel from the first environment is run after the switch to the second one).

-
(defun my/jupyter-refresh-kernelspecs ()
-  "Refresh Jupyter kernelspecs"
-  (interactive)
-  (jupyter-available-kernelspecs t))
-

Also, if some kernel wasn’t present at the moment of the load of emacs-jupyter, it won’t be added to the org-src-lang-modes list. E.g. I have Hy kernel installed in a separate Anaconda environment, so if Emacs hasn’t been launched in this environment, I wouldn’t be able to use hy in org-src blocks.

-

Fortunately, emacs-jupyter provides a function for that problem as well.

-
(defun my/jupyter-refesh-langs ()
-  "Refresh Jupyter languages"
-  (interactive)
-  (org-babel-jupyter-aliases-from-kernelspecs t))
-

Hy

-
(use-package ob-hy
-  :straight t)
-

View HTML in browser

-

Open HTML in the begin_export block with xdg-open.

-
(setq my/org-view-html-tmp-dir "/tmp/org-html-preview/")
-
-(use-package f
-  :straight t)
-
-(defun my/org-view-html ()
-  (interactive)
-  (let ((elem (org-element-at-point))
-	(temp-file-path (concat my/org-view-html-tmp-dir (number-to-string (random (expt 2 32))) ".html")))
-    (cond
-     ((not (eq 'export-block (car elem)))
-      (message "Not in an export block!"))
-     ((not (string-equal (plist-get (car (cdr elem)) :type) "HTML"))
-      (message "Export block is not HTML!"))
-     (t (progn
-	  (f-mkdir my/org-view-html-tmp-dir)
-	  (f-write (plist-get (car (cdr elem)) :value) 'utf-8 temp-file-path)
-	  (start-process "org-html-preview" nil "xdg-open" temp-file-path))))))
-

PlantUML

-
(setq org-plantuml-executable-path "/home/pavel/.guix-extra-profiles/emacs/emacs/bin/plantuml")
-(setq org-plantuml-exec-mode 'plantuml)
-(add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
-

Setup

-

Enable languages

-
(org-babel-do-load-languages
- 'org-babel-load-languages
- '((emacs-lisp . t)
-   (python . t)
-   (sql . t)
-   ;; (typescript .t)
-   (hy . t)
-   (shell . t)
-   (plantuml . t)
-   (octave . t)
-   (jupyter . t)))
-
-(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images)
-

Use Jupyter block instead of built-in Python.

-
(org-babel-jupyter-override-src-block "python")
-(org-babel-jupyter-override-src-block "hy")
-

Turn of some minor modes in source blocks.

-
(add-hook 'org-src-mode-hook
-	  (lambda ()
-	    ;; (hs-minor-mode -1)
-	    ;; (electric-indent-local-mode -1)
-	    ;; (rainbow-delimiters-mode -1)
-	    (highlight-indent-guides-mode -1)))
-

Async code blocks evaluations. Jupyter blocks have a built-in async, so they are set as ignored.

-
(use-package ob-async
-  :straight t
-  :after (org)
-  :config
-  (setq ob-async-no-async-languages-alist '("python" "hy" "jupyter-python" "jupyter-octave")))
-

Managing Jupyter kernels

-

Functions for managing local Jupyter kernels.

-

my/insert-jupyter-kernel inserts a path to an active Jupyter kernel to the buffer. Useful to quickly write a header like:

-
#+PROPERTY: header-args:python :session <path-to-kernel>
-

my/jupyter-connect-repl opens a emacs-jupyter REPL, connected to an active kernel. my/jupyter-qtconsole runs a standalone Jupyter QtConsole.

-

Requirements: ss

-
(setq my/jupyter-runtime-folder (expand-file-name "~/.local/share/jupyter/runtime"))
-
-(defun my/get-open-ports ()
-  (mapcar
-   #'string-to-number
-   (split-string (shell-command-to-string "ss -tulpnH | awk '{print $5}' | sed -e 's/.*://'") "\n")))
-
-(defun my/list-jupyter-kernel-files ()
-  (mapcar
-   (lambda (file) (cons (car file) (cdr (assq 'shell_port (json-read-file (car file))))))
-   (sort
-    (directory-files-and-attributes my/jupyter-runtime-folder t ".*kernel.*json$")
-    (lambda (x y) (not (time-less-p (nth 6 x) (nth 6 y)))))))
-
-(defun my/select-jupyter-kernel ()
-  (let ((ports (my/get-open-ports))
-	(files (my/list-jupyter-kernel-files)))
-    (completing-read
-     "Jupyter kernels: "
-     (seq-filter
-      (lambda (file)
-	(member (cdr file) ports))
-      files))))
-
-(defun my/insert-jupyter-kernel ()
-  "Insert a path to an active Jupyter kernel into the buffer"
-  (interactive)
-  (insert (my/select-jupyter-kernel)))
-
-(defun my/jupyter-connect-repl ()
-  "Open an emacs-jupyter REPL, connected to a Jupyter kernel"
-  (interactive)
-  (jupyter-connect-repl (my/select-jupyter-kernel) nil nil nil t))
-
-(defun my/jupyter-qtconsole ()
-  "Open Jupyter QtConsole, connected to a Jupyter kernel"
-  (interactive)
-  (start-process "jupyter-qtconsole" nil "setsid" "jupyter" "qtconsole" "--existing"
-		 (file-name-nondirectory (my/select-jupyter-kernel))))
-

I’ve also noticed that there are JSON files left in the runtime folder whenever the kernel isn’t stopped correctly. So here is a cleanup function.

-
(defun my/jupyter-cleanup-kernels ()
-  (interactive)
-  (let* ((ports (my/get-open-ports))
-	 (files (my/list-jupyter-kernel-files))
-	 (to-delete (seq-filter
-		     (lambda (file)
-		       (not (member (cdr file) ports)))
-		     files)))
-    (when (and (length> to-delete 0)
-	       (y-or-n-p (format "Delete %d files?" (length to-delete))))
-      (dolist (file to-delete)
-	(delete-file (car file))))))
-

Do not wrap the output in emacs-jupyter

-

Emacs-jupyter has its own insertion mechanisms, which always prepends output statements with :. That is not desirable in cases where a kernel supports only plain output, e.g. calysto_hy kernel.

-

So there we have a minor mode that overrides this behavior.

-
(defun my/jupyter-org-scalar (value)
-  (cond
-   ((stringp value) value)
-   (t (jupyter-org-scalar value))))
-
-(define-minor-mode my/emacs-jupyter-raw-output
-  "Make emacs-jupyter do raw output")
-
-(defun my/jupyter-org-scalar-around (fun value)
-  (if my/emacs-jupyter-raw-output
-      (my/jupyter-org-scalar value)
-    (funcall fun value)))
-
-(advice-add 'jupyter-org-scalar :around #'my/jupyter-org-scalar-around)
-

Wrap source code output

-

A function to remove :RESULTS: drawer from the results. Once again, necessary because emacs-jupyter doesn’t seem to respect :results raw.

-
(defun my/org-strip-results (data)
-  (replace-regexp-in-string ":\\(RESULTS\\|END\\):\n" "" data))
-

And an all-in-one function to:

- -

As for now, looks sufficient to format source code outputs to get a tolerable LaTeX.

-
(defun my/org-caption-wrap (data &optional name caption attrs strip-drawer src-wrap)
-  (let* ((data-s (if (and strip-drawer (not (string-empty-p strip-drawer)))
-		     (my/org-strip-results data)
-		   data))
-	 (drawer-start (if (string-match-p "^:RESULTS:.*" data-s) 10 0)))
-    (concat
-     (substring data-s 0 drawer-start)
-     (and name (not (string-empty-p name)) (concat "#+NAME:" name "\n"))
-     (and caption (not (string-empty-p caption)) (concat "#+CAPTION:" caption "\n"))
-     (and attrs (not (string-empty-p attrs)) (concat "#+ATTR_LATEX:" attrs "\n"))
-     (if (and src-wrap (not (string-empty-p src-wrap)))
-	 (concat "#+begin_src " src-wrap "\n"
-		 (substring data-s drawer-start)
-		 (when (not (string-match-p ".*\n" data-s)) "\n")
-		 "#+end_src")
-       (substring data-s drawer-start)))))
-

To use, add the following snippet to the org file:

-
#+NAME: out_wrap
-#+begin_src emacs-lisp :var data="" caption="" name="" attrs="" strip-drawer="" src-wrap="" :tangle no :exports none
-(my/org-caption-wrap data name caption attrs strip-drawer src-wrap)
-#+end_src
-

Example usage:

-
:post out_wrap(name="fig:chart", caption="График", data=*this*)
-

Managing a literate programming project

-

A few tricks to do literate programming.

-

I prefer to put the org files to a separate directory (e.g. org). So I’ve come up with the following solution to avoid manually prefixing the :tangle arguments.

-

Set up the following argument with the path to the project root:

-
#+PROPERTY: PRJ-DIR ..
-

A function to do the prefixing:

-
(defun my/org-prj-dir (path)
-  (expand-file-name path (org-entry-get nil "PRJ-DIR" t)))
-

Example usage is as follows:

-
:tangle (my/org-prj-dir "sqrt_data/api/__init__.py")
-

Productivity & Knowledge management

-

My ongoing effort to get a productivity setup in Org.

-

Some inspiration:

- -

Used files

-
(setq org-agenda-files '("inbox.org" "projects.org" "work.org" "sem-11.org" "life.org"))
-;; (setq org-default-notes-file (concat org-directory "/notes.org"))
-

Hotkeys

-
(my-leader-def
-  :infix "o"
-  "" '(:which-key "org-mode")
-  "c" 'org-capture
-  "a" 'org-agenda)
-

Refile targets

-
(setq org-refile-targets
-      '(("projects.org" :maxlevel . 2)
-	("work.org" :maxlevel . 2)
-	("sem-11.org" :maxlevel . 3)
-	("life.org" :maxlevel . 2)))
-(setq org-refile-use-outline-path 'file)
-(setq org-outline-path-complete-in-steps nil)
-

Capture templates & various settings

-

Settings for Org capture mode. The goal here is to have a non-disruptive process to capture various ideas.

-
(defun my/generate-inbox-note-name ()
-  (format
-   "%s/inbox-notes/%s.org"
-   org-directory
-   (format-time-string "%Y%m%d%H%M%S")))
-
-(setq org-capture-templates
-      `(("i" "Inbox" entry  (file "inbox.org")
-	 ,(concat "* TODO %?\n"
-		  "/Entered on/ %U"))
-	("e" "email" entry (file "inbox.org")
-	 ,(concat "* TODO %:from %:subject \n"
-		  "/Entered on/ %U\n"
-		  "/Received on/ %:date-timestamp-inactive\n"
-		  "%a\n"))
-	("f" "elfeed" entry (file "inbox.org")
-	 ,(concat "* TODO %:elfeed-entry-title\n"
-		  "/Entered on/ %U\n"
-		  "%a\n"))
-	("n" "note" entry (file my/generate-inbox-note-name)
-	 ,(concat "* %?\n"
-		  "/Entered on/ %U"))))
-

Effort estimation

-
(add-to-list 'org-global-properties
-	     '("Effort_ALL" . "0 0:05 0:10 0:15 0:30 0:45 1:00 2:00 4:00"))
-

Log DONE time

-
(setq org-log-done 'time)
-

Trello sync

-

Some of the projects I’m participating in are managed via Trello, so I use org-trello to keep track of them. The package has a remarkably awkward keybindings setup, so my effort to call my-leader-def to set keybindings I like is no less awkward.

-

Also, trello files are huge and have a lot of information and tasks which do not concern me, so I don’t add them to org-agenda-files.

-
(setq org-trello-files
-      (thread-last (concat org-directory "/trello")
-	(directory-files)
-	(seq-filter
-	 (lambda (f) (string-match-p (rx ".org" eos) f)))
-	(mapcar
-	 (lambda (f) (concat org-directory "/trello/" f)))))
-
(use-package org-trello
-  :straight (:build (:not native-compile))
-  :commands (org-trello-mode)
-  :init
-  (setq org-trello-current-prefix-keybinding "C-c o")
-  (setq org-trello-add-tags nil)
-
-  (add-hook 'org-mode-hook
-	    (lambda ()
-	      (when (string-match-p (rx "trello") (or (buffer-file-name) ""))
-		(org-trello-mode))))
-  :config
-  (eval
-   `(my-leader-def
-      :infix "o t"
-      :keymaps '(org-trello-mode-map)
-      "" '(:which-key "trello")
-      ,@(mapcan
-	 (lambda (b) (list (nth 1 b) (macroexp-quote (nth 0 b))))
-	 org-trello-interactive-command-binding-couples))))
-

org-ql

-

org-ql is a package to query the org files. I’m using it in my review workflow and for custom agenda views.

-
(use-package org-ql
-  :straight (:fetcher github
-		      :repo "alphapapa/org-ql"
-		      :files (:defaults (:exclude "helm-org-ql.el"))))
-

Custom agendas

-

Some custom agendas to fit my workflow.

-

Despite the fact that I don’t add org-trello-files to org-agenda-files I still want to see them in agenda, so I use org-ql-block from org-ql.

-
(defun my/org-scheduled-get-time ()
-  (let ((scheduled (org-get-scheduled-time (point))))
-    (if scheduled
-	(format-time-string "%Y-%m-%d" scheduled)
-      "")))
-
-(setq org-agenda-custom-commands
-      `(("p" "My outline"
-	 ((agenda "")
-	  (todo "NEXT"
-		((org-agenda-prefix-format "  %i %-12:c [%e] ")
-		 (org-agenda-overriding-header "Next tasks")))
-	  (org-ql-block
-	   `(and
-	     (regexp ,(rx ":orgtrello_users:" (* nonl) "sqrtminusone"))
-	     (todo)
-	     (deadline))
-	   ((org-agenda-files ',org-trello-files)
-	    (org-ql-block-header "Trello assigned")))
-	  (tags-todo "inbox"
-		     ((org-agenda-overriding-header "Inbox")
-		      (org-agenda-prefix-format " %i %-12:c")
-		      (org-agenda-hide-tags-regexp ".")))
-	  (tags-todo "+waitlist+SCHEDULED<=\"<+14d>\""
-		     ((org-agenda-overriding-header "Waitlist")
-		      (org-agenda-hide-tags-regexp "waitlist")
-		      (org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)")))))
-	("tp" "Personal tasks"
-	 ((tags-todo "personal"
-		     ((org-agenda-prefix-format "  %i %-12:c [%e] ")))))))
-

Review workflow

-

My take on a review workflow. As a baseline, I want to have a template that lists the important changes since the last review and other basic information. I’m doing reviews regularly, but the time intervals still may vary, hence this flexibility.

-
Data from git & org-roam
-

First, as I have autocommit set up in my org directory, here is a handy function to get an alist of changed files of a form (status . path). In principle, the rev parameter can be a commit, tag, etc but here I’m interested in a form like @{2021-08-30}.

-
(setq my/git-diff-status
-      '(("A" . added)
-	("C" . copied)
-	("D" . deleted)
-	("M" . modified)
-	("R" . renamed)
-	("T" . type-changed)
-	("U" . unmerged)))
-
-(defun my/get-files-status (rev)
-  (let ((files (shell-command-to-string (concat "git diff --name-status " rev))))
-    (mapcar
-     (lambda (file)
-       (let ((elems (split-string file "\t")))
-	 (cons
-	  (cdr (assoc (car elems) my/git-diff-status))
-	  (nth 1 elems))))
-     (split-string files "\n" t))))
-

I’ll use it to get a list of added and changed Roam files since the last review. Date should have be in a format YYYY-MM-DD.

-
(defun my/org-changed-files-since-date (date)
-  (let ((default-directory org-directory))
-    (my/get-files-status (format "@{%s}" date))))
-

Now we are ready to format this list to insert it into the capture template.

-
(defun my/org-review-format-roam (rev)
-  (let* ((changes (my/org-changed-files-since-date rev))
-	 (new-roam
-	  (seq-filter
-	   (lambda (elem)
-	     (and (eq (car elem) 'added)
-		  (string-match-p (rx bos "roam") (cdr elem))))
-	   changes))
-	 (changed-roam
-	  (seq-filter
-	   (lambda (elem)
-	     (and (eq (car elem) 'modified)
-		  (string-match-p (rx bos "roam") (cdr elem))))
-	   changes)))
-    (concat
-     (unless (seq-empty-p new-roam)
-       (concat "** New Roam entries \n"
-	       (mapconcat
-		(lambda (entry)
-		  (format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
-		new-roam
-		"\n")
-	       "\n"))
-     (unless (seq-empty-p changed-roam)
-       (concat "** Changed Roam entries \n"
-	       (mapconcat
-		(lambda (entry)
-		  (format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
-		changed-roam
-		"\n"))))))
-
Data from org-journal
-

Second, I want to have a list of new jounal entries since the last review.

-
(defun my/org-journal-entries-since-date (rev-date)
-  (mapcar
-   (lambda (date)
-     (let ((time (encode-time (parse-time-string date))))
-       `((file . ,(org-journal--get-entry-path time))
-	 (header . ,(format-time-string org-journal-date-format time)))))
-   (seq-filter
-    (lambda (date) (string-lessp rev-date date))
-    (mapcar
-     (lambda (date)
-       (format "%04d-%02d-%02dT00:00:00+0300" (nth 2 date) (nth 0 date) (nth 1 date)))
-     (org-journal--list-dates)))))
-

Format the results:

-
(defun my/org-review-format-journal (rev-date)
-  (mapconcat
-   (lambda (item)
-     (format "- [[file:%s::*%s][%s]]"
-	     (cdr (assoc 'file item))
-	     (cdr (assoc 'header item))
-	     (cdr (assoc 'header item))))
-   (my/org-journal-entries-since-date rev-date)
-   "\n"))
-
Data from org-agenda via org-ql
-

Third, I want to list some changes in my agenda. This section will change depending on what I’m currently working on.

-

So, here is a list of queries results of which I want to see in the review template. The format is (name date-field order-by-field query).

-
(setq my/org-ql-review-queries
-      `(("Waitlist" scheduled scheduled
-	 (and
-	  (done)
-	  (tags-inherited "waitlist")))
-	("Personal tasks done" closed ,nil
-	 (and
-	  (tags-inherited "personal")
-	  (todo "DONE")))
-	("Attended meetings" closed scheduled
-	 (and
-	  (tags-inherited "meeting")
-	  (todo "PASSED")))
-	("Done project tasks" closed deadline
-	 (and
-	  (todo "DONE")
-	  (ancestors
-	   (heading "Tasks"))))))
-

The query will be executed like this: (and (date-field :from rev-date) query)

-
(defun my/org-review-exec-ql (saved rev-date)
-  (let ((query `(and
-		 (,(nth 1 saved) :from ,rev-date)
-		 ,(nth 3 saved))))
-    (org-ql-query
-      :select #'element
-      :from (org-agenda-files)
-      :where query
-      :order-by (nth 2 saved))))
-

Format one element of query result.

-
(defun my/org-review-format-element (elem)
-  (concat
-   (string-pad
-    (plist-get (cadr elem) :raw-value)
-    40)
-   (when-let (scheduled (plist-get (cadr elem) :scheduled))
-     (concat " [SCHEDULED: " (plist-get (cadr scheduled) :raw-value) "]"))
-   (when-let (deadline (plist-get (cadr elem) :deadline))
-     (concat " [DEADLINE: " (plist-get (cadr deadline) :raw-value) "]"))))
-

Execute all the saved queries and format an Org list for the capture template.

-
(defun my/org-review-format-queries (rev-date)
-  (mapconcat
-   (lambda (results)
-     (concat "** " (car results) "\n"
-	     (string-join
-	      (mapcar (lambda (r) (concat "- " r)) (cdr results))
-	      "\n")
-	     "\n"))
-   (seq-filter
-    (lambda (result)
-      (not (seq-empty-p (cdr result))))
-    (mapcar
-     (lambda (saved)
-       (cons
-	(car saved)
-	(mapcar
-	 #'my/org-review-format-element
-	 (my/org-review-exec-ql saved rev-date))))
-     my/org-ql-review-queries))
-   "\n"))
-
Capture template
-

Now, we have to put all this together and define a capture template for the review.

-

I’ll use a separate directory for the review files, just like for org-journal and org-roam. The filename will have a format YYYY-MM-DD.org, which will also free me from the effort of storing the last review date somewhere.

-

If somehow there are no files in the folder, fallback to the current date minus one week.

-
(setq my/org-review-directory "review")
-
-(defun my/org-review-get-filename ()
-  (concat my/org-review-directory "/" (format-time-string "%Y-%m-%d.org" (current-time))))
-
-(defun my/get-last-review-date ()
-  (substring
-   (or
-    (-max-by
-     'string-greaterp
-     (-filter
-      (lambda (f) (not (or (string-equal f ".") (string-equal f ".."))))
-      (directory-files (f-join org-directory my/org-review-directory))))
-    (format-time-string
-     "%Y-%m-%d"
-     (time-subtract
-      (current-time)
-      (seconds-to-time (* 60 60 24 7)))))
-   0 10))
-

A template looks like this:

-
(setq my/org-review-capture-template
-      `("r" "Review" plain (file ,(my/org-review-get-filename))
-	,(string-join
-	  '("#+TITLE: Review %t"
-	    ""
-	    "Last review date: %(org-timestamp-translate (org-timestamp-from-string (format \"<%s>\" (my/get-last-review-date))))"
-	    ""
-	    "* Roam"
-	    "%(my/org-review-format-roam (my/get-last-review-date))"
-	    "* Journal"
-	    "New journal entries:"
-	    "%(my/org-review-format-journal (my/get-last-review-date))"
-	    "* Agenda"
-	    "%(my/org-review-format-queries (my/get-last-review-date))"
-	    "* Thoughts                                                            :crypt:"
-	    "%?")
-	  "\n")))
-
-(add-to-list 'org-capture-templates my/org-review-capture-template t)
-

Org Journal

-

org-journal is a plugin for maintaining a journal in org mode. I want to have its entries separate from my knowledge base.

-
(use-package org-journal
-  :straight t
-  :if (not my/remote-server)
-  :after org
-  :config
-  (setq org-journal-dir (concat org-directory "/journal"))
-  (setq org-journal-file-type 'weekly)
-  (setq org-journal-file-format "%Y-%m-%d.org")
-  (setq org-journal-date-format "%A, %Y-%m-%d")
-  (setq org-journal-enable-encryption t))
-
-(my-leader-def
-  :infix "oj"
-  "" '(:which-key "org-journal")
-  "j" 'org-journal-new-entry
-  "o" 'org-journal-open-current-journal-file
-  "s" 'org-journal-search)
-

Also, I want to store some information in the journal as properties of the record. So below is a function which does just that.

-

As of now, it stores Emacs version, hostname, location and current EMMS track if there is one.

-
(defun my/set-journal-header ()
-  (org-set-property "Emacs" emacs-version)
-  (org-set-property "Hostname" system-name)
-  (when (boundp 'my/location)
-    (org-set-property "Location" my/location))
-  (when (fboundp 'emms-playlist-current-selected-track)
-    (let ((track (emms-playlist-current-selected-track)))
-      (when track
-	(let ((album (cdr (assoc 'info-album track)))
-	      (artist (or (cdr (assoc 'info-albumartist track))
-			  (cdr (assoc 'info-album track))))
-	      (title (cdr (assoc 'info-title track)))
-	      (string ""))
-	  (when artist
-	    (setq string (concat string "[" artist "] ")))
-	  (when album
-	    (setq string (concat string album " - ")))
-	  (when title
-	    (setq string (concat string title)))
-	  (when (> (length string) 0)
-	    (org-set-property "EMMS_Track" string)))))))
-
-(add-hook 'org-journal-after-entry-create-hook
-	  #'my/set-journal-header)
-

Org Roam

-

org-roam is a plain-text knowledge database.

- - - - - - - - - - - - - - -
Guix dependency
emacs-emacsql-sqlite3
graphviz
-

References:

- - -
(use-package emacsql-sqlite
-  :defer t
-  :straight (:type built-in))
-
-(use-package org-roam
-  :straight (:host github :repo "org-roam/org-roam"
-		   :files (:defaults "extensions/*.el"))
-  :if (not my/remote-server)
-  :after org
-  :init
-  (setq org-roam-directory (concat org-directory "/roam"))
-  (setq org-roam-file-extensions '("org"))
-  (setq org-roam-v2-ack t)
-  (setq orb-insert-interface 'ivy-bibtex)
-  :config
-  (org-roam-setup)
-  (setq org-roam-capture-templates
-	`(("d" "default" plain "%?"
-	   :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
-	   :unnarrowed t)))
-  (require 'org-roam-protocol)
-  (general-define-key
-   :keymaps 'org-roam-mode-map
-   :states '(normal)
-   "TAB" #'magit-section-toggle
-   "q" #'quit-window
-   "k" #'magit-section-backward
-   "j" #'magit-section-forward
-   "gr" #'revert-buffer
-   "RET" #'org-roam-buffer-visit-thing))
-
-(my-leader-def
-  :infix "or"
-  "" '(:which-key "org-roam")
-  "i" 'org-roam-node-insert
-  "r" 'org-roam-node-find
-  "g" 'org-roam-graph
-  "c" 'org-roam-capture
-  "b" 'org-roam-buffer-toggle)
-
-(with-eval-after-load 'org
-  (my-leader-def
-    :keymap 'org-mode-map
-    :infix "or"
-    "t" 'org-roam-tag-add
-    "T" 'org-toam-tag-remove)
-  (general-define-key
-   :keymap 'org-mode-map
-   "C-c i" 'org-id-get-create
-   "C-c l o" 'org-roam-node-insert))
-
org-roam-ui
-

A browser frontend to visualize a Roam directory in a form of a graph.

-
(use-package org-roam-ui
-  :straight (:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
-  :after org-roam
-  ;; :hook (org-roam . org-roam-ui-mode)
-  :init
-  (my-leader-def "oru" #'org-roam-ui-mode))
-
org-roam-protocol
-

Open links such as org-protocol:// from browser. Run M-x server-start for org-protocol to work.

-
[Desktop Entry]
-Name=Org-Protocol
-Exec=emacsclient %u
-Icon=emacs-icon
-Type=Application
-Terminal=false
-MimeType=x-scheme-handler/org-protocol
-

Don’t forget to run the following after setup:

-
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
-

org-ref

- - - - - - - - - - - - - -
TypeDescription
TODOFigure out how not to load Helm
-

org-ref is a package that provides support for various citations & references in Org mode.

-

Useful to use BibTeX citations in LaTeX export.

-

As of now, this package loads Helm on start. To avoid this, I have to exclude Helm from the Package-requires in the org-ref.el file. I haven’t found a way to do this without modifying the package source yet.

-
(use-package org-ref
-  :straight (:files (:defaults (:exclude "*helm*")))
-  :if (not my/remote-server)
-  :init
-  (setq org-ref-completion-library 'org-ref-ivy-cite)
-  (setq bibtex-dialect 'biblatex)
-  (setq org-ref-default-bibliography '("~/Documents/org-mode/bibliography.bib"))
-  (setq reftex-default-bibliography org-ref-default-bibliography)
-  (setq bibtex-completion-bibliography org-ref-default-bibliography)
-  :after (org)
-  :config
-  (general-define-key
-   :keymaps 'org-mode-map
-   :infix "C-c l"
-   "" '(:which-key "org-ref")
-   "l" 'org-ref-ivy-insert-cite-link
-   "r" 'org-ref-ivy-insert-ref-link
-   "h" 'org-ref-cite-hydra/body)
-  (general-define-key
-   :keymaps 'bibtex-mode-map
-   "M-RET" 'org-ref-bibtex-hydra/body)
-  ;; (add-to-list 'orhc-candidate-formats
-  ;;              '("online" . "  |${=key=}| ${title} ${url}"))
-  )
-

org-roam-bibtex

-

Integration with bibtex and org-ref.

-

There are some problems with org roam v2, so I disabled it as of now. I will probably use another way of managing bibliography notes anyway.

-
(use-package org-roam-bibtex
-  :straight (:host github :repo "org-roam/org-roam-bibtex")
-  :after (org-roam org-ref)
-  :disabled
-  :config
-  (org-roam-bibtex-mode))
-

Managing tables

-

I use Org to manage some small tables which I want to process further. So here is a function that saves each table to a CSV file.

-
(defun my/export-org-tables-to-csv ()
-  (interactive)
-  (org-table-map-tables
-   (lambda ()
-     (when-let
-	 (name
-	  (plist-get (cadr (org-element-at-point)) :name))
-       (org-table-export
-	(concat
-	 (file-name-directory
-	  (buffer-file-name))
-	 name ".csv")
-	"orgtbl-to-csv")))))
-

UI

-

OFF (OFF) Instant equations preview

-

Instant math previews for org mode.

-

References:

- - -
(use-package org-latex-impatient
-  :straight (:repo "yangsheng6810/org-latex-impatient"
-		   :branch "master"
-		   :host github)
-  :hook (org-mode . org-latex-impatient-mode)
-  :disabled
-  :init
-  (setq org-latex-impatient-tex2svg-bin
-	"/home/pavel/Programs/miniconda3/lib/node_modules/mathjax-node-cli/bin/tex2svg")
-  (setq org-latex-impatient-scale 1.75)
-  (setq org-latex-impatient-delay 1)
-  (setq org-latex-impatient-border-color "#ffffff"))
-

LaTeX fragments

-

A function to enable LaTeX native highlighting. Not setting this as default, because it loads LaTeX stuff.

-
(defun my/enable-org-latex ()
-  (interactive)
-  (customize-set-variable 'org-highlight-latex-and-related '(native))
-  (add-hook 'org-mode-hook (lambda () (yas-activate-extra-mode 'LaTeX-mode)))
-  (sp-local-pair 'org-mode "$" "$")
-  (sp--remove-local-pair "'"))
-

Call the function before opening an org file or reopen a buffer after calling the function.

-

Scale latex fragments preview.

-
(setq my/org-latex-scale 1.75)
-(setq org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale))
-

Also, LaTeX fragments preview tends to break whenever the are custom #+LATEX_HEADER entries. To circumvent this, I add a custom header and modify the org-preview-latex-process-alist variable

-
(setq my/latex-preview-header "\\documentclass{article}
-\\usepackage[usenames]{color}
-\\usepackage{graphicx}
-\\usepackage{grffile}
-\\usepackage{longtable}
-\\usepackage{wrapfig}
-\\usepackage{rotating}
-\\usepackage[normalem]{ulem}
-\\usepackage{amsmath}
-\\usepackage{textcomp}
-\\usepackage{amssymb}
-\\usepackage{capt-of}
-\\usepackage{hyperref}
-\\pagestyle{empty}")
-
-(setq org-preview-latex-process-alist
-      (mapcar
-       (lambda (item)
-	 (cons
-	  (car item)
-	  (plist-put (cdr item) :latex-header my/latex-preview-header)))
-       org-preview-latex-process-alist))
-

Better headers

-

org-superstar-mode is package that makes Org heading lines look a bit prettier.

-

Disabled it for now because of overlapping functionality with org-bars.

-
(use-package org-superstar
-  :straight t
-  :disabled
-  :hook (org-mode . org-superstar-mode))
-

org-bars highlights Org indentation with bars.

-
(use-package org-bars
-  :straight (:repo "tonyaldon/org-bars" :host github)
-  :if (display-graphic-p)
-  :hook (org-mode . org-bars-mode))
-

Remove the elipsis at the end of folded headlines. The elipsis seems unnecesary with org-bars.

-
(defun my/org-no-ellipsis-in-headlines ()
-  (remove-from-invisibility-spec '(outline . t))
-  (add-to-invisibility-spec 'outline))
-
-(add-hook 'org-mode-hook #'my/org-no-ellipsis-in-headlines)
-

Org Agenda Icons

-

Categories are broad labels to group agenda items.

-
(if (not my/lowpower)
-    (setq org-agenda-category-icon-alist
-	  `(("inbox" ,(list (all-the-icons-faicon "inbox")) nil nil :ascent center)
-	    ("work" ,(list (all-the-icons-faicon "cog")) nil nil :ascent center)
-	    ("education" ,(list (all-the-icons-material "build")) nil nil :ascent center)
-	    ("personal" ,(list (all-the-icons-faicon "music")) nil nil :ascent center)
-	    ("misc" ,(list (all-the-icons-material "archive")) nil nil :ascent center)
-	    ;; ("lesson" ,(list (all-the-icons-faicon "book")) nil nil :ascent center)
-	    ;; ("meeting" ,(list (all-the-icons-material "chat")) nil nil :ascent center)
-	    ;; ("event" ,(list (all-the-icons-octicon "clock")) nil nil :ascent center)
-	    ("." ,(list (all-the-icons-faicon "circle-o")) nil nil :ascent center))))
-

Export

-

General settings

-
;; (setq org-export-backends '(md html latex beamer org))
-

Hugo

-
(use-package ox-hugo
-  :straight t
-  :after ox)
-

Jupyter Notebook

-
(use-package ox-ipynb
-  :straight (:host github :repo "jkitchin/ox-ipynb")
-  :after ox)
-

Html export

-
(use-package htmlize
-  :straight t
-  :after ox
-  :config
-  (setq org-html-htmlize-output-type 'css))
-

LaTeX

-

Add a custom LaTeX template without default packages. Packages are indented to be imported with function from Import *.sty.

-
(defun my/setup-org-latex ()
-  (setq org-latex-prefer-user-labels t)
-  (setq org-latex-compiler "xelatex") ;; Probably not necessary
-  (setq org-latex-pdf-process '("latexmk -outdir=%o %f")) ;; Use latexmk
-  (setq org-latex-listings 'minted) ;; Use minted to highlight source code
-  (setq org-latex-minted-options    ;; Some minted options I like
-	'(("breaklines" "true")
-	  ("tabsize" "4")
-	  ("autogobble")
-	  ("linenos")
-	  ("numbersep" "0.5cm")
-	  ("xleftmargin" "1cm")
-	  ("frame" "single")))
-  ;; Use extarticle without the default packages
-  (add-to-list 'org-latex-classes
-	       '("org-plain-extarticle"
-		 "\\documentclass{extarticle}
-[NO-DEFAULT-PACKAGES]
-[PACKAGES]
-[EXTRA]"
-		 ("\\section{%s}" . "\\section*{%s}")
-		 ("\\subsection{%s}" . "\\subsection*{%s}")
-		 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
-		 ("\\paragraph{%s}" . "\\paragraph*{%s}")
-		 ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
-  ;; Use beamer without the default packages
-  (add-to-list 'org-latex-classes
-	       '("org-latex-beamer"
-		 "\\documentclass{beamer}
-[NO-DEFAULT-PACKAGES]
-[PACKAGES]
-[EXTRA]"
-		 ("beamer" "\\documentclass[presentation]{beamer}"
-		  ("\\section{%s}" . "\\section*{%s}")
-		  ("\\subsection{%s}" . "\\subsection*{%s}")
-		  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))
-
-;; Make sure to eval the function when org-latex-classes list already exists
-(with-eval-after-load 'ox-latex
-  (my/setup-org-latex))
-

Keybindings & stuff

-
(general-define-key
- :keymaps 'org-mode-map
- "C-c d" 'org-decrypt-entry
- "C-c e" 'org-encrypt-entry
- "M-p" 'org-latex-preview
- "M-o" 'org-redisplay-inline-images)
-
-(general-define-key
- :keymaps 'org-mode-map
- :states '(normal emacs)
- "L" 'org-shiftright
- "H" 'org-shiftleft
- "S-<next>" 'org-next-visible-heading
- "S-<prior>" 'org-previous-visible-heading
- "M-0" 'org-next-visible-heading
- "M-9" 'org-previous-visible-heading
- "M-]" 'org-babel-next-src-block
- "M-[" 'org-babel-previous-src-block)
-
-(general-define-key
- :keymaps 'org-agenda-mode-map
- "M-]" 'org-agenda-later
- "M-[" 'org-agenda-earlier)
-
-;; (general-imap :keymaps 'org-mode-map "RET" 'evil-org-return)
-(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c)
-
-;; (my-leader-def "aa" 'org-agenda)
-
-
(defun my/org-link-copy (&optional arg)
-  "Extract URL from org-mode link and add it to kill ring."
-  (interactive "P")
-  (let* ((link (org-element-lineage (org-element-context) '(link) t))
-	  (type (org-element-property :type link))
-	  (url (org-element-property :path link))
-	  (url (concat type ":" url)))
-    (kill-new url)
-    (message (concat "Copied URL: " url))))
-
-(general-nmap :keymaps 'org-mode-map
-    "C-x C-l" 'my/org-link-copy)
-

Open a file from org-directory

-

A function to open a file from org-directory, excluding a few directories like roam and journal.

-
(defun my/org-file-open ()
-  (interactive)
-  (let* ((default-directory org-directory)
-	 (project-files
-	  (seq-filter
-	   (lambda (f)
-	     (and
-	      (string-match-p (rx (* nonl) ".org" eos) f)
-	      (not (string-match-p (rx (| "journal" "roam" "review" "archive")) f))))
-	   (projectile-current-project-files))))
-    (find-file
-     (concat org-directory "/" (completing-read "Org file: " project-files)))))
-
-(my-leader-def
-  "o o" 'my/org-file-open)
-

Presentations

-

Doing presentations with org-present.

-
(use-package hide-mode-line
-  :straight t
-  :after (org-present))
-
-(defun my/present-next-with-latex ()
-  (interactive)
-  (org-present-next)
-  (org-latex-preview '(16)))
-
-(defun my/present-prev-with-latex ()
-  (interactive)
-  (org-present-prev)
-  (org-latex-preview '(16)))
-
-(use-package org-present
-  :straight (:host github :repo "rlister/org-present")
-  :if (not my/remote-server)
-  :commands (org-present)
-  :config
-  (general-define-key
-   :keymaps 'org-present-mode-keymap
-   "<next>" 'my/present-next-with-latex
-   "<prior>" 'my/present-prev-with-latex)
-  (add-hook 'org-present-mode-hook
-	    (lambda ()
-	      (blink-cursor-mode 0)
-	      (org-present-big)
-	      ;; (org-display-inline-images)
-	      (org-present-hide-cursor)
-	      (org-present-read-only)
-	      (display-line-numbers-mode 0)
-	      (hide-mode-line-mode +1)
-	      (setq-local org-format-latex-options
-			  (plist-put org-format-latex-options
-				     :scale (* org-present-text-scale my/org-latex-scale 0.5)))
-	      (org-latex-preview '(16))))
-  (add-hook 'org-present-mode-quit-hook
-	    (lambda ()
-	      (blink-cursor-mode 1)
-	      (org-present-small)
-	      ;; (org-remove-inline-images)
-	      (org-present-show-cursor)
-	      (org-present-read-write)
-	      (display-line-numbers-mode 1)
-	      (hide-mode-line-mode 0)
-	      (setq-local org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale))
-	      (org-latex-preview '(64)))))
-

Tools

-

Various small packages.

-

TOC

-

Make a TOC inside the org file.

-

References:

- - -
(use-package org-make-toc
-  :after (org)
-  :if (not my/remote-server)
-  :commands
-  (org-make-toc
-   org-make-toc-insert
-   org-make-toc-set
-   org-make-toc-at-point)
-  :straight t)
-

Screenshots

-

A nice package to make screenshots and insert them to the Org document.

-
(use-package org-attach-screenshot
-  :commands (org-attach-screenshot)
-  :straight t)
-

System configuration

-

Functions used across my literate config files.

-

Tables for Guix Dependencies

-

A function to extract Guix dependencies from the org file.

- - -
(defun my/extract-guix-dependencies (&optional category)
-  (let ((dependencies '()))
-    (org-table-map-tables
-     (lambda ()
-       (let* ((table
-	       (seq-filter
-		(lambda (q) (not (eq q 'hline)))
-		(org-table-to-lisp)))
-	      (dep-name-index
-	       (cl-position
-		nil
-		(mapcar #'substring-no-properties (nth 0 table))
-		:test (lambda (_ elem)
-			(string-match-p "[G|g]uix.*dep" elem))))
-	      (category-name-index
-	       (cl-position
-		nil
-		(mapcar #'substring-no-properties (nth 0 table))
-		:test (lambda (_ elem)
-			(string-match-p ".*[C|c]ategory.*" elem))))
-	      (disabled-name-index
-	       (cl-position
-		nil
-		(mapcar #'substring-no-properties (nth 0 table))
-		:test (lambda (_ elem)
-			(string-match-p ".*[D|d]isabled.*" elem)))))
-	 (when dep-name-index
-	   (dolist (elem (cdr table))
-	     (when
-		 (and
-		  ;; Category
-		  (or
-		   ;; Category not set and not present in the table
-		   (and
-		    (or (not category) (string-empty-p category))
-		    (not category-name-index))
-		   ;; Category is set and present in the table
-		   (and
-		    category-name-index
-		    (not (string-empty-p category))
-		    (string-match-p category (nth category-name-index elem))))
-		  ;; Not disabled
-		  (or
-		   (not disabled-name-index)
-		   (string-empty-p (nth disabled-name-index elem))))
-	       (add-to-list
-		'dependencies
-		(substring-no-properties (nth dep-name-index elem)))))))))
-    dependencies))
-

Now, join the dependencies list to make it compatible with Scheme:

-
(defun my/format-guix-dependencies (&optional category)
-  (mapconcat
-   (lambda (e) (concat "\"" e "\""))
-   (my/extract-guix-dependencies category)
-   "\n"))
-

Noweb evaluations

-

Turn off eval confirmations for configuration files.

-
(setq my/org-config-files
-      '("/home/pavel/Emacs.org"
-	"/home/pavel/Desktop.org"
-	"/home/pavel/Console.org"
-	"/home/pavel/Guix.org"
-	"/home/pavel/Mail.org"))
-
-(add-hook 'org-mode-hook
-	  (lambda ()
-	    (when (member (buffer-file-name) my/org-config-files)
-	      (setq-local org-confirm-babel-evaluate nil))))
-

yadm hook

-

A script to run tangle from CLI.

-
(require 'org)
-
-(org-babel-do-load-languages
- 'org-babel-load-languages
- '((emacs-lisp . t)
-   (shell . t)))
-
-;; Do not ask to confirm evaluations
-(setq org-confirm-babel-evaluate nil)
-
-<<guix-tables>>
-
-;; A few dummy modes to avoid being prompted for comment systax
-(define-derived-mode fish-mode prog-mode "Fish"
-  (setq-local comment-start "# ")
-  (setq-local comment-start-skip "#+[\t ]*"))
-
-(define-derived-mode yaml-mode text-mode "YAML"
-  (setq-local comment-start "# ")
-  (setq-local comment-start-skip "#+ *"))
-
-(mapcar #'org-babel-tangle-file
-	'("/home/pavel/Emacs.org"
-	  "/home/pavel/Desktop.org"
-	  "/home/pavel/Console.org"
-	  "/home/pavel/Guix.org"
-	  "/home/pavel/Mail.org"))
-

To launch from CLI, run:

-
emacs -Q --batch -l run-tangle.el
-

I have added this line to yadm’s post_alt hook, so tangle is run after yadm alt

-

Programming

-

General setup

-

LSP

-

LSP-mode provides an IDE-like experience for Emacs - real-time diagnostic, code actions, intelligent autocompletion, etc.

-

References:

- -
Setup
-
(use-package lsp-mode
-  :straight t
-  :if (not (or my/slow-ssh my/is-termux my/remote-server))
-  :hook (
-	 (typescript-mode . lsp)
-	 (js-mode . lsp)
-	 (vue-mode . lsp)
-	 (go-mode . lsp)
-	 (svelte-mode . lsp)
-	 ;; (python-mode . lsp)
-	 (json-mode . lsp)
-	 (haskell-mode . lsp)
-	 (haskell-literate-mode . lsp)
-	 (java-mode . lsp)
-	 ;; (csharp-mode . lsp)
-	 )
-  :commands lsp
-  :init
-  (setq lsp-keymap-prefix nil)
-  :config
-  (setq lsp-idle-delay 1)
-  (setq lsp-eslint-server-command '("node" "/home/pavel/.emacs.d/.cache/lsp/eslint/unzipped/extension/server/out/eslintServer.js" "--stdio"))
-  (setq lsp-eslint-run "onSave")
-  (setq lsp-signature-render-documentation nil)
-  ;; (lsp-headerline-breadcrumb-mode nil)
-  (setq lsp-headerline-breadcrumb-enable nil)
-  (add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte")))
-
-(use-package lsp-ui
-  :straight t
-  :commands lsp-ui-mode
-  :config
-  (setq lsp-ui-doc-delay 2)
-  (setq lsp-ui-sideline-show-hover nil))
-
Integrations
-

The only integration left now is treemacs.

-

Origami should’ve leveraged LSP folding, but it was too unstable at the moment I tried it.

-
;; (use-package helm-lsp
-;;   :straight t
-;;   :commands helm-lsp-workspace-symbol)
-
-;; (use-package origami
-;;   :straight t
-;;   :hook (prog-mode . origami-mode))
-
-;; (use-package lsp-origami
-;;   :straight t
-;;   :config
-;;   (add-hook 'lsp-after-open-hook #'lsp-origami-try-enable))
-
-(use-package lsp-treemacs
-  :after (lsp)
-  :straight t
-  :commands lsp-treemacs-errors-list)
-
Keybindings
-
(my-leader-def
-  :infix "l"
-  "" '(:which-key "lsp")
-  "d" 'lsp-ui-peek-find-definitions
-  "r" 'lsp-rename
-  "u" 'lsp-ui-peek-find-references
-  "s" 'lsp-ui-find-workspace-symbol
-  "l" 'lsp-execute-code-action
-  "e" 'list-flycheck-errors)
-

Flycheck

-

A syntax checking extension for Emacs. Integrates with LSP-mode, but can also use various standalone checkers.

-

References:

- - -
(use-package flycheck
-  :straight t
-  :config
-  (global-flycheck-mode)
-  (setq flycheck-check-syntax-automatically '(save idle-buffer-switch mode-enabled))
-  ;; (add-hook 'evil-insert-state-exit-hook
-  ;;           (lambda ()
-  ;;             (if flycheck-checker
-  ;;                 (flycheck-buffer))
-  ;;             ))
-  (advice-add 'flycheck-eslint-config-exists-p :override (lambda() t))
-  (add-to-list 'display-buffer-alist
-	       `(,(rx bos "*Flycheck errors*" eos)
-		 (display-buffer-reuse-window
-		  display-buffer-in-side-window)
-		 (side            . bottom)
-		 (reusable-frames . visible)
-		 (window-height   . 0.33))))
-

Tree Sitter

-

An incremental code parsing system, constructing a syntax tree at runtime.

-

Right now it doesn’t do much except provide a better syntax highlighting than regexes, but this integration is a rather recent development. There are already some major modes built on top of this thing.

-

Also, it seems to break if ran from mmm-mode, so there is a small workaround.

-

References:

- - -
(defun my/tree-sitter-if-not-mmm ()
-  (when (not (and (boundp 'mmm-temp-buffer-name)
-		  (string-equal mmm-temp-buffer-name (buffer-name))))
-    (tree-sitter-mode)
-    (tree-sitter-hl-mode)))
-
-(use-package tree-sitter
-  :straight t
-  :if (not my/remote-server)
-  :hook ((typescript-mode . my/tree-sitter-if-not-mmm)
-	 (js-mode . my/tree-sitter-if-not-mmm)
-	 (python-mode . tree-sitter-mode)
-	 (python-mode . tree-sitter-hl-mode)
-	 (csharp-mode . tree-sitter-mode)))
-
-(use-package tree-sitter-langs
-  :straight t
-  :after tree-sitter)
-

DAP

-

An Emacs client for Debugger Adapter Protocol.

-

As of time of this writing, I mostly debug TypeScript, so the main competitor is Chrome Inspector for node.js.

-

References:

- - -
(use-package dap-mode
-  :straight t
-  :commands (dap-debug)
-  :init
-  (setq lsp-enable-dap-auto-configure nil)
-  :config
-
-  (setq dap-ui-variable-length 100)
-  (setq dap-auto-show-output nil)
-  (require 'dap-node)
-  (dap-node-setup)
-
-  (require 'dap-chrome)
-  (dap-chrome-setup)
-
-  (require 'dap-python)
-
-  (dap-mode 1)
-  (dap-ui-mode 1)
-  (dap-tooltip-mode 1)
-  (tooltip-mode 1))
-
Controls
-

I don’t like some keybindings in the built-in hydra, and there seems to be no easy way to modify the existing hydra, so I create my own. I tried to use transient, but the transient buffer seems to conflict with special buffers of DAP, and hydra does not.

-

Also, I want the hydra to toggle UI windows instead of just opening them, so here is macro that defines such functions:

-
(with-eval-after-load 'dap-mode
-  (defmacro my/define-dap-ui-window-toggler (name)
-    `(defun ,(intern (concat "my/dap-ui-toggle-" name)) ()
-       ,(concat "Toggle DAP " name "buffer")
-       (interactive)
-       (if-let (window (get-buffer-window ,(intern (concat "dap-ui--" name "-buffer"))))
-	   (quit-window nil window)
-	 (,(intern (concat "dap-ui-" name))))))
-
-  (my/define-dap-ui-window-toggler "locals")
-  (my/define-dap-ui-window-toggler "expressions")
-  (my/define-dap-ui-window-toggler "sessions")
-  (my/define-dap-ui-window-toggler "breakpoints")
-  (my/define-dap-ui-window-toggler "repl"))
-

And here is the hydra:

-
(defhydra my/dap-hydra (:color pink :hint nil :foreign-keys run)
-  "
-^Stepping^         ^UI^                     ^Switch^                   ^Breakpoints^         ^Debug^                     ^Expressions
-^^^^^^^^------------------------------------------------------------------------------------------------------------------------------------------
-_n_: Next          _uc_: Controls           _ss_: Session              _bb_: Toggle          _dd_: Debug                 _ee_: Eval
-_i_: Step in       _ue_: Expressions        _st_: Thread               _bd_: Delete          _dr_: Debug recent          _er_: Eval region
-_o_: Step out      _ul_: Locals             _sf_: Stack frame          _ba_: Add             _dl_: Debug last            _es_: Eval thing at point
-_c_: Continue      _ur_: REPL               _su_: Up stack frame       _bc_: Set condition   _de_: Edit debug template   _ea_: Add expression
-_r_: Restart frame _uo_: Output             _sd_: Down stack frame     _bh_: Set hit count   _Q_:  Disconnect            _ed_: Remove expression
-		 _us_: Sessions           _sF_: Stack frame filtered _bl_: Set log message                           _eu_: Refresh expressions
-		 _ub_: Breakpoints                                                                               "
-
-  ("n" dap-next)
-  ("i" dap-step-in)
-  ("o" dap-step-out)
-  ("c" dap-continue)
-  ("r" dap-restart-frame)
-  ("uc" dap-ui-controls-mode)
-  ("ue" my/dap-ui-toggle-expressions)
-  ("ul" my/dap-ui-toggle-locals)
-  ("ur" my/dap-ui-toggle-repl)
-  ("uo" dap-ui-go-to-output-buffer)
-  ("us" my/dap-ui-toggle-sessions)
-  ("ub" my/dap-ui-toggle-breakpoints)
-  ("ss" dap-switch-session)
-  ("st" dap-switch-thread)
-  ("sf" dap-switch-stack-frame)
-  ("sF" my/dap-switch-stack-frame)
-  ("su" dap-up-stack-frame)
-  ("sd" dap-down-stack-frame)
-  ("bb" dap-breakpoint-toggle)
-  ("ba" dap-breakpoint-add)
-  ("bd" dap-breakpoint-delete)
-  ("bc" dap-breakpoint-condition)
-  ("bh" dap-breakpoint-hit-condition)
-  ("bl" dap-breakpoint-log-message)
-  ("dd" dap-debug)
-  ("dr" dap-debug-recent)
-  ("dl" dap-debug-last)
-  ("de" dap-debug-edit-template)
-  ("ee" dap-eval)
-  ("ea" dap-ui-expressions-add)
-  ("er" dap-eval-region)
-  ("es" dap-eval-thing-at-point)
-  ("ed" dap-ui-expressions-remove)
-  ("eu" dap-ui-expressions-refresh)
-  ("q" nil "quit" :color blue)
-  ("Q" dap-disconnect :color red))
-
-(my-leader-def "d" #'my/dap-hydra/body)
-
UI Fixes
-

There are some problems with DAP UI in my setup.

-

First, DAP uses Treemacs buffers quite extensively, and they hide the doom modeline for some reason, so I can’t tell which buffer is active and can’t see borders between buffers.

-

Second, lines are truncated in some strange way, but calling toggle-truncate-lines seems to fix that.

-

So I define a macro that creates a function that I can further use in an advice.

-
(defvar my/dap-mode-buffer-fixed nil)
-
-(with-eval-after-load 'dap-mode
-  (defmacro my/define-dap-tree-buffer-fixer (buffer-var buffer-name)
-    `(defun ,(intern (concat "my/fix-dap-ui-" buffer-name "-buffer")) (&rest _)
-       (with-current-buffer ,buffer-var
-	 (unless my/dap-mode-buffer-fixed
-	   (toggle-truncate-lines 1)
-	   (doom-modeline-set-modeline 'info)
-	   (setq-local my/dap-mode-buffer-fixed t)))))
-
-  (my/define-dap-tree-buffer-fixer dap-ui--locals-buffer "locals")
-  (my/define-dap-tree-buffer-fixer dap-ui--expressions-buffer "expressions")
-  (my/define-dap-tree-buffer-fixer dap-ui--sessions-buffer "sessions")
-  (my/define-dap-tree-buffer-fixer dap-ui--breakpoints-buffer "breakpoints")
-
-  (advice-add 'dap-ui-locals :after #'my/fix-dap-ui-locals-buffer)
-  (advice-add 'dap-ui-expressions :after #'my/fix-dap-ui-expressions-buffer)
-  (advice-add 'dap-ui-sessions :after #'my/fix-dap-ui-sessions-buffer)
-  (advice-add 'dap-ui-breakpoints :after #'my/fix-dap-ui-breakpoints-buffer))
-
Helper functions
-

Some helper functions that make debugging with DAP easier.

-

DAP seems to mess with window parameters from time to time. This function clears “bad” window parameters.

-
(defun my/clear-bad-window-parameters ()
-  "Clear window parameters that interrupt my workflow."
-  (interactive)
-  (let ((window (get-buffer-window (current-buffer))))
-    (set-window-parameter window 'no-delete-other-windows nil)))
-

A function to kill a value from a treemacs node.

-
(defun my/dap-yank-value-at-point (node)
-  (interactive (list (treemacs-node-at-point)))
-  (kill-new (message (plist-get (button-get node :item) :value))))
-

A function to open a value from a treemacs node in a new buffer.

-
(defun my/dap-display-value (node)
-  (interactive (list (treemacs-node-at-point)))
-  (let ((value (plist-get (button-get node :item) :value)))
-    (when value
-      (let ((buffer (generate-new-buffer "dap-value")))
-	(with-current-buffer buffer
-	  (insert value))
-	(select-window (display-buffer buffer))))))
-
Improved stack frame switching
-

One significant improvement over Chrome Inspector for my particular stack is an ability to filter the stack frame list, for instance to see only frames that relate to my current project.

-

So, here are functions that customize the filters:

-
(with-eval-after-load 'dap-mode
-  (setq my/dap-stack-frame-filters
-	`(("node_modules,node:internal" . ,(rx (or "node_modules" "node:internal")))
-	  ("node_modules" . ,(rx (or "node_modules")))
-	  ("node:internal" . ,(rx (or "node:internal")))))
-
-  (setq my/dap-stack-frame-current-filter (cdar my/dap-stack-frame-filters))
-
-  (defun my/dap-stack-frame-filter-set ()
-    (interactive)
-    (setq my/dap-stack-frame-current-filter
-	  (cdr
-	   (assoc
-	    (completing-read "Filter: " my/dap-stack-frame-filters)
-	    my/dap-stack-frame-filters))))
-
-  (defun my/dap-stack-frame-filter (frame)
-    (when-let (path (dap--get-path-for-frame frame))
-      (not (string-match my/dap-stack-frame-current-filter path)))))
-

And here is a version of dap-switch-stack-frame that uses the said filter.

-
(defun my/dap-switch-stack-frame ()
-  "Switch stackframe by selecting another stackframe stackframes from current thread."
-  (interactive)
-  (when (not (dap--cur-session))
-    (error "There is no active session"))
-
-  (-if-let (thread-id (dap--debug-session-thread-id (dap--cur-session)))
-      (-if-let (stack-frames
-		(gethash
-		 thread-id
-		 (dap--debug-session-thread-stack-frames (dap--cur-session))))
-	  (let* ((index 0)
-		 (stack-framces-filtered
-		  (-filter
-		   #'my/dap-stack-frame-filter
-		   stack-frames))
-		 (new-stack-frame
-		  (dap--completing-read
-		   "Select active frame: "
-		   stack-framces-filtered
-		   (-lambda ((frame &as &hash "name"))
-		     (if-let (frame-path (dap--get-path-for-frame frame))
-			 (format "%s: %s (in %s)"
-				 (cl-incf index) name frame-path)
-		       (format "%s: %s" (cl-incf index) name)))
-		   nil
-		   t)))
-	    (dap--go-to-stack-frame (dap--cur-session) new-stack-frame))
-	(->> (dap--cur-session)
-	     dap--debug-session-name
-	     (format "Current session %s is not stopped")
-	     error))
-    (error "No thread is currently active %s" (dap--debug-session-name (dap--cur-session)))))
-
Debug templates
-

Some debug templates I frequently use.

-
(with-eval-after-load 'dap-mode
-  (dap-register-debug-template
-   "Node::Nest.js"
-   (list :type "node"
-	 :request "attach"
-	 :name "Node::Attach"
-	 :port 9229
-	 :outFiles ["${workspaceFolder}/dist/**/*.js"]
-	 :sourceMaps t
-	 :program "${workspaceFolder}/src/app.ts"))
-  (dap-register-debug-template
-   "Node::Babel"
-   (list :type "node"
-	 :request "attach"
-	 :name "Node::Attach"
-	 :port 9229
-	 :program "${workspaceFolder}/dist/bin/www.js")))
-
-

OFF (OFF) TabNine

-

A ML-based autocompletion system.

-

More often than not gives really good results, but is slow as hell & consumes a lot of RAM. Also, LSP-provided completions were more useful in my experience.

-

References:

- - -
(use-package company-tabnine
-  :straight t
-  :if (not my/lowpower)
-  :after company
-  :config
-  (add-to-list 'company-backends #'company-tabnine))
-

OFF (OFF) Code Compass

-

A set of code analyzing tools.

-

References:

- -
Dependencies
-
(use-package async
-  :straight t)
-(use-package dash
-  :straight t)
-(use-package f
-  :straight t)
-(use-package s
-  :straight t)
-(use-package simple-httpd
-  :straight t)
-
Plugin
-
(use-package code-compass
-  :straight (
-  :repo "ag91/code-compass"
-  :files ("code-compass.el")
-  :branch "main"
-  ))
-

Reformatter

-

A general-purpose package to run formattters on files. While the most popular formatters are already packaged for Emacs, those that aren’t can be invoked with this package.

-
(use-package reformatter
-  :straight t)
-

General additional config

-

Make smartparens behave the way I like for C-like languages.

-
(defun my/set-smartparens-indent (mode)
-  (sp-local-pair mode "{" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET")))
-  (sp-local-pair mode "[" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET")))
-  (sp-local-pair mode "(" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET"))))
-

Override flycheck checker with eslint.

-
(defun my/set-flycheck-eslint()
-  "Override flycheck checker with eslint."
-  (setq-local lsp-diagnostic-package :none)
-  (setq-local flycheck-checker 'javascript-eslint))
-

Web development

-

Configs for various web development technologies I’m using.

-

Emmet

-

Emmet is a toolkit which greatly speeds up typing HTML & CSS.

- - - - - - - - - - - - - -
TypeNote
TODOmake expand div[disabled] as
-

My bit of config here:

- - -
(use-package emmet-mode
-  :straight t
-  :hook ((vue-html-mode . emmet-mode)
-	 (svelte-mode . emmet-mode)
-	 (web-mode . emmet-mode)
-	 (html-mode . emmet-mode)
-	 (css-mode . emmet-mode)
-	 (scss-mode . emmet-mode))
-  :config
-  ;; (setq emmet-indent-after-insert nil)
-  (setq my/emmet-mmm-submodes '(vue-html-mode css-mode))
-  (defun my/emmet-or-tab (&optional arg)
-    (interactive)
-    (if (and
-	 (boundp 'mmm-current-submode)
-	 mmm-current-submode
-	 (not (member mmm-current-submode my/emmet-mmm-submodes)))
-	(indent-for-tab-command arg)
-      (or (emmet-expand-line arg)
-	  (emmet-go-to-edit-point 1)
-	  (indent-for-tab-command arg))))
-  (general-imap :keymaps 'emmet-mode-keymap
-    "TAB" 'my/emmet-or-tab
-    "<backtab>" 'emmet-prev-edit-point))
-

Prettier

-
(use-package prettier
-  :commands (prettier-prettify)
-  :straight t
-  :init
-  (my-leader-def
-    :keymaps '(js-mode-map web-mode-map typescript-mode-map vue-mode-map svelte-mode-map)
-    "rr" #'prettier-prettify))
-

TypeScript

-
(use-package typescript-mode
-  :straight t
-  :mode "\\.ts\\'"
-  :config
-  (add-hook 'typescript-mode-hook #'smartparens-mode)
-  (add-hook 'typescript-mode-hook #'rainbow-delimiters-mode)
-  (add-hook 'typescript-mode-hook #'hs-minor-mode)
-  (my/set-smartparens-indent 'typescript-mode))
-

JavaScript

-
(add-hook 'js-mode-hook #'smartparens-mode)
-(add-hook 'js-mode-hook #'hs-minor-mode)
-(my/set-smartparens-indent 'js-mode)
-

Jest

-
(use-package jest-test-mode
-  :straight t
-  :hook ((typescript-mode . jest-test-mode)
-	 (js-mode . jest-test-mode))
-  :config
-  (my-leader-def
-    :keymaps 'jest-test-mode-map
-    :infix "t"
-    "t" 'jest-test-run-at-point
-    "r" 'jest-test-run
-    "a" 'jest-test-run-all-tests))
-
(defun my/jest-test-run-at-point-copy ()
-  "Run the top level describe block of the current buffer's point."
-  (interactive)
-  (let ((filename (jest-test-find-file))
-	(example  (jest-test-example-at-point)))
-    (if (and filename example)
-	(jest-test-from-project-directory filename
-	  (let ((jest-test-options (seq-concatenate 'list jest-test-options (list "-t" example))))
-	    (kill-new (jest-test-command filename))))
-      (message jest-test-not-found-message))))
-

web-mode

-

web-mode.el is a major mode to edit various web templates.

-

Trying this one out instead of vue-mode and svelte-mode, because this one seems to have better support for tree-sitter and generally less problems.

-
(use-package web-mode
-  :straight t
-  :init
-  (add-to-list 'auto-mode-alist '("\\.svelte\\'" . web-mode))
-  (add-to-list 'auto-mode-alist '("\\.vue\\'" . web-mode))
-  :config
-  (add-hook 'web-mode-hook 'smartparens-mode)
-  (add-hook 'web-mode-hook 'hs-minor-mode)
-  (my/set-smartparens-indent 'web-mode))
-

Hooking this up with lsp.

-
(setq my/web-mode-lsp-extensions
-      `(,(rx ".svelte" eos)
-	,(rx ".vue" eos)))
-
-(defun my/web-mode-lsp ()
-  (when (seq-some
-	 (lambda (regex) (string-match-p regex (buffer-name)))
-	 my/web-mode-lsp-extensions)
-    (lsp-deferred)))
-
-(add-hook 'web-mode-hook #'my/web-mode-lsp)
-

Vue settings

-
(defun my/web-mode-vue-setup ()
-  (when (string-match-p (rx ".vue" eos) (buffer-name))
-    (setq-local web-mode-script-padding 0)))
-
-(add-hook 'web-mode-hook 'my/web-mode-vue-setup)
-

OFF (OFF) Vue.js

-
(use-package vue-mode
-  :straight t
-  :disabled
-  :mode "\\.vue\\'"
-  :config
-  (add-hook 'vue-mode-hook #'hs-minor-mode)
-  (add-hook 'vue-mode-hook #'smartparens-mode)
-  (my/set-smartparens-indent 'vue-mode)
-  (add-hook 'vue-mode-hook (lambda () (set-face-background 'mmm-default-submode-face nil)))
-  <<override-mmm-mode-func>>)
-
-(with-eval-after-load 'editorconfig
-  (add-to-list 'editorconfig-indentation-alist
-	       '(vue-mode css-indent-offset
-			  js-indent-level
-			  sgml-basic-offset
-			  ssass-tab-width
-			  typescript-indent-level
-			  emmet-indentation
-			  vue-html-extra-indent)))
-
mmm-mode fix
-

References:

- - -
(defun mmm-syntax-propertize-function (start stop)
-  (let ((saved-mode mmm-current-submode)
-	(saved-ovl  mmm-current-overlay))
-    (mmm-save-changed-local-variables
-     mmm-current-submode mmm-current-overlay)
-    (unwind-protect
-	(mapc (lambda (elt)
-		(let* ((mode (car elt))
-		       (func (get mode 'mmm-syntax-propertize-function))
-		       (beg (cadr elt)) (end (nth 2 elt))
-		       (ovl (nth 3 elt))
-		       syntax-ppss-cache
-		       syntax-ppss-last)
-		  (goto-char beg)
-		  (mmm-set-current-pair mode ovl)
-		  (mmm-set-local-variables mode mmm-current-overlay)
-		  (save-restriction
-		    (if mmm-current-overlay
-			(narrow-to-region (overlay-start mmm-current-overlay)
-					  (overlay-end mmm-current-overlay))
-		      (narrow-to-region beg end))
-		    (cond
-		     (func
-		      (funcall func beg end))
-		     (font-lock-syntactic-keywords
-		      (let ((syntax-propertize-function nil))
-			(font-lock-fontify-syntactic-keywords-region beg end))))
-		    (run-hook-with-args 'mmm-after-syntax-propertize-functions
-					mmm-current-overlay mode beg end))))
-	      (mmm-regions-in start stop))
-      (mmm-set-current-pair saved-mode saved-ovl)
-      (mmm-set-local-variables (or saved-mode mmm-primary-mode) saved-ovl))))
-

OFF (OFF) Svelte

-

Had some problems with this and tree-sitter. Web-mode seems to be doing better.

-
(use-package svelte-mode
-  :straight t
-  :mode "\\.svelte\\'"
-  :disabled
-  :config
-  (add-hook 'svelte-mode-hook 'my/set-flycheck-eslint)
-  (add-hook 'svelte-mode-hook #'smartparens-mode)
-  (my/set-smartparens-indent 'svelte-mode)
-  ;; I have my own Emmet
-  (setq lsp-svelte-plugin-css-completions-emmet nil)
-  (setq lsp-svelte-plugin-html-completions-emmet nil))
-

SCSS

-
(add-hook 'scss-mode-hook #'smartparens-mode)
-(add-hook 'scss-mode-hook #'hs-minor-mode)
-(my/set-smartparens-indent 'scss-mode)
-

PHP

-
(use-package php-mode
-  :straight t
-  :mode "\\.php\\'")
-

LaTeX

-

AUCTeX

-

The best LaTeX editing environment I’ve found so far.

-

References:

- - -
(use-package tex
-  :straight auctex
-  :defer t
-  :config
-  (setq-default TeX-auto-save t)
-  (setq-default TeX-parse-self t)
-  (TeX-PDF-mode)
-  ;; Use XeLaTeX & stuff
-  (setq-default TeX-engine 'xetex)
-  (setq-default TeX-command-extra-options "-shell-escape")
-  (setq-default TeX-source-correlate-method 'synctex)
-  (TeX-source-correlate-mode)
-  (setq-default TeX-source-correlate-start-server t)
-  (setq-default LaTeX-math-menu-unicode t)
-
-  (setq-default font-latex-fontify-sectioning 1.3)
-
-  ;; Scale preview for my DPI
-  (setq-default preview-scale-function 1.4)
-  (when (boundp 'tex--prettify-symbols-alist)
-    (assoc-delete-all "--" tex--prettify-symbols-alist)
-    (assoc-delete-all "---" tex--prettify-symbols-alist))
-
-  (add-hook 'LaTeX-mode-hook
-	    (lambda ()
-	      (TeX-fold-mode 1)
-	      (outline-minor-mode)))
-
-  (add-to-list 'TeX-view-program-selection
-	       '(output-pdf "Zathura"))
-
-  ;; Do not run lsp within templated TeX files
-  (add-hook 'LaTeX-mode-hook
-	    (lambda ()
-	      (unless (string-match "\.hogan\.tex$" (buffer-name))
-		(lsp))
-	      (setq-local lsp-diagnostic-package :none)
-	      (setq-local flycheck-checker 'tex-chktex)))
-
-  (add-hook 'LaTeX-mode-hook #'rainbow-delimiters-mode)
-  (add-hook 'LaTeX-mode-hook #'smartparens-mode)
-  (add-hook 'LaTeX-mode-hook #'prettify-symbols-mode)
-
-  (my/set-smartparens-indent 'LaTeX-mode)
-  (require 'smartparens-latex)
-
-  (general-nmap
-    :keymaps '(LaTeX-mode-map latex-mode-map)
-    "RET" 'TeX-command-run-all
-    "C-c t" 'orgtbl-mode)
-
-  <<init-greek-latex-snippets>>
-  <<init-english-latex-snippets>>
-  <<init-math-latex-snippets>>
-  <<init-section-latex-snippets>>)
-

BibTeX

-
(use-package ivy-bibtex
-  :commands (ivy-bibtex)
-  :straight t
-  :init
-  (my-leader-def "fB" 'ivy-bibtex))
-
-(add-hook 'bibtex-mode 'smartparens-mode)
-

Import *.sty

-

A function to import .sty files to the LaTeX document.

-
(defun my/list-sty ()
-  (reverse
-   (sort
-    (seq-filter
-     (lambda (file) (if (string-match ".*\.sty$" file) 1 nil))
-     (directory-files
-      (seq-some
-       (lambda (dir)
-	 (if (and
-	      (f-directory-p dir)
-	      (seq-some
-	       (lambda (file) (string-match ".*\.sty$" file))
-	       (directory-files dir))
-	      ) dir nil))
-       (list "./styles" "../styles/" "." "..")) :full))
-    (lambda (f1 f2)
-      (let ((f1b (file-name-base f1))
-	    (f1b (file-name-base f2)))
-	(cond
-	 ((string-match-p ".*BibTex" f1) t)
-	 ((and (string-match-p ".*Locale" f1) (not (string-match-p ".*BibTex" f2))) t)
-	 ((string-match-p ".*Preamble" f2) t)
-	 (t (string-lessp f1 f2))))))))
-
-(defun my/import-sty ()
-  (interactive)
-  (insert
-   (apply #'concat
-	  (cl-mapcar
-	   (lambda (file) (concat "\\usepackage{" (file-name-sans-extension (file-relative-name file default-directory)) "}\n"))
-	   (my/list-sty)))))
-
-(defun my/import-sty-org ()
-  (interactive)
-  (insert
-   (apply #'concat
-	  (cl-mapcar
-	   (lambda (file) (concat "#+LATEX_HEADER: \\usepackage{" (file-name-sans-extension (file-relative-name file default-directory)) "}\n"))
-	   (my/list-sty)))))
-

Snippets

- - - - - - - - - - - - - -
NoteType
TODOMove yasnippet snippets here? Maybe extract to a separate file?
-
Greek letters
-

Autogenerate snippets for greek letters. I have a few blocks like this because it’s faster & more flexible than usual yasnippet snippets.

-

Noweb points to the AUCTeX config block.

-
(setq my/greek-alphabet
-      '(("a" . "\\alpha")
-	("b" . "\\beta" )
-	("g" . "\\gamma")
-	("d" . "\\delta")
-	("e" . "\\epsilon")
-	("z" . "\\zeta")
-	("h" . "\\eta")
-	("o" . "\\theta")
-	("i" . "\\iota")
-	("k" . "\\kappa")
-	("l" . "\\lambda")
-	("m" . "\\mu")
-	("n" . "\\nu")
-	("x" . "\\xi")
-	("p" . "\\pi")
-	("r" . "\\rho")
-	("s" . "\\sigma")
-	("t" . "\\tau")
-	("u" . "\\upsilon")
-	("f" . "\\phi")
-	("c" . "\\chi")
-	("v" . "\\psi")
-	("g" . "\\omega")))
-
-(setq my/latex-greek-prefix "'")
-
-;; The same for capitalized letters
-(dolist (elem my/greek-alphabet)
-  (let ((key (car elem))
-	(value (cdr elem)))
-    (when (string-equal key (downcase key))
-      (add-to-list 'my/greek-alphabet
-		   (cons
-		    (capitalize (car elem))
-		    (concat
-		     (substring value 0 1)
-		     (capitalize (substring value 1 2))
-		     (substring value 2)))))))
-
-(yas-define-snippets
- 'latex-mode
- (mapcar
-  (lambda (elem)
-    (list (concat my/latex-greek-prefix (car elem)) (cdr elem) (concat "Greek letter " (car elem))))
-  my/greek-alphabet))
-
English letters
-
(setq my/english-alphabet
-      '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"))
-
-(dolist (elem my/english-alphabet)
-  (when (string-equal elem (downcase elem))
-    (add-to-list 'my/english-alphabet (upcase elem))))
-
-(setq my/latex-mathbb-prefix "`")
-
-(yas-define-snippets
- 'latex-mode
- (mapcar
-  (lambda (elem)
-    (list (concat my/latex-mathbb-prefix elem) (concat "\\mathbb{" elem "}") (concat "Mathbb letter " elem)))
-  my/english-alphabet))
-
Math symbols
-
(setq my/latex-math-symbols
-      '(("x" . "\\times")
-	("." . "\\cdot")
-	("v" . "\\forall")
-	("s" . "\\sum_{$1}^{$2}$0")
-	("p" . "\\prod_{$1}^{$2}$0")
-	("d" . "\\partial")
-	("e" . "\\exists")
-	("i" . "\\int_{$1}^{$2}$0")
-	("c" . "\\cap")
-	("u" . "\\cup")
-	("0" . "\\emptyset")
-	("^" . "\\widehat{$1}$0")
-	("_" . "\\overline{$1}$0")
-	("~" . "\\sim")
-	("|" . "\\mid")
-	("_|" . "\\perp")))
-
-(setq my/latex-math-prefix ";")
-
-(yas-define-snippets
- 'latex-mode
- (mapcar
-  (lambda (elem)
-    (let ((key (car elem))
-	  (value (cdr elem)))
-      (list (concat my/latex-math-prefix key) value (concat "Math symbol " value))))
-  my/latex-math-symbols))
-
Section snippets
-

Section snippets. The code turned out to be more complicated than just writing the snippets by hand.

-
(setq my/latex-section-snippets
-      '(("ch" . "\\chapter{$1}")
-	("sec" . "\\section{$1}")
-	("ssec" . "\\subsection{$1}")
-	("sssec" . "\\subsubsection{$1}")
-	("par" . "\\paragraph{$1}}")))
-
-(setq my/latex-section-snippets
-      (mapcar
-       (lambda (elem)
-	 `(,(car elem)
-	   ,(cdr elem)
-	   ,(progn
-	      (string-match "[a-z]+" (cdr elem))
-	      (match-string 0 (cdr elem)))))
-       my/latex-section-snippets))
-
-(dolist (elem my/latex-section-snippets)
-  (let* ((key (nth 0 elem))
-	 (value (nth 1 elem))
-	 (desc (nth 2 elem))
-	 (star-index (string-match "\{\$1\}" value)))
-    (add-to-list 'my/latex-section-snippets
-		 `(,(concat key "*")
-		   ,(concat
-		     (substring value 0 star-index)
-		     "*"
-		     (substring value star-index))
-		   ,(concat desc " with *")))
-    (add-to-list 'my/latex-section-snippets
-		 `(,(concat key "l")
-		   ,(concat value "%\n\\label{sec:$2}")
-		   ,(concat desc " with label")))))
-
-(dolist (elem my/latex-section-snippets)
-  (setf (nth 1 elem) (concat (nth 1 elem) "\n$0")))
-
-(yas-define-snippets
- 'latex-mode
- my/latex-section-snippets)
-

Other markup languages

-

Markdown

-
(use-package markdown-mode
-  :straight t
-  :mode "\\.md\\'"
-  :config
-  (setq markdown-command
-	(concat
-	 "pandoc"
-	 " --from=markdown --to=html"
-	 " --standalone --mathjax --highlight-style=pygments"
-	 " --css=pandoc.css"
-	 " --quiet"
-	 ))
-  (setq markdown-live-preview-delete-export 'delete-on-export)
-  (setq markdown-asymmetric-header t)
-  (setq markdown-open-command "/home/pavel/bin/scripts/chromium-sep")
-  (add-hook 'markdown-mode-hook #'smartparens-mode)
-  (general-define-key
-   :keymaps 'markdown-mode-map
-   "M-<left>" 'markdown-promote
-   "M-<right>" 'markdown-demote))
-
-;; (use-package livedown
-;;   :straight (:host github :repo "shime/emacs-livedown")
-;;   :commands livedown-preview
-;;   :config
-;;   (setq livedown-browser "qutebrowser"))
-
-

PlantUML

- - - - - - - - - - - -
Guix dependency
plantuml
-
(use-package plantuml-mode
-  :straight t
-  :mode "(\\.\\(plantuml?\\|uml\\|puml\\)\\'"
-  :config
-  (setq plantuml-executable-path "/home/pavel/.guix-extra-profiles/emacs/emacs/bin/plantuml")
-  (setq plantuml-default-exec-mode 'executable)
-  (setq plantuml-indent-level 2)
-  (setq my/plantuml-indent-regexp-return "^\s*return\s+.+$")
-  (add-to-list
-   'plantuml-indent-regexp-end
-   my/plantuml-indent-regexp-return)
-  (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
-  (add-to-list 'auto-mode-alist '("\\.uml\\'" . plantuml-mode))
-  (add-hook 'plantuml-mode-hook #'smartparens-mode))
-
-(general-nmap
-  :keymaps 'plantuml-mode-map
-  "RET" 'plantuml-preview)
-

LanguageTool

-

LanguageTool is a great offline spell checker. For some reason, the download link is nowhere to be found on the home page, so it is listed in the references as well.

-

References:

- - -
(use-package langtool
-  :straight t
-  :commands (langtool-check)
-  :config
-  (setq langtool-language-tool-server-jar "/home/pavel/bin/LanguageTool-5.4/languagetool-server.jar")
-  (setq langtool-mother-tongue "ru")
-  (setq langtool-default-language "en-US"))
-
-(my-leader-def
-  :infix "L"
-  "" '(:which-key "languagetool")
-  "c" 'langtool-check
-  "s" 'langtool-server-stop
-  "d" 'langtool-check-done
-  "n" 'langtool-goto-next-error
-  "p" 'langtool-goto-previous-error
-  "l" 'langtool-correct-buffer)
-

Lisp

-

These are your father’s parentheses. Elegant weapons for a more… civilized age.

-

Meta Lisp

-

Some packages for editing various Lisps.

-
(use-package lispy
-  :commands (lispy-mode)
-  :straight t)
-
-(use-package lispyville
-  :hook (lispy-mode . lispyville-mode)
-  :straight t)
-
-(sp-with-modes sp-lisp-modes
-  (sp-local-pair "'" nil :actions nil))
-

Emacs Lisp

-
Package Lint
-

A package that checks for the metadata in Emacs Lisp packages.

-
(use-package flycheck-package
-  :straight t
-  :after flycheck
-  :config
-  (flycheck-package-setup))
-
General settings
-
(add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode)
-;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)
-(add-hook 'emacs-lisp-mode-hook #'lispy-mode)
-

Common lisp

-
SLIME
-
(use-package slime
-  :straight t
-  :config
-  (setq inferior-lisp-program "sbcl")
-  (add-hook 'slime-repl-mode 'smartparens-mode))
-
General settings
-
(add-hook 'lisp-mode-hook #'aggressive-indent-mode)
-;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)
-(add-hook 'lisp-mode-hook #'lispy-mode)
-

Clojure

-
(use-package clojure-mode
-  :straight t
-  :mode "\\.clj[sc]?\\'"
-  :config
-  ;; (add-hook 'clojure-mode-hook #'smartparens-strict-mode)
-  (add-hook 'clojure-mode-hook #'lispy-mode)
-  (add-hook 'clojure-mode-hook #'aggressive-indent-mode))
-
-(use-package cider
-  :mode "\\.clj[sc]?\\'"
-  :straight t)
-

Hy

-

Python requirements:

- - -
(use-package hy-mode
-  :straight t
-  :mode "\\.hy\\'"
-  :config
-  (add-hook 'hy-mode-hook #'lispy-mode)
-  (add-hook 'hy-mode-hook #'aggressive-indent-mode))
-

Scheme

-
(use-package geiser
-  :straight t
-  :if (not my/lowpower)
-  :config
-  (setq geiser-default-implementation 'guile))
-
-(use-package geiser-guile
-  :straight t
-  :after geiser)
-
-(add-hook 'scheme-mode-hook #'aggressive-indent-mode)
-(add-hook 'scheme-mode-hook #'lispy-mode)
-

CLIPS

-

An honorary Lisp

-
(use-package clips-mode
-  :straight t
-  :mode "\\.cl\\'"
-  :config
-  (add-hook 'clips-mode 'lispy-mode))
-

Python

-

Use Microsoft Language Server for Python.

-

For some reason it doesn’t use pipenv python executable, so here is a small workaround.

-
(setq my/pipenv-python-alist '())
-
-(defun my/get-pipenv-python ()
-  (let ((default-directory (projectile-project-root)))
-    (if (file-exists-p "Pipfile")
-	(let ((asc (assoc default-directory my/pipenv-python-alist)))
-	  (if asc
-	      (cdr asc)
-	    (let ((python-executable
-		   (string-trim (shell-command-to-string "PIPENV_IGNORE_VIRTUALENVS=1 pipenv run which python 2>/dev/null"))))
-	      (if (string-match-p ".*not found.*" python-executable)
-		  (message "Pipfile found, but not pipenv executable!")
-		(message (format "Found pipenv python: %s" python-executable))
-		(add-to-list 'my/pipenv-python-alist (cons default-directory python-executable))
-		python-executable))))
-      "python")))
-
-(use-package lsp-pyright
-  :straight t
-  :defer t
-  :if (not my/slow-ssh)
-  :hook (python-mode . (lambda ()
-			 (require 'lsp-pyright)
-			 (setq-local lsp-pyright-python-executable-cmd (my/get-pipenv-python))
-			 (lsp))))
-
-(add-hook 'python-mode-hook #'smartparens-mode)
-(add-hook 'python-mode-hook #'hs-minor-mode)
-

pipenv

-

Pipenv is a package manager for Python.

-

Automatically creates & manages virtualenvs and stores data in Pipfile and Pipfile.lock (like npm’s package.json and package-lock.json).

-
(use-package pipenv
-  :straight t
-  :hook (python-mode . pipenv-mode)
-  :if (not my/slow-ssh)
-  :init
-  (setq
-   pipenv-projectile-after-switch-function
-   #'pipenv-projectile-after-switch-extended))
-

yapf

-

yapf is a formatter for Python files.

- - - - - - - - - - - -
Guix dependency
python-yapf
-

References:

- - -
(use-package yapfify
-  :straight (:repo "JorisE/yapfify" :host github)
-  :commands (yapfify-region
-	     yapfify-buffer
-	     yapfify-region-or-buffer
-	     yapf-mode))
-

Global config:

-
[style]
-based_on_style = facebook
-column_limit = 80
-

isort

-

isort is a Python package to sort Python imports.

- - - - - - - - - - - -
Guix dependency
python-isort
-

References:

- - -
(use-package py-isort
-  :straight t
-  :commands (py-isort-buffer py-isort-region))
-

The following bindings calls yapf & isort on the buffer

-
(my-leader-def
-  :keymaps 'python-mode-map
-  "rr" (lambda ()
-	 (interactive)
-	 (unless (and (fboundp #'org-src-edit-buffer-p) (org-src-edit-buffer-p))
-	   (py-isort-buffer))
-	 (yapfify-buffer)))
-

sphinx-doc

-

A package to generate sphinx-compatible docstrings.

-
(use-package sphinx-doc
-  :straight t
-  :hook (python-mode . sphinx-doc-mode)
-  :config
-  (my-leader-def
-    :keymaps 'sphinx-doc-mode-map
-    "rd" 'sphinx-doc))
-

pytest

-

pytest is an unit testing framework for Python.

-

Once again a function to set pytest executable from pipenv.

-

References:

- - -
(defun my/set-pipenv-pytest ()
-  (setq-local
-   python-pytest-executable
-   (concat (my/get-pipenv-python) " -m pytest")))
-
-(use-package python-pytest
-  :straight t
-  :commands (python-pytest-dispatch)
-  :init
-  (my-leader-def
-    :keymaps 'python-mode-map
-    :infix "t"
-    "t" 'python-pytest-dispatch)
-  :config
-  <<override-pytest-run>>
-  (add-hook 'python-mode-hook #'my/set-pipenv-pytest)
-  (when (derived-mode-p 'python-mode)
-    (my/set-pipenv-pytest)))
-
Fix comint buffer width
-

For some reason default comint output width is way too large.

-

To fix that, I’ve modified the following function in the python-pytest package.

-
(cl-defun python-pytest--run-as-comint (&key command)
-  "Run a pytest comint session for COMMAND."
-  (let* ((buffer (python-pytest--get-buffer))
-	 (process (get-buffer-process buffer)))
-    (with-current-buffer buffer
-      (when (comint-check-proc buffer)
-	(unless (or compilation-always-kill
-		    (yes-or-no-p "Kill running pytest process?"))
-	  (user-error "Aborting; pytest still running")))
-      (when process
-	(delete-process process))
-      (let ((inhibit-read-only t))
-	(erase-buffer))
-      (unless (eq major-mode 'python-pytest-mode)
-	(python-pytest-mode))
-      (compilation-forget-errors)
-      (display-buffer buffer)
-      (setq command (format "export COLUMNS=%s; %s"
-			    (- (window-width (get-buffer-window buffer)) 5)
-			    command))
-      (insert (format "cwd: %s\ncmd: %s\n\n" default-directory command))
-      (setq python-pytest--current-command command)
-      (when python-pytest-pdb-track
-	(add-hook
-	 'comint-output-filter-functions
-	 'python-pdbtrack-comint-output-filter-function
-	 nil t))
-      (run-hooks 'python-pytest-setup-hook)
-      (make-comint-in-buffer "pytest" buffer "bash" nil "-c" command)
-      (run-hooks 'python-pytest-started-hook)
-      (setq process (get-buffer-process buffer))
-      (set-process-sentinel process #'python-pytest--process-sentinel))))
-

code-cells

-

Support for text with magic comments.

-
(use-package code-cells
-  :straight t
-  :commands (code-cells-mode))
-

tensorboard

-

A function to start up TensorBoard.

-
(setq my/tensorboard-buffer "TensorBoard-out")
-
-(defun my/tensorboard ()
-  (interactive)
-  (start-process
-   "tensorboard"
-   my/tensorboard-buffer
-   "tensorboard"
-   "serve"
-   "--logdir"
-   (car (find-file-read-args "Directory: " t)))
-  (display-buffer my/tensorboard-buffer))
-

Java

-
(use-package lsp-java
-  :straight t
-  :after (lsp)
-  :config
-  (setq lsp-java-jdt-download-url "https://download.eclipse.org/jdtls/milestones/0.57.0/jdt-language-server-0.57.0-202006172108.tar.gz"))
-
-(add-hook 'java-mode-hook #'smartparens-mode)
-;; (add-hook 'java-mode-hook #'hs-minor-mode)
-(my/set-smartparens-indent 'java-mode)
-

Go

-
(use-package go-mode
-  :straight t
-  :mode "\\.go\\'"
-  :config
-  (my/set-smartparens-indent 'go-mode)
-  (add-hook 'go-mode-hook #'smartparens-mode)
-  (add-hook 'go-mode-hook #'hs-minor-mode))
-

.NET

-

C#

- - - - - - - - - - - - - - - - - -
Guix dependenciesDisabled
omnisharpt
dotnett
-
(use-package csharp-mode
-  :straight t
-  :mode "\\.cs\\'"
-  :config
-  (setq lsp-csharp-server-path (executable-find "omnisharp-wrapper"))
-  (add-hook 'csharp-mode-hook #'csharp-tree-sitter-mode)
-  (add-hook 'csharp-tree-sitter-mode-hook #'smartparens-mode)
-  (add-hook 'csharp-mode-hook #'hs-minor-mode)
-  (my/set-smartparens-indent 'csharp-tree-sitter-mode))
-

MSBuild

-
(use-package csproj-mode
-  :straight t
-  :mode "\\.csproj\\'"
-  :config
-  (add-hook 'csproj-mode #'smartparens-mode))
-

fish

-
(use-package fish-mode
-  :straight t
-  :mode "\\.fish\\'"
-  :config
- (add-hook 'fish-mode-hook #'smartparens-mode))
-

sh

-
(add-hook 'sh-mode-hook #'smartparens-mode)
-

Haskell

-
(use-package haskell-mode
-  :straight t
-  :mode "\\.hs\\'")
-
-(use-package lsp-haskell
-  :straight t
-  :after (lsp haskell-mode))
-

Lua

-
(use-package lua-mode
-  :straight t
-  :mode "\\.lua\\'"
-  :hook (lua-mode . smartparens-mode))
-
-(my/set-smartparens-indent 'lua-mode)
-

JSON

-
(use-package json-mode
-  :straight t
-  :mode "\\.json\\'"
-  :config
-  (add-hook 'json-mode #'smartparens-mode)
-  (add-hook 'json-mode #'hs-minor-mode)
-  (my/set-smartparens-indent 'json-mode))
-

SQL

-

sql-formatter is a nice JavaScript package for pretty-printing SQL queries. It is not packaged for Emacs, so the easiest way to use it seems to be to define a custom formatter via reformatter.

-

Also, I’ve a simple function to switch dialects because I often alternate between them.

-

So far I didn’t find a nice SQL client for Emacs, but I occasionally run SQL queries in Org Mode, so this qute package is handy.

-
(setq my/sqlformatter-dialect-choice
-      '("db2" "mariadb" "mysql" "n1ql" "plsql" "postgresql" "redshift" "spark" "sql" "tsql"))
-
-(setq my/sqlformatter-dialect "postgresql")
-
-(defun my/sqlformatter-set-dialect ()
-  "Set dialect for sql-formatter"
-  (interactive)
-  (setq my/sqlformatter-dialect
-	(completing-read "Dialect: " my/sqlformatter-dialect-choice)))
-
-(reformatter-define sqlformat
-  :program (executable-find "sql-formatter")
-  :args `("-l" ,my/sqlformatter-dialect, "-u"))
-
-(my-leader-def
-  :keymaps '(sql-mode-map)
-  "rr" #'sqlformat-buffer)
-

YAML

-
(use-package yaml-mode
-  :straight t
-  :mode "\\.yml\\'"
-  :config
-  (add-hook 'yaml-mode-hook 'smartparens-mode)
-  (add-hook 'yaml-mode-hook 'highlight-indent-guides-mode)
-  (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode)))
-

.env

-
(use-package dotenv-mode
-  :straight t
-  :mode "\\.env\\..*\\'")
-

CSV

-
(use-package csv-mode
-  :straight t
-  :mode "\\.csv\\'")
-

OFF (OFF) PDF

-

A decent package to view PDFs in Emacs, but I prefer Zathura.

-

References:

- - -
(use-package pdf-tools
-  :straight t
-  :commands (pdf-tools-install))
-

Docker

-
(use-package dockerfile-mode
-  :mode "Dockerfile\\'"
-  :straight t
-  :config
-  (add-hook 'dockerfile-mode 'smartparens-mode))
-

crontab

-
(use-package crontab-mode
-  :straight t)
-

Apps & Misc

-

Managing dotfiles

-

A bunch of functions for managing dotfiles with yadm.

-

Open Emacs config

-
(defun my/edit-configuration ()
-  "Open the init file."
-  (interactive)
-  (find-file "~/Emacs.org"))
-
-;; (defun my/edit-exwm-configuration ()
-;;   "Open the exwm config file."
-;;   (interactive)
-;;   (find-file "~/.emacs.d/exwm.org"))
-
-(general-define-key "C-c c" 'my/edit-configuration)
-;; (general-define-key "C-c C" 'my/edit-exwm-configuration)
-(my-leader-def
-  :infix "c"
-  "" '(:which-key "configuration")
-  "c" 'my/edit-configuration)
-

Open Magit for yadm

-

Idea:

- - -
(with-eval-after-load 'tramp
-  (add-to-list 'tramp-methods
-	       `("yadm"
-		 (tramp-login-program "yadm")
-		 (tramp-login-args (("enter")))
-		 (tramp-login-env (("SHELL") "/bin/sh"))
-		 (tramp-remote-shell "/bin/sh")
-		 (tramp-remote-shell-args ("-c")))))
-
-
-(defun my/yadm-magit ()
-  (interactive)
-  (magit-status "/yadm::"))
-
-(my-leader-def "cm" 'my/yadm-magit)
-

Open a dotfile

-

Open a file managed by yadm.

-
(defun my/open-yadm-file ()
-  "Open a file managed by yadm"
-  (interactive)
-  (find-file
-   (concat
-    (file-name-as-directory (getenv "HOME"))
-    (completing-read
-     "yadm files: "
-     (split-string
-      (shell-command-to-string "yadm ls-files $HOME --full-name") "\n")))))
-
-(general-define-key "C-c f" 'my/open-yadm-file)
-(my-leader-def "cf" 'my/open-yadm-file)
-

Internet & Multimedia

-

Notmuch

-

My notmuch config now resides in Mail.org.

-
(unless (or my/is-termux my/remote-server)
-  (load-file (expand-file-name "mail.el" user-emacs-directory)))
-

Elfeed

-

elfeed is an Emacs RSS client.

-

The advice there sets shr-use-fonts to nil while rendering HTML, so the elfeed-show buffer will use monospace font.

-

Using my own fork until the modifications are merged into master.

-
(use-package elfeed
-  :straight (:repo "SqrtMinusOne/elfeed" :host github)
-  :if (not my/remote-server)
-  :commands (elfeed)
-  :init
-  (my-leader-def "ae" 'elfeed)
-  :config
-  (setq elfeed-db-directory "~/.elfeed")
-  (setq elfeed-enclosure-default-dir (expand-file-name "~/Downloads"))
-  (advice-add #'elfeed-insert-html
-	      :around
-	      (lambda (fun &rest r)
-		(let ((shr-use-fonts nil))
-		  (apply fun r))))
-  (general-define-key
-   :states '(normal)
-   :keymaps 'elfeed-search-mode-map
-   "o" #'my/elfeed-search-filter-source
-   "c" #'elfeed-search-clear-filter
-   "gl" (lambda () (interactive) (elfeed-search-set-filter "+later")))
-  (general-define-key
-   :states '(normal)
-   :keymaps 'elfeed-show-mode-map
-   "ge" #'my/elfeed-show-visit-eww))
-

elfeed-org allows configuring Elfeed feeds with an Org file.

-
(use-package elfeed-org
-  :straight t
-  :after (elfeed)
-  :config
-  (setq rmh-elfeed-org-files '("~/.emacs.d/private.org"))
-  (elfeed-org))
-
Some additions
-

Filter elfeed search buffer by the feed under the cursor.

-
(defun my/elfeed-search-filter-source (entry)
-  "Filter elfeed search buffer by the feed under cursor."
-  (interactive (list (elfeed-search-selected :ignore-region)))
-  (when (elfeed-entry-p entry)
-    (elfeed-search-set-filter
-     (concat
-      "@6-months-ago "
-      "+unread "
-      "="
-      (replace-regexp-in-string
-       (rx "?" (* not-newline) eos)
-       ""
-       (elfeed-feed-url (elfeed-entry-feed entry)))))))
-

Open a URL with eww.

-
(defun my/elfeed-show-visit-eww ()
-  "Visit the current entry in eww"
-  (interactive)
-  (let ((link (elfeed-entry-link elfeed-show-entry)))
-    (when link
-      (eww link))))
-
Custom faces
-

Setting up custom faces for certain tags to make the feed look a bit nicer.

-
(defface elfeed-videos-entry
-  `((t :foreground ,(doom-color 'red)))
-  "Face for the elfeed entries with tag \"videos\"")
-
-(defface elfeed-twitter-entry
-  `((t :foreground ,(doom-color 'blue)))
-  "Face for the elfeed entries with tah \"twitter\"")
-
-(defface elfeed-emacs-entry
-  `((t :foreground ,(doom-color 'magenta)))
-  "Face for the elfeed entries with tah \"emacs\"")
-
-(defface elfeed-music-entry
-  `((t :foreground ,(doom-color 'green)))
-  "Face for the elfeed entries with tah \"music\"")
-
-(defface elfeed-podcasts-entry
-  `((t :foreground ,(doom-color 'yellow)))
-  "Face for the elfeed entries with tag \"podcasts\"")
-
-(defface elfeed-blogs-entry
-  `((t :foreground ,(doom-color 'orange)))
-  "Face for the elfeed entries with tag \"blogs\"")
-
-(with-eval-after-load 'elfeed
-  (setq elfeed-search-face-alist
-	'((twitter elfeed-twitter-entry)
-	  (podcasts elfeed-podcasts-entry)
-	  (music elfeed-music-entry)
-	  (videos elfeed-videos-entry)
-	  (emacs elfeed-emacs-entry)
-	  (blogs elfeed-blogs-entry)
-	  (unread elfeed-search-unread-title-face))))
-

Also a function to automatically adjust these colors with the Doom theme.

-
(defun my/update-my-theme-elfeed (&rest _)
-  (custom-theme-set-faces
-   'my-theme-1
-   `(elfeed-videos-entry ((t :foreground ,(doom-color 'red))))
-   `(elfeed-twitter-entry ((t :foreground ,(doom-color 'blue))))
-   `(elfeed-emacs-entry ((t :foreground ,(doom-color 'magenta))))
-   `(elfeed-music-entry ((t :foreground ,(doom-color 'green))))
-   `(elfeed-podcasts-entry ((t :foreground ,(doom-color 'yellow))))
-   `(elfeed-blogs-entry ((t :foreground ,(doom-color 'orange)))))
-  (enable-theme 'my-theme-1))
-
-(advice-add 'load-theme :after #'my/update-my-theme-elfeed)
-(when (fboundp 'doom-color)
-  (my/update-my-theme-elfeed))
-
elfeed-score
-

elfeed-score is a package that implements scoring for the elfeed entries. Entries are scored by a set of rules for tags/title/content/etc and sorted by that score.

-
(defun my/elfeed-toggle-score-sort ()
-  (interactive)
-  (setq elfeed-search-sort-function
-	(if elfeed-search-sort-function
-	    nil
-	  #'elfeed-score-sort))
-  (message "Sorting by score: %S" (if elfeed-search-sort-function "ON" "OFF"))
-  (elfeed-search-update--force))
-
-(use-package elfeed-score
-  :straight t
-  :after (elfeed)
-  :init
-  (setq elfeed-score-serde-score-file "~/.emacs.d/elfeed.score")
-  :config
-  (elfeed-score-enable)
-  (setq elfeed-search-print-entry-function #'elfeed-score-print-entry)
-  (general-define-key
-   :states '(normal)
-   :keymaps '(elfeed-search-mode-map)
-   "=" elfeed-score-map)
-  (general-define-key
-   :keymaps '(elfeed-score-map)
-   "=" #'my/elfeed-toggle-score-sort))
-
YouTube & EMMS
-

Previously this block was opening MPV with start-process, but now I’ve managed to hook up MPV with EMMS. So there is the EMMS+elfeed “integration”.

-

The following function converts URLs from Invidious and the like to YouTube.

-
(defun my/get-youtube-url (link)
-  (let ((watch-id (cadr
-		   (assoc "watch?v"
-			  (url-parse-query-string
-			   (substring
-			    (url-filename
-			     (url-generic-parse-url link))
-			    1))))))
-    (concat "https://www.youtube.com/watch?v=" watch-id)))
-

Now, a function to add YouTube link with metadata from elfeed to EMMS.

-
(with-eval-after-load 'emms
-  (define-emms-source elfeed (entry)
-    (let ((track (emms-track
-		  'url (my/get-youtube-url (elfeed-entry-link entry)))))
-      (emms-track-set track 'info-title (elfeed-entry-title entry))
-      (emms-playlist-insert-track track))))
-
-(defun my/elfeed-add-emms-youtube ()
-  (interactive)
-  (emms-add-elfeed elfeed-show-entry)
-  (elfeed-tag elfeed-show-entry 'watched)
-  (elfeed-show-refresh))
-
-(with-eval-after-load 'elfeed
-  (general-define-key
-   :states '(normal)
-   :keymaps 'elfeed-show-mode-map
-   "gm" #'my/elfeed-add-emms-youtube))
-

EMMS

-

EMMS is the Emacs Multi-Media System. I use it to control MPD & MPV.

-

References:

- - -
(use-package emms
-  :straight t
-  :if (not my/remote-server)
-  :commands (emms-smart-browse
-	     emms-browser
-	     emms-add-url
-	     emms-add-file
-	     emms-add-find)
-  :if (not my/is-termux)
-  :init
-  (my-leader-def
-    :infix "as"
-    "" '(:which-key "emms")
-    "s" 'emms-smart-browse
-    "b" 'emms-browser
-    "p" 'emms-pause
-    "q" 'emms-stop
-    "h" 'emms-previous
-    "l" 'emms-next
-    "u" 'emms-player-mpd-connect)
-  (setq emms-mode-line-icon-enabled-p nil)
-  :config
-  (require 'emms-setup)
-  (require 'emms-player-mpd)
-  (require 'emms-player-mpv)
-  (emms-all)
-  ;; MPD setup
-  <<emms-mpd-setup>>
-  ;; MPV setup
-  <<emms-mpv-setup>>
-  ;; evil-lion and evil-commentary shadow some gX bindings
-  ;; (add-hook 'emms-browser-mode-hook
-  ;; (lambda ()
-  ;; (evil-lion-mode -1)
-  ;; (evil-commentary-mode -1)
-  ;; ))
-  ;; I have everything I need in polybar
-  (emms-mode-line-mode -1)
-  (emms-playing-time-display-mode -1)
-  <<emms-fixes>>)
-
MPD
-

mpd is a server for playing music. It has a couple of first-class clients, including curses-based ncmpcpp, but of course, I want to use Emacs.

-
(setq emms-source-file-default-directory (expand-file-name "~/Music/"))
-(add-to-list 'emms-info-functions 'emms-info-mpd)
-(add-to-list 'emms-player-list 'emms-player-mpd)
-(setq emms-player-mpd-server-name "localhost")
-(setq emms-player-mpd-server-port "6600")
-(setq emms-player-mpd-music-directory "~/Music")
-

Connect on setup. For some reason, it stops the mpd playback whenever it connects, but it is not a big issue.

-
(emms-player-mpd-connect)
-

Clear MPD playlist on clearing EMMS playlist. IDK if this is fine for MPD library playlist, I don’t use them anyhow.

-
(add-hook 'emms-playlist-cleared-hook 'emms-player-mpd-clear)
-

Set a custom regex for MPD. EMMS sets up the default one from MPD’s diagnostic output so that regex opens basically everything, including videos, https links, etc. That is fine if MPD is the only player in EMMS, but as I want to use MPV as well, I override the regex.

-
(emms-player-set emms-player-mpd
-		 'regex
-		 (emms-player-simple-regexp
-		  "m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff"))
-

After all this is done, run M-x emms-cache-set-from-mpd-all to set cache from MPD. If everything is correct, EMMS browser will be populated with MPD database.

-
MPV
- - - - - - - - - - - - - - -
Guix dependency
mpv
yt-dlp
-

mpv is a decent media player, which has found a place in this configuration because it integrates with youtube-dl yt-dlp.

-

It looks like YouTube has started to throttle youtube-dl, and yt-dlp has a workaround for that particular case. Just don’t forget to add the following like to the mpv config:

-
script-opts=ytdl_hook-ytdl_path=yt-dlp
-

It seems a bit strange to keep the MPV config in this file, but I don’t use the program outside Emacs.

-
(add-to-list 'emms-player-list 'emms-player-mpv)
-

Also a custom regex. My demands for MPV include running yt-dlp, so there is a regex that matches youtube.com or some of the video formats.

-
(emms-player-set emms-player-mpv
-		 'regex
-		 (rx (or (: "https://" (* nonl) "youtube.com" (* nonl))
-			 (+ (? (or "https://" "http://"))
-			    (* nonl)
-			    (regexp (eval (emms-player-simple-regexp
-			    "mp4" "mov" "wmv" "webm" "flv" "avi" "mkv")))))))
-

By default MPV plays the video in the best possible quality, which may be pretty high, even too high with limited bandwidth. So here is the logic to choose the quality.

-
(setq my/youtube-dl-quality-list
-      '("bestvideo[height<=720]+bestaudio/best[height<=720]"
-	"bestvideo[height<=480]+bestaudio/best[height<=480]"
-	"bestvideo[height<=1080]+bestaudio/best[height<=1080]"))
-
-(setq my/default-emms-player-mpv-parameters
-      '("--quiet" "--really-quiet" "--no-audio-display"))
-
-(defun my/set-emms-mpd-youtube-quality (quality)
-  (interactive "P")
-  (unless quality
-    (setq quality (completing-read "Quality: " my/youtube-dl-quality-list nil t)))
-  (setq emms-player-mpv-parameters
-	`(,@my/default-emms-player-mpv-parameters ,(format "--ytdl-format=%s" quality))))
-
-(my/set-emms-mpd-youtube-quality (car my/youtube-dl-quality-list))
-

Now emms-add-url should work on YouTube URLs just fine. Just keep in mind that it will only add the URL to the playlist, not play it right away.

-
Cache cleanup
-

All the added URLs reside in the EMMS cache after being played. I don’t want them to stay there for a long time, so here is a handy function to clean it.

-
(defun my/emms-cleanup-urls ()
-  (interactive)
-  (let ((keys-to-delete '()))
-    (maphash (lambda (key value)
-	       (when (eq (cdr (assoc 'type value)) 'url)
-		 (add-to-list 'keys-to-delete key)))
-	     emms-cache-db)
-    (dolist (key keys-to-delete)
-      (remhash key emms-cache-db)))
-  (setq emms-cache-dirty t))
-
-(my-leader-def "asc" #'my/emms-cleanup-urls)
-
Fetching lyrics
-

My package for fetching EMMS lyrics and album covers.

-
(use-package lyrics-fetcher
-  :straight t
-  :after (emms)
-  :init
-  (my-leader-def
-    "ast" #'lyrics-fetcher-show-lyrics
-    "asT" #'lyrics-fetcher-show-lyrics-query)
-  :config
-  (setq lyrics-fetcher-genius-access-token
-	(password-store-get "My_Online/APIs/genius.com"))
-  (general-define-key
-   :states '(emacs normal)
-   :keymaps 'emms-browser-mode-map
-   "gl" 'lyrics-fetcher-emms-browser-show-at-point
-   "gC" 'lyrics-fetcher-emms-browser-fetch-covers-at-point
-   "go" 'lyrics-fetcher-emms-browser-open-large-cover-at-point))
-
Some keybindings
-
(with-eval-after-load 'emms-browser
-  (general-define-key
-   :states '(normal)
-   :keymaps 'emms-browser-mode-map
-   "q" 'quit-window))
-
-(with-eval-after-load 'emms
-  (general-define-key
-   :states '(normal)
-   :keymaps 'emms-playlist-mode-map
-   "q" 'quit-window))
-
EMMS & mpd Fixes
-

Some fixes until I submit a patch. I’ve submitted a patch for with these fixes, so I’ll remove this section eventually.

-

For some reason EMMS doesn’t fetch albumartist from MPD. Overriding this function fixes that.

-
(defun emms-info-mpd-process (track info)
-  (dolist (data info)
-    (let ((name (car data))
-	  (value (cdr data)))
-      (setq name (cond ((string= name "artist") 'info-artist)
-		       ((string= name "albumartist") 'info-albumartist)
-		       ((string= name "composer") 'info-composer)
-		       ((string= name "performer") 'info-performer)
-		       ((string= name "title") 'info-title)
-		       ((string= name "album") 'info-album)
-		       ((string= name "track") 'info-tracknumber)
-		       ((string= name "disc") 'info-discnumber)
-		       ((string= name "date") 'info-year)
-		       ((string= name "genre") 'info-genre)
-		       ((string= name "time")
-			(setq value (string-to-number value))
-			'info-playing-time)
-		       (t nil)))
-      (when name
-	(emms-track-set track name value)))))
-

Also, emms-player-mpd-get-alists has an interesting bug. This function parses the response to listallinfo, which looks something like this:

-
tag1: value1
-tag2: value2
-...
-tag1: value1'
-tag2: value2'
-

This structure has to be converted to list of alists, which looks like:

-
(("tag1" . "value1"
-  "tag2" . "value2")
-  ("tag1" . "value1'"
-  ("tag2" . "value2'")))
-

The original implementation creates a new alist whenever it encounters a tag it has already put in the current alist. Which doesn’t work too well if some tags don’t repeat, if the order is messed up, etc.

-

Fortunately, according to the protocol specification, each new record has to start with file, directory or playlist. I’ve overridden the function with that in mind and it fixed the import, at least in my case.

-
(defun emms-player-mpd-get-alists (info)
-  "Turn the given parsed INFO from MusicPD into an list of alists.
-
-The list will be in reverse order."
-  (when (and info
-	     (null (car info))          ; no error has occurred
-	     (cdr info))                ; data exists
-    (let ((alists nil)
-	  (alist nil)
-	  cell)
-      (dolist (line (cdr info))
-	(when (setq cell (emms-player-mpd-parse-line line))
-	  (if (member (car cell) '("file" "directory" "playlist"))
-	      (setq alists (cons alist alists)
-		    alist (list cell))
-	    (setq alist (cons cell alist)))))
-      (when alist
-	(setq alists (cons alist alists)))
-      alists)))
-

ytel

-

ytel is a YouTube (actually Invidious) frontend, which lets one search YouTube (whereas the setup with elfeed just lets one view the pre-defined subscriptions).

-

The package doesn’t provide evil bindings, so I define my own.

-
(use-package ytel
-  :straight t
-  :commands (ytel)
-  :config
-  (setq ytel-invidious-api-url "https://invidio.xamh.de/")
-  (general-define-key
-   :states '(normal)
-   :keymaps 'ytel-mode-map
-   "q" #'ytel-quit
-   "s" #'ytel-search
-   "L" #'ytel-search-next-page
-   "H" #'ytel-search-previous-page
-   "RET" #'my/ytel-add-emms))
-

And here is the same kind of integration with EMMS as in the elfeed setup:

-
(with-eval-after-load 'emms
-  (define-emms-source ytel (video)
-    (let ((track (emms-track
-		  'url (concat "https://www.youtube.com/watch?v="
-			       (ytel-video-id video)))))
-      (emms-track-set track 'info-title (ytel-video-title video))
-      (emms-track-set track 'info-artist (ytel-video-author video))
-      (emms-playlist-insert-track track))))
-
-(defun my/ytel-add-emms ()
-  (interactive)
-  (emms-add-ytel (ytel-get-current-video)))
-

EWW

-

Emacs built-in web browser. I wonder if anyone actually uses it.

-

I use it occasionally to open links in elfeed.

-
(defun my/toggle-shr-use-fonts ()
-  "Toggle the shr-use-fonts variable in buffer"
-  (interactive)
-  (setq-local shr-use-fonts (not shr-use-fonts)))
-
-(my-leader-def "aw" 'eww)
-
-(general-define-key
- :keymaps 'eww-mode-map
- "+" 'text-scale-increase
- "-" 'text-scale-decrease)
-

ERC

-

ERC is a built-it Emacs IRC client.

-
(use-package erc
-  :commands (erc erc-tls)
-  :straight (:type built-in)
-  :init
-  (my-leader-def "ai" #'erc-tls)
-  :config
-  ;; Logging
-  (setq erc-log-channels-directory "~/.erc/logs")
-  (setq erc-save-buffer-on-part t)
-  ;; Config of my ZNC instance.
-  (setq erc-server "sqrtminusone.xyz")
-  (setq erc-port 1984)
-  (setq erc-nick "sqrtminusone")
-  (setq erc-user-full-name "Pavel Korytov")
-  (setq erc-password (password-store-get "Selfhosted/ZNC"))
-  (setq erc-kill-buffer-on-part t)
-  (setq erc-track-shorten-start 8))
-

Exclude everything but actual messages from notifications.

-
(setq erc-track-exclude-types '("NICK" "JOIN" "LEAVE" "QUIT" "PART"
-				"301"   ; away notice
-				"305"   ; return from awayness
-				"306"   ; set awayness
-				"324"   ; modes
-				"329"   ; channel creation date
-				"332"   ; topic notice
-				"333"   ; who set the topic
-				"353"   ; Names notice
-				))
-

A plugin to highlight IRC nicknames:

-
(use-package erc-hl-nicks
-  :hook (erc-mode . erc-hl-nicks-mode)
-  :after (erc)
-  :straight t)
-

ZNC support. Seems to provide a few nice features for ZNC.

-
(use-package znc
-  :straight t
-  :after (erc))
-

Google Translate

-

Emacs interface to Google Translate.

-

Can’t make it load lazily for some strange reason.

-

References:

- - -
(use-package google-translate
-  :straight t
-  :functions (my-google-translate-at-point google-translate--search-tkk)
-  :custom
-  (google-translate-backend-method 'curl)
-  :config
-  (require 'facemenu)
-  (defun google-translate--search-tkk ()
-    "Search TKK."
-    (list 430675 2721866130))
-  (defun my-google-translate-at-point()
-    "reverse translate if prefix"
-    (interactive)
-    (if current-prefix-arg
-	(google-translate-at-point)
-      (google-translate-at-point-reverse)))
-  (setq google-translate-translation-directions-alist
-	'(("en" . "ru")
-	  ("ru" . "en"))))
-
-(my-leader-def
-  :infix "at"
-  "" '(:which-key "google translate")
-  "p" 'google-translate-at-point
-  "P" 'google-translate-at-point-reverse
-  "q" 'google-translate-query-translate
-  "Q" 'google-translate-query-translate-reverse
-  "t" 'google-translate-smooth-translate)
-

Reading documentation

-

tldr

-

tldr is a collaborative project providing cheatsheets for various console commands. For some reason, the built-in download in the package is broken, so I use my own function.

-
(use-package tldr
-  :straight t
-  :commands (tldr)
-  :config
-  (setq tldr-source-zip-url "https://github.com/tldr-pages/tldr/archive/refs/heads/main.zip")
-
-  (defun tldr-update-docs ()
-    (interactive)
-    (shell-command-to-string (format "curl -L %s --output %s" tldr-source-zip-url tldr-saved-zip-path))
-    (when (file-exists-p "/tmp/tldr")
-      (delete-directory "/tmp/tldr" t))
-    (shell-command-to-string (format "unzip -d /tmp/tldr/ %s" tldr-saved-zip-path))
-    (when (file-exists-p tldr-directory-path)
-      (delete-directory tldr-directory-path 'recursive 'no-trash))
-    (shell-command-to-string (format "mv %s %s" "/tmp/tldr/tldr-main" tldr-directory-path))))
-
-(my-leader-def "hT" 'tldr)
-

man & info

-

Of course, Emacs can also display man and info pages.

-
(setq Man-width-max 180)
-(my-leader-def "hM" 'man)
-
-(general-define-key
- :states '(normal)
- :keymaps 'Info-mode-map
- (kbd "RET") 'Info-follow-nearest-node)
-
-(defun my/man-fix-width (&rest _)
-  (setq-local Man-width (- (window-width) 4)))
-
-(advice-add #'Man-update-manpage :before #'my/man-fix-width)
-

devdocs.io

-

Finally, there is also an Emacs plugin for devdocs.io.

-
(use-package devdocs
-  :straight t
-  :commands (devdocs-install devdocs-lookup)
-  :init
-  (my-leader-def
-    "he" #'devdocs-lookup
-    "hE" #'devdocs-install))
-

Utilities

-

pass

-

I use pass as my password manager. Expectedly, there is Emacs frontend for it.

-

Although I use this rofi frontend for actually inserting passwords.

-
(use-package pass
-  :straight t
-  :commands (pass)
-  :init
-  (my-leader-def "ak" #'pass)
-  :config
-  (setq pass-show-keybindings nil))
-

Docker

-

A package to manage docker containers from Emacs.

-

The file progidy-config.el sets variable my/docker-directories, which allows to

-
(use-package docker
-  :straight t
-  :commands (docker)
-  :init
-  (my-leader-def "ao" 'docker))
-

By default, docker commands are run in default-directory. Even worse, transient doesn’t allow to set default-directory temporarily, via let. But often I don’t want to change default-directory of a buffer (e.g. via Dired) to run a command from there.

-

So I decided to implement the following advice:

-
(setq my/selected-docker-directory nil)
-
-(defun my/docker-override-dir (fun &rest args)
-  (let ((default-directory (or my/selected-docker-directory default-directory)))
-    (setq my/selected-docker-directory nil)
-    (apply fun args)))
-

It overrides default-directory for the first launch of a function. Now, add the advice to the required functions from docker.el:

-
(with-eval-after-load 'docker
-  (advice-add #'docker-compose-run-docker-compose-async :around #'my/docker-override-dir)
-  (advice-add #'docker-compose-run-docker-compose :around #'my/docker-override-dir)
-  (advice-add #'docker-run-docker-async :around #'my/docker-override-dir)
-  (advice-add #'docker-run-docker :around #'my/docker-override-dir))
-

And here is a function which prompts the user for the directory. File progidy-config.el sets an alist of possible directories, look the section about progidy.

-
(defun my/docker-from-dir ()
-  (interactive)
-  (when (not (boundp 'my/docker-directories))
-    (load (concat user-emacs-directory "prodigy-config")))
-  (let* ((directories
-	  (mapcar
-	   (lambda (el) (cons (format "%-30s %s" (car el) (cdr el)) (cdr el)))
-	   my/docker-directories))
-	 (selected-directory
-	  (cdr (assoc (completing-read "Docker: " directories nil nil "^")
-		      directories))))
-    (setq my/selected-docker-directory selected-directory)
-    (docker)))
-
-(my-leader-def "aO" 'my/docker-from-dir)
-

Progidy

-

prodigy.el is a package to run various services. I’ve previously used tmuxp + tmux, but want to try this as well.

-

The actual service definitions are in the ~/.emacs.d/prodigy.org, which tangles to prodigy-config.el. Both files are encrypted in yadm, as they contain personal data.

-
(use-package prodigy
-  :straight t
-  :commands (prodigy)
-  :init
-  (my-leader-def "aP" 'prodigy)
-  :config
-  (when (not (boundp 'my/docker-directories))
-    (load (concat user-emacs-directory "prodigy-config")))
-  (general-define-key
-   :states '(normal)
-   :keymaps 'prodigy-view-mode-map
-   "C-h" 'evil-window-left
-   "C-l" 'evil-window-right
-   "C-k" 'evil-window-up
-   "C-j" 'evil-window-down))
-

A few functions to work with apps on ports.

-
(defun my/get-apps-on-ports ()
-  (mapcar
-   (lambda (line)
-     (let* ((split (split-string line (rx (| (+ " ") (+ "\t")))))
-	    (process (elt split 6)))
-       `((netid . ,(elt split 0))
-	 (state . ,(elt split 1))
-	 (recv-q . ,(elt split 2))
-	 (send-q . ,(elt split 3))
-	 ,@(let ((data (elt split 4)))
-	     (save-match-data
-	       (string-match (rx (group-n 1 (* nonl)) ":" (group-n 2 (or (+ num) "*"))) data)
-	       `((local-address . ,(match-string 1 data))
-		 (local-port . ,(match-string 2 data)))))
-	 ,@(unless (string-empty-p process)
-	     `((pid . ,(save-match-data
-			 (string-match (rx "pid=" (+ num)) process)
-			 (string-to-number (substring (match-string 0 process) 4)))))))))
-   (seq-filter
-    (lambda (s) (not (string-empty-p s)))
-    (split-string
-     (shell-command-to-string "ss -tulpnH | grep LISTEN") "\n"))))
-
-(defun my/kill-app-on-port (port &optional signal)
-  (let ((apps (my/get-apps-on-ports)))
-    (dolist (app apps)
-      (when (string-equal (cdr (assoc 'local-port app)) port)
-	(signal-process (cdr (assoc 'pid app)) (or signal 15))
-	(message "Sent %d to %d" (or signal 15) (cdr (assoc 'pid app)))))))
-

screenshot.el

-

Tecosaur’s plugin to make beautiful code screenshots.

- - - - - - - - - - - -
Guix dependency
imagemagick
-
(use-package screenshot
-  :straight (:repo "tecosaur/screenshot" :host github :files ("screenshot.el") :commit "f8204e82dc0c1158c401735d36a143e6f6d24cf5")
-  :if (display-graphic-p)
-  :commands (screenshot)
-  :init
-  (my-leader-def "S" 'screenshot))
-

proced

-

proced is an Emacs built-it process viewer, like top.

-
(my-leader-def "ah" 'proced)
-(setq proced-auto-update-interval 1)
-(add-hook 'proced-mode-hook (lambda ()
-			      (visual-line-mode -1)
-			      (setq-local truncate-lines t)
-			      (proced-toggle-auto-update 1)))
-

Guix

-

An Emacs package to help managing GNU Guix.

-
(use-package guix
-  :straight t
-  :commands (guix)
-  :init
-  (my-leader-def "ag" 'guix))
-

Productivity

-

pomm

-

My package for doing Pomodoro timer.

-
(use-package pomm
-  :straight (:host github :repo "SqrtMinusOne/pomm.el" :files (:defaults "resources"))
-  ;; :straight (:local-repo "~/Code/Emacs/pomm" :files (:defaults "resources"))
-  :init
-  (my-leader-def "ap" #'pomm)
-  :config
-  (setq alert-default-style 'libnotify)
-  (add-hook 'pomm-on-tick-hook 'pomm-update-mode-line-string)
-  (add-hook 'pomm-on-status-changed-hook 'pomm-update-mode-line-string))
-

OFF (OFF) Pomidor

-

A simple pomodoro technique timer.

-

Disabled it in favour of my own package.

-
(use-package pomidor
-  :straight t
-  :disabled
-  :commands (pomidor)
-  :init
-  (my-leader-def "aP" #'pomidor)
-  :config
-  (setq pomidor-sound-tick nil)
-  (setq pomidor-sound-tack nil)
-  (general-define-key
-   :states '(normal)
-   :keymaps 'pomidor-mode-map
-   (kbd "q") #'quit-window
-   (kbd "Q") #'pomidor-quit
-   (kbd "R") #'pomidor-reset
-   (kbd "h") #'pomidor-hold
-   (kbd "H") #'pomidor-unhold
-   (kbd "RET") #'pomidor-stop
-   (kbd "M-RET") #'pomidor-break))
-

Calendar

-

Emacs' built-in calendar. Can even calculate sunrise and sunset times.

-
(setq calendar-date-style 'iso) ;; YYYY/mm/dd
-(setq calendar-week-start-day 1)
-(setq calendar-time-display-form '(24-hours ":" minutes))
-
-(setq calendar-latitude 59.9375)
-(setq calendar-longitude 30.308611)
-

Fun

-

Discord integration

-

Integration with Discord. Shows which file is being edited in Emacs.

-

In order for this to work in Guix, a service is necessary - Discord rich presence.

-

Some functions to override the displayed message:

-
(defun my/elcord-mask-buffer-name (name)
-  (cond
-   ((string-match-p (rx bos (? "CAPTURE-") (= 14 num) "-" (* not-newline) ".org" eos) name)
-    "<ORG-ROAM>")
-   ((string-match-p (rx bos (+ num) "-" (+ num) "-" (+ num) ".org" eos) name)
-    "<ORG-JOURNAL>")
-   ((string-match-p (rx bos "EXWM") name)
-    "<EXWM>")
-   (t name)))
-
-(defun my/elcord-buffer-details-format-functions ()
-  (format "Editing %s" (my/elcord-mask-buffer-name (buffer-name))))
-
-(defun my/elcord-update-presence-mask-advice (r)
-  (list (my/elcord-mask-buffer-name (nth 0 r)) (nth 1 r)))
-

And the package configuration:

-
(use-package elcord
-  :straight t
-  :if (and (or
-	    (string= (system-name) "indigo")
-	    (string= (system-name) "eminence"))
-	   (not my/slow-ssh)
-	   (not my/remote-server))
-  :config
-  (setq elcord-buffer-details-format-function #'my/elcord-buffer-details-format-functions)
-  (advice-add 'elcord--try-update-presence :filter-args #'my/elcord-update-presence-mask-advice)
-  (elcord-mode))
-

Snow

-
(use-package snow
-  :straight (:repo "alphapapa/snow.el" :host github)
-  :commands (snow))
-

Zone

-
(use-package zone
-  :ensure nil
-  :config
-  (setq original-zone-programs (copy-sequence zone-programs)))
-
-(defun my/zone-with-select ()
-  (interactive)
-  (ivy-read "Zone programs"
-	    (cl-pairlis
-	     (cl-mapcar 'symbol-name original-zone-programs)
-	     original-zone-programs)
-	    :action (lambda (elem)
-		      (setq zone-programs (vector (cdr elem)))
-		      (zone))))
-

Also, a function to copy a URL to the video under cursor.

-
(defun my/ytel-kill-url ()
-  (interactive)
-  (kill-new
-   (concat
-    "https://www.youtube.com/watch?v="
-    (ytel-video-id (ytel-get-current-video)))))
-

Guix settings

- - - - - - - - - - - - - - - - - - - - - -
Guix dependencyDescription
emacs-vtermA vterm package
ripgrepA recursive search tool
the-silver-searcherAnother recursive search tool
-

-
(my/format-guix-dependencies)
-
(specifications->manifest
- '("emacs-native-comp"
-   <<packages()>>))
-
-
- -
- - diff --git a/public/configs/guix/index.html b/public/configs/guix/index.html deleted file mode 100644 index 09ee9d5..0000000 --- a/public/configs/guix/index.html +++ /dev/null @@ -1,717 +0,0 @@ - - - - - - Guix - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

GNU Guix is (1) a transactional package manager and (2) a GNU/Linux distribution.

-

My personal selling points are declarative package configuration and transactional upgrades.

-

References:

- - - -

Profiles

-

A profile is a way to group Guix packages. Amongst its advantages, profiles can be defined by manifests, which in turn can be stored in VCS.

-

References:

- -

Activate profiles

-

A script to activate guix profiles. Usage:

-
activate-profiles [profile1] [profile2] ...
-

Source: David Wilson’s config

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

Update profiles

-

A script to update Guix profiles. Usage:

-
update-profiles [profile1] [profile2] ...
-

Source: once again, David Wilson’s config.

-
GREEN='\033[1;32m'
-NC='\033[0m'
-GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles
-
-profiles=$*
-if [[ $# -eq 0 ]]; then
-    profiles="$GUIX_EXTRA_PROFILES/*";
-fi
-
-for profile in $profiles; do
-  profileName=$(basename $profile)
-  profilePath=$GUIX_EXTRA_PROFILES/$profileName
-
-  echo
-  echo -e "${GREEN}Updating profile:" $profilePath "${NC}"
-  echo
-
-  guix package --profile="$profilePath/$profileName" --manifest="$HOME/.config/guix/manifests/$profileName.scm"
-done
-

Channels

-

Specifying additional channels.

-

channel-q is my Guix channel. Don’t use it at home.

-

References:

- - -
(cons*
- (channel
-  (name 'channel-q)
-  (url "file:///home/pavel/Code/channel-q"))
- (channel
-  (name 'flat)
-  (url "https://github.com/flatwhatson/guix-channel.git")
-  (introduction
-   (make-channel-introduction
-    "33f86a4b48205c0dc19d7c036c85393f0766f806"
-    (openpgp-fingerprint
-     "736A C00E 1254 378B A982  7AF6 9DBE 8265 81B6 4490"))))
- (channel
-  (name 'nonguix)
-  (url "https://gitlab.com/nonguix/nonguix")
-  ;; (commit "d54973e47b89fe5772a5b6e2d0c0b86acb089e27")
-  (introduction
-   (make-channel-introduction
-    "897c1a470da759236cc11798f4e0a5f7d4d59fbc"
-    (openpgp-fingerprint
-     "2A39 3FFF 68F4 EF7A 3D29  12AF 6F51 20A0 22FB B2D5"))))
- %default-channels)
-

Systems

-

Configuring systems with Guix.

-

Yes, all my machines are named after colors I like.

-

Base configuration

-

The base configuration is shared between all the machines.

-

While it’s possible to make a single .scm file with base configuration and load it, I noticed that it produces more cryptic error messages whenever there is an error in the base file, so I opt-in for noweb.

-

guix system invocation is as follows:

-
sudo -E guix system reconfigure ~/.config/guix/systems/[system].scm
-

Common modules:

-
(use-modules (gnu))
-(use-modules (gnu system nss))
-(use-modules (gnu packages bash))
-(use-modules ((gnu packages base) #:select (coreutils glibc)))
-(use-modules (gnu packages certs))
-(use-modules (gnu packages version-control))
-(use-modules (gnu packages vim))
-(use-modules (gnu packages gnome))
-(use-modules (gnu packages xorg))
-(use-modules (gnu packages wm))
-(use-modules (gnu packages openbox))
-(use-modules (gnu services docker))
-(use-modules (gnu services cups))
-(use-modules (gnu services virtualization))
-(use-modules (srfi srfi-1))
-(use-modules (guix channels))
-(use-modules (guix inferior))
-(use-modules (nongnu packages linux))
-(use-modules (nongnu system linux-initrd))
-
-(use-service-modules desktop networking ssh xorg nix)
-(use-package-modules ssh)
-

In principle, we could define a variable called base-operating-system and extend it in ancestors. However, then we would have to define mandatory fields like host-name, bootloader with dummy values. Since I’m already using noweb, there is little point.

-

The following code will be inserted at the top of the operating-system definition.

-

Use the full Linux kernel. I hope I’ll be able to use Libre kernel somewhere later.

-

Inferior in the kernel is used to avoid recompilation. It looks like I can pin these to different commits than in my channels.scm

-
(kernel
- (let*
-     ((channels
-       (list (channel
-	      (name 'nonguix)
-	      (url "https://gitlab.com/nonguix/nonguix")
-	      (commit "d3c5eea0cbfe3e5bfbcf1fe15bc916fefacc624f"))
-	     (channel
-	      (name 'guix)
-	      (url "https://git.savannah.gnu.org/git/guix.git")
-	      (commit "cf88c967afbf15c58efb0ba37d6638f1be9a0481"))))
-      (inferior
-       (inferior-for-channels channels)))
-   (first (lookup-inferior-packages inferior "linux" "5.12.9"))))
-;; (kernel linux)
-(initrd microcode-initrd)
-(firmware (list linux-firmware))
-(locale "en_US.utf8")
-(timezone "Europe/Moscow")
-

US/RU keyboard layout, switch with Alt+Shift.

-
(keyboard-layout (keyboard-layout "us,ru" #:options '("grp:alt_shift_toggle")))
-

User accounts.

-
(users (cons* (user-account
-	       (name "pavel")
-	       (comment "Pavel")
-	       (group "users")
-	       (home-directory "/home/pavel")
-	       (supplementary-groups
-		'("wheel"  ;; sudo
-		  "netdev" ;; network devices
-		  "audio"
-		  "video"
-		  "input"
-		  "tty"
-		  "docker"
-		  "scanner"
-		  "libvirt"
-		  "lp")))
-	      %base-user-accounts))
-
-

Base packages, necessary right after the installation.

-
(packages
- (append
-  (list nss-certs
-	    git
-	i3-gaps
-	i3lock
-	openbox
-	xterm
-	    vim)
-  %base-packages))
-

Default services for each machine:

- - -
(define %my-base-services
-  (cons*
-   (service openssh-service-type)
-   (screen-locker-service i3lock "i3lock")
-   (extra-special-file "/lib64/ld-linux-x86-64.so.2" (file-append glibc "/lib/ld-linux-x86-64.so.2"))
-   (service nix-service-type)
-   (service cups-service-type
-	    (cups-configuration
-	     (web-interface? #t)))
-   (service docker-service-type)
-   (service libvirt-service-type
-	    (libvirt-configuration
-	     (unix-sock-group "libvirt")
-	     (tls-port "16555")))
-   (service virtlog-service-type)
-   (modify-services %desktop-services
-		    (network-manager-service-type
-		     config =>
-		     (network-manager-configuration
-		      (inherit config)
-		      (vpn-plugins (list network-manager-openvpn)))))))
-
-

indigo

-

indigo is my desktop PC.

-
<<system-common>>
-
-(operating-system
- <<system-base>>
-
- (host-name "indigo")
- (services (cons*
-	    (set-xorg-configuration
-	     (xorg-configuration
-	      (keyboard-layout keyboard-layout)))
-	    %my-base-services))
-
- (bootloader
-  (bootloader-configuration
-   (bootloader grub-efi-bootloader)
-   (target "/boot/efi")
-   (keyboard-layout keyboard-layout)))
-
- (swap-devices
-  (list (uuid "059a2c26-8f70-4986-adf0-1a2e7b511404")))
-
- (file-systems
-  (cons* (file-system
-	  (mount-point "/")
-	  (device (file-system-label "my-root"))
-	  (type "ext4"))
-	     (file-system
-	      (mount-point "/boot/efi")
-	      (device "/dev/sda1")
-	      (type "vfat"))
-	 %base-file-systems)))
-

eminence

-

eminence is a HP 15s laptop.

-

%backlight-udev-rule should enable members of video group change the display backlight. See the relevant page at Arch Wiki.

-
<<system-common>>
-
-(define %backlight-udev-rule
-  (udev-rule
-   "90-backlight.rules"
-   (string-append "ACTION==\"add\", SUBSYSTEM==\"backlight\", "
-		  "RUN+=\"/run/current-system/profile/bin/chgrp video /sys/class/backlight/%k/brightness\""
-		  "\n"
-		  "ACTION==\"add\", SUBSYSTEM==\"backlight\", "
-		  "RUN+=\"/run/current-system/profile/bin/chmod g+w /sys/class/backlight/%k/brightness\"")))
-
-(operating-system
- <<system-base>>
-
- (host-name "eminence")
- (services (cons*
-	    (set-xorg-configuration
-	     (xorg-configuration
-	      (keyboard-layout keyboard-layout)))
-	    (modify-services %my-base-services
-			     (elogind-service-type
-			      config =>
-			      (elogind-configuration
-			       (inherit config)
-			       (handle-lid-switch-external-power 'suspend)))
-			     (udev-service-type
-			      config =>
-			      (udev-configuration
-			       (inherit config)
-			       (rules (cons %backlight-udev-rule
-					    (udev-configuration-rules config))))))))
-
- (bootloader
-  (bootloader-configuration
-   (bootloader grub-efi-bootloader)
-   (target "/boot/efi")
-   (keyboard-layout keyboard-layout)))
-
- (swap-devices
-  (list (uuid "f93cf3f6-7ee7-42ec-8ee2-f3d896fdf9b5")))
-
- (file-systems
-  (cons* (file-system
-	  (mount-point "/")
-	  (device
-	   (uuid "1d937704-bbeb-43b5-bc63-453886c426af"
-		 'ext4))
-	  (type "ext4"))
-	 (file-system
-	  (mount-point "/boot/efi")
-	  (device (uuid "0031-3784" 'fat32))
-	  (type "vfat"))
-	 %base-file-systems)))
-

azure

-

azure is a Lenovo Ideapad 330 laptop.

-

%backlight-udev-rule should enable members of video group change the display backlight. See the relevant page at Arch Wiki.

-
<<system-common>>
-
-(define %backlight-udev-rule
-  (udev-rule
-   "90-backlight.rules"
-   (string-append "ACTION==\"add\", SUBSYSTEM==\"backlight\", "
-		  "RUN+=\"/run/current-system/profile/bin/chgrp video /sys/class/backlight/%k/brightness\""
-		  "\n"
-		  "ACTION==\"add\", SUBSYSTEM==\"backlight\", "
-		  "RUN+=\"/run/current-system/profile/bin/chmod g+w /sys/class/backlight/%k/brightness\"")))
-
-(operating-system
- <<system-base>>
-
- (host-name "azure")
- (services (cons*
-	    (set-xorg-configuration
-	     (xorg-configuration
-	      (keyboard-layout keyboard-layout)))
-	    (modify-services %my-base-services
-			     (elogind-service-type config =>
-						   (elogind-configuration (inherit config)
-									  (handle-lid-switch-external-power 'suspend)))
-			     (udev-service-type config =>
-						(udev-configuration (inherit config)
-								    (rules (cons %backlight-udev-rule
-										 (udev-configuration-rules config))))))))
-
- (bootloader
-  (bootloader-configuration
-   (bootloader grub-efi-bootloader)
-   (target "/boot/efi")
-   (keyboard-layout keyboard-layout)))
-
- (swap-devices
-  (list (uuid "4b2dedb3-b111-4e69-8c05-6daa2b072c76")))
-
- (file-systems
-  (cons* (file-system
-	  (mount-point "/")
-	  (device (file-system-label "my-root"))
-	  (type "ext4"))
-	     (file-system
-	      (mount-point "/boot/efi")
-	      (device "/dev/sda1")
-	      (type "vfat"))
-	 %base-file-systems)))
-

System installation

-

Preparation

-

In my case, the provided ISO doesn’t work because of the Libre kernel.

-

Fortunately, David Wilson has made a repository with a toolchain to make an ISO with the full kernel. In case it won’t be an option, the nonguix repo also has instructions on how to do that.

-

When an ISO is there, we have to write it on a USB stick. Run sudo fdisk -l to get a list of disks.

-

The approach given in the official instruction is to create a bootable USB with dd:

-
sudo dd of=/dev/sdxX if=<path-to-iso> status=progress && sync
-

However, I couldn’t make it work for some strange reason. Fortunately, gnome-disk-utility was able to produce a bootable USB.

-

Installation

-

Going further, the official instructions for installation & SystemCrafters wiki entry are pretty good, so it’s not necessary to repeat them here.

-

After installation

-

After the installation, the strategy is as follows.

-

Set a password for the main user (pavel). Login with openbox to get a tolerable interface because i3’s default config is horrible.

-

Connect to the internet.

-

Clone the dotfiles repo:

-
mkdir Code
-cd Code
-git clone https://github.com/SqrtMinusOne/dotfiles.git
-

Copy the channels file and run guix pull:

-
cp ~/Code/dotfiles/.config/guix/channels.scm ~/.config/guix
-guix pull
-

The first pull usually takes a while. After that install yadm and pull dotfiles:

-
guix install yadm
-guix clone https://github.com/SqrtMinusOne/dotfiles.git
-

And activate the required profiles. Again, downloading & building Emacs, Starship and stuff will take a while.

-

Don’t forget to install JetBrainsMono Nerd Font.

-

Misc software & notes

- - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependencyDescription
systempatchelfA program to modify existsing ELF executables
systemglibcA lot of stuff, including ELF interpeter and ldd
-

VPN

- - - - - - - - - - - - - - - - - - - - - -
CategoryGuix dependency
systemopenvpn
systemopenvpn-update-resolve-conf
systemvpnc
-

I’m not sure how to properly spin up VPN on Guix, so here is what ended I’m doing after some trial and error.

-

I’m using CyberGhost VPN. ~/.vpn folder stores its OpenVPN config (openvpn.ovpn), modified as follows:

- -

vpn-start

-

As of now, CyberGhost doesn’t provide ipv6, so we have to disable it.

-
export DISPLAY=:0
-CONN=$(nmcli -f NAME con show --active | grep -Ev "(.*docker.*|NAME|br-.*|veth.*|tun.*|vnet.*|virbr.*)" | sed 's/ *$//g')
-
-if [ -z "$CONN" ]; then
-    echo "No connection!"
-    notify-send "VPN" "No connection for VPN to run"
-    exit
-fi
-
-echo "Connection: $CONN"
-notify-send "VPN" "Initializing for connection: $CONN"
-
-pkexec nmcli con modify "$CONN" ipv6.method ignore
-nmcli connection up "$CONN"
-pkexec openvpn --config ~/.vpn/openvpn.ovpn
-

vpn-stop

-

Also a script to reverse the changes.

-
CONN=$(nmcli -f NAME con show --active | grep -Ev "(.*docker.*|NAME|br-.*|veth.*|tun.*)" | sed 's/ *$//g')
-echo "Connection: $CONN"
-
-pkexec nmcli con modify "$CONN" ipv6.method auto
-nmcli connection up "$CONN"
-

flatpak

-

As for now, the easiest way to install most of proprietary software is via flatpak. See the relevant section in Desktop.org.

-

conda

-

conda is a package manager, which I use for managing various versions of Python & Node.js.

-

It is packaged for GNU Guix, although the definition has its fair share of workarounds. It is almost surprising to see it work with all the C libraries and stuff. But there are still some problems.

-

First, it’s impossible to perform conda init to patch files like .bashrc, because the command is hell-bent on modifying /gnu/store/. So I do this manually, look for the init_conda procedures in Console.org.

-

Second, the base environment has /gnu/store/... as a root, so don’t install anything there (and don’t run conda with superuser rights!).

-

Third, by default it tries to create envronments in /gnu/store. It’s enough to create one environment like this to fix it:

-
mkdir -p ~/.conda/envs
-conda create -p ~/.conda/envs/test
-

Fourth, you may need to unset $PYTHONPATH if you have any global packages installed, otherwise Python from anaconda will try to import them instead of the conda versions.

-

Finally, I also want to have an ability to use global npm. Some settings for that are located in Console.org. Here we want to unset NPM_CONFIG_USERCONFIG if there is npm available in the environment.

-

So here is a script to set up conda hooks:

-
# Get writable conda envs with npm & without it
-readarray -t CONDA_ENVS_ALL <<< $(conda env list --json | jq '.envs[]')
-CONDA_ENVS_NPM=()
-CONDA_ENVS_NO_NPM=()
-for env in "${CONDA_ENVS_ALL[@]}"; do
-    env="${env:1:${#env}-2}"
-    if [ -w "$env" ]; then
-	if [ -f "$env/bin/npm" ]; then
-	    CONDA_ENVS_NPM+=($env)
-	else
-	    CONDA_ENVS_NO_NPM+=($env)
-	fi
-    fi
-done
-
-for env in "${CONDA_ENVS_NPM[@]}"; do
-    echo "Found npm in $env"
-    mkdir -p "$env/etc/conda/activate.d"
-    mkdir -p "$env/etc/conda/deactivate.d"
-
-    echo "unset NPM_CONFIG_USERCONFIG" > "$env/etc/conda/activate.d/conda.sh"
-    echo "set -e NPM_CONFIG_USERCONFIG" > "$env/etc/conda/activate.d/conda.fish"
-    echo "export NPM_CONFIG_USERCONFIG=$HOME/._npmrc" > "$env/etc/conda/deactivate.d/conda.sh"
-    echo "export NPM_CONFIG_USERCONFIG=$HOME/._npmrc" > "$env/etc/conda/deactivate.d/conda.fish"
-done
-
-for env in "${CONDA_ENVS_NO_NPM}"; do
-    echo "Did not found npm in $env"
-    rm -rf "$env/etc/conda/activate.d/conda.sh" || true
-    rm -rf "$env/etc/conda/activate.d/conda.fish" || true
-    rm -rf "$env/etc/conda/deactivate.d/conda.sh" || true
-    rm -rf "$env/etc/conda/deactivate.d/conda.fish" || true
-done
-

Slack

-

What a nonsense of a program.

-

I was able to launch the nix version with the following wrapper script:

-
export PATH="$HOME/bin/dummies:$PATH"
-mkdir -p ~/.cache/slack
-slack -r ~/.cache/slack
-

Also, it requires a lsb_release in the PATH, so here is one:

-
echo "LSB Version:    Hey. I spent an hour figuring out why Slack doesn't launch."
-echo "Distributor ID: It seems like it requires an lsb_release."
-echo "Description:    But GNU Guix doesn't have one."
-echo "Release:        42.2"
-echo "Codename:       n/a"
-

virt-manager

-

Run the following to fix the network:

-
sudo virsh net-define /run/current-system/profile/etc/libvirt/qemu/networks/default.xml
-sudo virsh net-start default
-sudo herd restart libvirtd
-

wakatime-cli

- - - - - - - - - - - - - -
NoteDescription
TODOPackage this for Guix
-

Before I figure out how to package this for Guix:

- -

Manifest

-

-
(my/format-guix-dependencies category)
-

System

-
(specifications->manifest
- '(
-   <<packages("system")>>))
-
-
- -
- - diff --git a/public/configs/index.html b/public/configs/index.html deleted file mode 100644 index 2fc3d07..0000000 --- a/public/configs/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - Configs - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Configs

- -
- -
- - diff --git a/public/configs/index.xml b/public/configs/index.xml deleted file mode 100644 index a171069..0000000 --- a/public/configs/index.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - Configs on SqrtMinusOne - https://sqrtminusone.xyz/configs/ - Recent content in Configs on SqrtMinusOne - Hugo -- gohugo.io - en-us - - Console - https://sqrtminusone.xyz/configs/console/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/console/ - #+TOC headlines 6 -.profile Environment export QT_QPA_PLATFORMTHEME=&#34;qt5ct&#34; export QT_AUTO_SCREEN_SCALE_FACTOR=0 Set ripgrep config path -export RIPGREP_CONFIG_PATH=$HOME/.config/ripgrep/ripgreprc My paths My script folders -if [ -d &#34;$HOME/bin&#34; ] ; then export PATH=&#34;$HOME/bin:$PATH&#34; export PATH=&#34;$HOME/bin/scripts:$PATH&#34; fi Guix settings Enable extra profiles -if [ -z &#34;$IS_ANDROID&#34; ]; then GUIX_EXTRA_PROFILES=$HOME/.guix-extra-profiles for i in $GUIX_EXTRA_PROFILES/*; do profile=$i/$(basename &#34;$i&#34;) if [ -f &#34;$profile&#34;/etc/profile ]; then GUIX_PROFILE=&#34;$profile&#34; . &#34;$GUIX_PROFILE&#34;/etc/profile fi export XDG_DATA_DIRS=&#34;$XDG_DATA_DIRS:$profile/share&#34; unset profile done fi Set Jupyter config PATH. - - - - Desktop - https://sqrtminusone.xyz/configs/desktop/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/desktop/ - My general desktop environment configuration. -Parts prefixed with (OFF) are not used, but kept for historic purposes. For some reason GitHub&rsquo;s org renderer ignores TODO status, hence such a prefix. Round brackets instead of square ones to prevent GitHub&rsquo;s org renderer from screwing up. - Table of Contents Global customization Colors Xresources Colors in Xresources Fonts Themes Device-specific settings EXWM Xsession Startup apps Moving windows Resizing windows App shortcuts Move workspace to another monitor Switch to the opposite monitor Switching buffers Add all EXWM buffers to current perspective Revive perspectives Locking up Keybindings Pinentry Modeline EXWM config i3wm General settings Managing windows Workspaces Rules Scratchpad Launch script i3 config Gaps &amp; borders Keybindings Move &amp; resize windows OFF (OFF) Intergration with dmenu Integration with rofi Launching apps &amp; misc keybindings Apps Media controls &amp; brightness Screenshots Colors OFF (OFF) i3blocks Keyboard Layout Autostart Polybar General settings Colors Glyphs Modules Global bar config Launch script Individual modules pulseaudio mpd cpu ram-memory swap-memory network ipstack-vpn openvpn xkeyboard battery weather sun aw-afk date pomm SEP TSEP i3 Rofi Theme Scripts Buku bookmarks Man pages Emojis pass Flameshot dunst keynav Config Using with picom Picom Shadows Fading Opacity General settings Zathura Various software Browsers Office &amp; Multimedia LaTeX Dev Manifests Flatpak Nix Services Music GNU Mcron ActivityWatch PulseEffects xsettingsd nm-applet Discord rich presence Polkit Authentication agent Xmodmap VPN Davmail Shepherd config Sync Guix settings Global customization Colors Most of the colors are from the Palenight theme. - - - - Emacs config - https://sqrtminusone.xyz/configs/emacs/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/emacs/ - #+begin_quote One day we won&rsquo;t hate one another, no young boy will march to war and I will clean up my Emacs config. But that day isn'. - Table of Contents Primary setup Measure startup speed straight.el use-package Config variants &amp; environment Performance Garbage collection Run garbage collection when Emacs is unfocused Native compilation Anaconda Custom file location Private config No littering Prevent Emacs from closing Global editing configuration General keybindings stuff general. - - - - Guix - https://sqrtminusone.xyz/configs/guix/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/guix/ - GNU Guix is (1) a transactional package manager and (2) a GNU/Linux distribution. -My personal selling points are declarative package configuration and transactional upgrades. -References: - Official help System Crafters wiki Pjotr Prins' Guix notes Davil Wilson&rsquo;s YouTube series Table of Contents Profiles Activate profiles Update profiles Channels Systems Base configuration indigo eminence azure System installation Preparation Installation After installation Misc software &amp; notes VPN vpn-start vpn-stop flatpak conda Slack virt-manager wakatime-cli Manifest Profiles A profile is a way to group Guix packages. - - - - Mail - https://sqrtminusone.xyz/configs/mail/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/mail/ - My email configration. Currently I use lieer to fetch emails from Gmail, davmail &amp; offlineimap to fetch emails from MS Exchange, notmuch to index, msmtp to send emails. Also using notmuch frontend from Emacs. -My problem with any particular mail setup was that I use Gmail labels quite extensively, and handling these over IMAP is rather awkward. Notmuch seems to be the only software that provides the same first-class support for labels. - - - - My dotfiles - https://sqrtminusone.xyz/configs/readme/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://sqrtminusone.xyz/configs/readme/ - A set of my GNU/Linux configuration files. View at GitHub. -The majority of the software is configured with literate configuration strategy via Emacs' Org Mode. This way has its advantages and disadvantages, but overall it&rsquo;s pretty nice to keep the configs interweaved with comments in a handful of files. -The files themselves are managed and deployed via yadm, but I mostly use Org Mode rich noweb whenever I can instead of what yadm offers. - - - - diff --git a/public/configs/mail/index.html b/public/configs/mail/index.html deleted file mode 100644 index 8a8fee5..0000000 --- a/public/configs/mail/index.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - Mail - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

My email configration. Currently I use lieer to fetch emails from Gmail, davmail & offlineimap to fetch emails from MS Exchange, notmuch to index, msmtp to send emails. Also using notmuch frontend from Emacs.

-

My problem with any particular mail setup was that I use Gmail labels quite extensively, and handling these over IMAP is rather awkward. Notmuch seems to be the only software that provides the same first-class support for labels.

-

But I also have an Exchange account, with which I communicate via IMAP/SMTP adapter, and in this case, I synchronize notmuch tags and IMAP folders.

-

References:

- - - -

Lieer

- - - - - - - - - - - -
Guix dependency
python-lieer
-

Lieer is a program to link up Gmail and notmuch. Basically, it downloads mail from Gmail via API, stores them in Maildir, and synchronizes labels with notmuch.

-

I have a separate directory in my ~/Mail for each address. To init lieer, run the following command in the directory:

-
gmi init <address>
-

After which the settings will be stored in gmailieer.json and the credentials in .credentials.gmailieer.json. The latter file is stored encrypted.

-

My preferred settings:

-
gmi set --replace-slash-with-dot
-gmi set --ignore-tags-local new
-

Running gmi sync in the required directory performs the synchronization. The first sync takes a while, the subsequent syncs are pretty fast.

-

DavMail

-

is a gateway between MS Exchange and the rest of the world, which uses IMAP/SMTP/LDAP/etc. As I have one corporate MS Exchange address, this is just the program I need. As of yet, it isn’t packaged for Guix, but it’s easy enough to download.

-

It has a GUI mode, but I prefer headless config.

-
davmail.server=true
-davmail.mode=Auto
-davmail.url=https://mail.etu.ru/owa/
-
-davmail.server.certificate.hash=0C:9E:CF:D3:62:26:DB:FA:F1:EE:36:9D:60:E7:31:71:CF:1F:92:85
-
-davmail.caldavPort=1080
-davmail.imapPort=1143
-davmail.ldapPort=1389
-davmail.popPort=1110
-davmail.smtpPort=1025
-
-davmail.imapAutoExpunge=false
-davmail.enableKeepalive=false
-

Also it’s a bit of problem to get it launched as it looks for its jars in the pwd, so here is a script.

-
cd $HOME/bin/davmail-6.0.0-3375
-./davmail davmail.properties
-

Shepherd service is defined in Desktop.org.

-

OfflineIMAP

- - - - - - - - - - - -
Guix dependency
offlineimap
-

OfflineIMAP is a program that can synchronize IMAP mailbox and Maildir. Lieer does everything by itself, but my pirate Exchange IMAP needs this program. There is also isync, but there I had some weird issues with duplicate UIDs, which don’t occur for OfflineIMAP.

-

I have a few options for setting a username and password. First, I can run pass in remotepasswordeval, and while this will work, it will keep my keyring unlocked because I want to run offlineimap every couple of minutes.

-

Another option is to use noweb and not push the file below to the version control. Then I have a plaintext password of email on my computer, but I think it’s a lesser evil than the entire keyring.

-

I would use password-store-get from password-store.el, but I want this to be able to run without any 3rd party packages, so it’s just bash.

-

-
pass show Job/Infrastructure/pvkorytov@etu.ru | sed -n 's/username: //;2p'
-

-
pass show Job/Infrastructure/pvkorytov@etu.ru | head -n 1
-
[general]
-accounts = pvkorytov
-
-[Account pvkorytov]
-localrepository = pvkorytov-local
-remoterepository = pvkorytov-remote
-
-[Repository pvkorytov-local]
-type = Maildir
-localfolders = ~/Mail/pvkorytov_etu/
-
-[Repository pvkorytov-remote]
-type = IMAP
-remotehost = localhost
-remoteuser = <<mail-username()>>
-remotepass = <<mail-password()>>
-remoteport = 1143
-starttls = no
-ssl = no
-

Notmuch

- - - - - - - - - - - - - - -
Guix dependency
notmuch
parallel
-

Notmuch is an email indexer program, which handles labels in a way somewhat similar to Gmail. It also provides a frontend for Emacs, but it’s not the only one available.

-

Config

-

Not much is going on here.

-

First, the database path.

-
[database]
-path=/home/pavel/Mail
-

My name and list of emails. It’s not like it’s a secret anyhow.

-
[user]
-name=Pavel Korytov
-primary_email=thexcloud@gmail.com
-other_email=progin6304@gmail.com;pvkorytov@etu.ru
-

A list of tags which will be added by notmuch new and directory names which will be ignored by notmuch new.

-
[new]
-tags=new;
-ignore=.osync_workdir;.mbsyncstate;.uidvalidity;.lock;/.*gmailieer\.json.*/
-

Exclude these tags from search by default.

-
[search]
-exclude_tags=trash;spam;
-

Maildir compatibility.

-
[maildir]
-synchronize_flags=true
-

Hooks

-

Now we have to link up lieer & davmail’s maildir and with notmuch. This is done via the notmuch hook system, which allows running custom scripts before and after any command.

-

With lieer and Gmail, it is enough to simply run the program, because Gmail has first-class support for tags. Maildir does not, so I decide to synchronize notmuch tags and IMAP folders. In essence, the idea is to:

- -

The problem is that with that approach one email can have only one tag, but it’s better than nothing.

-

So, here are the rules which match tags & folders:

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tagfolder
inboxINBOX
sentSent
spamJunk
trashTrash
job.digitalJob_Digital
job.digital.docsJob_Digital.Docs
job.digital.supportJob_Digital.Support
job.digital.superserviceJob_Digital.Superservice
-

And below is a noweb function, which generates the following commands for notmuch to execute:

- -

These rules are getting included in the respective hooks.

-

-
(setq my/maildir-root "~/Mail")
-
-(let ((rules '()))
-  (dolist (row tags)
-    (let ((tag (nth 0 row))
-	  (folder (nth 1 row)))
-      (unless (string-empty-p make_tag)
-	(add-to-list
-	 'rules
-	 (format "notmuch tag +%s \"path:%s/%s/cur/** AND NOT tag:%s\""
-		 tag root folder tag)
-	 t))
-      (unless (string-empty-p remove)
-	(add-to-list
-	 'rules
-	 (format "notmuch tag -%s \"NOT path:%s/%s/cur/** AND tag:%s AND tag:%s\""
-		 tag root folder tag root_tag)
-	 t))
-      (unless (string-empty-p move)
-	(add-to-list
-	 'rules
-	 (concat
-	  (format "notmuch search --output=files \"NOT path:%s/%s/cur/** AND tag:%s AND tag:%s\""
-		  root folder tag root_tag)
-	  (format " | xargs -I ! mv ! %s/%s/%s/cur/" my/maildir-root root folder))
-	 t))))
-  (unless (string-empty-p archive_root)
-    (add-to-list
-     'rules
-     (concat
-      (format "notmuch search --output=files \"NOT path:%s/%s/cur/** AND %s AND tag:%s\""
-	      root archive_root
-	      (mapconcat
-	       (lambda (row)
-		 (format "NOT tag:%s" (car row)))
-	       tags
-	       " AND ")
-	      root_tag)
-      (format " | xargs -I ! mv ! %s/%s/%s/cur/" my/maildir-root root archive_root))
-     t))
-  (string-join rules "\n"))
-

pre_new

-

This hook runs fetch from Gmail & offlineimap in parallel before the notmuch new command. The parallel command is provided by GNU Parallel.

-

It isn’t necessary to run cd for offlineimap, but it’s easier to write that way.

-

-
(my/mail-format-tags-rules tags "pvkorytov_etu" "pvkorytov" nil nil t "Archive")
-
# GMI="/home/pavel/Programs/miniconda3/envs/mail/bin/gmi"
-GMI="gmi"
-
-echo "Running pre-new filters"
-<<mail-tags(move="t",archive_root="Archive")>>
-echo "Pre-new filters done"
-
-parallel --link -j0 "(cd /home/pavel/Mail/{1}/ && {2} {3})" ::: thexcloud progin6304 pvkorytov_etu ::: "$GMI" "$GMI" "offlineimap" ::: sync sync ""
-

post_new

-

And this hook tags different mailboxes with different tags.

-

-
(my/mail-format-tags-rules tags "pvkorytov_etu" "pvkorytov" t t)
-
notmuch tag +main "path:thexcloud/** AND tag:new"
-notmuch tag +progin "path:progin6304/** AND tag:new"
-notmuch tag +pvkorytov "path:pvkorytov_etu/** AND tag:new"
-
-echo "Running post-new filters"
-<<mail-tags(make_tag="t",remove="t")>>
-echo "Post-new filters done"
-notmuch tag -new "tag:new"
-

Sync script

-

A script to run notmuch new and push a notification if there is new mail.

-
export DISPLAY=:0
-CHECK_FILE="/home/pavel/Mail/.last_check"
-QUERY="tag:unread"
-ALL_QUERY="tag:unread"
-if [ -f "$CHECK_FILE" ]; then
-    DATE=$(cat "$CHECK_FILE")
-    QUERY="$QUERY and date:@$DATE.."
-fi
-
-notmuch new
-NEW_UNREAD=$(notmuch count "$QUERY")
-ALL_UNREAD=$(notmuch count "$ALL_QUERY")
-
-if [ $NEW_UNREAD -gt 0 ]; then
-    MAIN_UNREAD=$(notmuch count "tag:unread AND tag:main")
-    PROGIN_UNREAD=$(notmuch count "tag:unread AND tag:progin")
-    ETU_UNREAD=$(notmuch count "tag:unread AND tag:pvkorytov")
-    read -r -d '' NOTIFICATION <<EOM
-$NEW_UNREAD new messages
-$MAIN_UNREAD thexcloud@gmail.com
-$PROGIN_UNREAD progin6304@gmail.com
-$ETU_UNREAD pvkorytov@etu.ru
-$ALL_UNREAD total
-EOM
-    notify-send "New Mail" "$NOTIFICATION"
-fi
-
-echo "$(date +%s)" > $CHECK_FILE
-

The script is ran via GNU Mcron every 5 minutes.

-
(job "*/5 * * * * " "~/bin/scripts/check-email")
-

MSMTP

- - - - - - - - - - - -
Guix dependency
msmtp
-

Sending emails can be done with MSMTP. It automatially chooses the email address and server based on the contents of the message, which is handy if there are multiple mailboxes to be managed.

-
defaults
-auth on
-tls on
-tls_trust_file /etc/ssl/certs/ca-certificates.crt
-logfile ~/.msmtp.log
-
-account main
-host smtp.gmail.com
-port 587
-from thexcloud@gmail.com
-user thexcloud@gmail.com
-passwordeval "pass show My_Online/APIs/google-main-app-password | head -n 1"
-
-account progin
-host smtp.gmail.com
-port 587
-from progin6304@gmail.com
-user progin6304@gmail.com
-passwordeval "pass show My_Online/ETU/progin6304@gmail.com | head -n 1"
-
-account pvkorytov
-tls off
-auth plain
-host localhost
-port 1025
-from pvkorytov@etu.ru
-user pvkorytov
-passwordeval "pass show Job/Infrastructure/pvkorytov@etu.ru | head -n 1"
-

Emacs

-

Finally, Emacs configuration. Let’s start with some variables:

-
(setq user-mail-address "thexcloud@gmail.com")
-(setq user-full-name "Pavel Korytov")
-

Then, the problem with my Guix setup is that Emacs by default doesn’t see the elisp files of notmuch, so here is a small workaround:

-
(let ((default-directory  "/home/pavel/.guix-extra-profiles/mail/mail/share/emacs/site-lisp"))
-  (normal-top-level-add-subdirs-to-load-path))
-
-

Finally the proper notmuch settings:

-
(use-package notmuch
-  ;; :ensure nil
-  :commands (notmuch notmuch-search)
-  :config
-  (setq mail-specify-envelope-from t)
-  (setq message-sendmail-envelope-from 'header)
-  (setq mail-envelope-from 'header)
-  (setq notmuch-always-prompt-for-sender t)
-  (setq sendmail-program (executable-find "msmtp"))
-  (setq send-mail-function #'sendmail-send-it)
-  (setq mml-secure-openpgp-sign-with-sender t)
-  (setq notmuch-mua-user-agent-function 'notmuch-mua-user-agent-full)
-  (add-hook 'notmuch-hello-mode-hook
-	    (lambda () (display-line-numbers-mode 0))))
-

The file to which this is tangled is read in the init.el.

-

Saved filters and keybindings

-

I want to have the saved filters available in both notmuch interface as as keybindings. So a bit more of abusing org tables.

-

Root keybindings:

-
(my-leader-def
-  :infix "am"
-  "" '(:which-key "notmuch")
-  "m" 'notmuch)
-

- - - - - - - - - - - - - - - - - - - - - - - - - -
Root tagPrefixKeybinding description
maintthexcloud@gmail.com
proginpprogin6304@gmail.com
pvkorytovepvkorytov@etu.ru
-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TagPrefixName
inboxiinbox
unreaduunread
sentssent
aall mail
-

The following formats the tables above to a proper syntax for setq notmuch-saved-searches:

-

-
(let ((searches '()))
-  (dolist (root_tag root_tags)
-    (dolist (tag filter_tags)
-      (add-to-list
-       'searches
-       (format "(:name \"%s\" :query \"%s\")"
-	       (format "%s (%s)"
-		       (nth 0 root_tag)
-		       (nth 2 tag))
-	       (concat "tag:" (nth 0 root_tag)
-		       (unless (string-empty-p (nth 0 tag))
-			 (concat " AND tag:" (nth 0 tag)))))
-       t)))
-  (string-join searches "\n"))
-

And the following does the same for my general.el definer:

-

-
(let ((bindings '()))
-  (dolist (root_tag root_tags)
-    (add-to-list
-     'bindings
-     (format "\"%s\" '(:which-key \"%s\")"
-	     (nth 1 root_tag)
-	     (nth 2 root_tag))
-     t)
-    (dolist (tag filter_tags)
-      (add-to-list
-       'bindings
-       (format "\"%s\" '((lambda () (interactive) (notmuch-search \"%s\")) :which-key \"%s\")"
-	       (concat (nth 1 root_tag) (nth 1 tag))
-	       (concat "tag:" (nth 0 root_tag)
-		       (unless (string-empty-p (nth 0 tag))
-			 (concat " AND tag:" (nth 0 tag))))
-	       (nth 2 tag))
-       t)))
-  (string-join bindings "\n"))
-
(setq notmuch-saved-searches
-      '((:name "drafts" :query "tag:draft")
-	<<format-notmuch-saved-searches()>>))
-
-(my-leader-def
-  :infix "am"
-  <<format-notmuch-keybindings()>>)
-

Signing messages

-
(with-eval-after-load 'notmuch
-  (add-hook 'message-setup-hook 'mml-secure-sign-pgpmime))
-
-(setq mml-secure-key-preferences
-      '((OpenPGP
-	 (sign
-	  ("thexcloud@gmail.com" "914472A1FD6775C166F96EBEED739ADF81C78160"))
-	 (encrypt))
-	(CMS
-	 (sign)
-	 (encrypt))))
-

mailcap

-

mailcap file is a file which defines how to read to different MIME types. Notmuch also uses it, so why not keep it here.

-
audio/*; mpc add %s
-
-image/*; feh %s
-
-application/msword; /usr/bin/xdg-open %s
-application/pdf; zathura %s
-application/postscript ; zathura %s
-
-text/html; firefox %s
-

Guix settings

-

-
(my/format-guix-dependencies)
-
(specifications->manifest
- '(
-   <<packages()>>))
-
-
- -
- - diff --git a/public/configs/readme/index.html b/public/configs/readme/index.html deleted file mode 100644 index c371cdc..0000000 --- a/public/configs/readme/index.html +++ /dev/null @@ -1,384 +0,0 @@ - - - - - - My dotfiles - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-
-
- -

A set of my GNU/Linux configuration files. View at GitHub.

-

The majority of the software is configured with literate configuration strategy via Emacs' Org Mode. This way has its advantages and disadvantages, but overall it’s pretty nice to keep the configs interweaved with comments in a handful of files.

-

The files themselves are managed and deployed via yadm, but I mostly use Org Mode rich noweb whenever I can instead of what yadm offers.

-

My current GNU/Linux distribution is GNU Guix. In the context of this repo, Guix allows me to list all the used programs in manifests, which means I have the same set of programs across multiple machines. Look for tables with “Guix dependency” in the header.

-

Literate configuration files:

- -

Programs used

-

Some of the notable programs are listed in the table below.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GroupProgramPurposeStatusDocumented?Notes
consolebashshelllaunches fish :)Console.org
consolefishshellactiveConsole.org
consolestarshippromptactiveConsole.org
consoletmuxterminal multiplexeractiveConsole.org
consolealacrittyterminal emulatoractiveConsole.org
mailnotmuchmail indexeractiveMail.org, post
maillieergmail API clientactiveMail.org, postcredentials are encrypted
mailmsmtpSMTP clientactiveMail.org
editoremacseverythingactiveEmacs.orgGitHub renders .org files without labels and tangle: no
editorvimtext edtioractive-A minimal config to have a lightweight terminal $EDITOR
editorneovimtext edtiorarchive-
documentslatexmkLaTeX build toolactive-
documentszathurapdf vieweractiveDesktop.org
desktopdunstnotification manageractiveDesktop.org
desktopi3wmtiling WMactiveDesktop.org
desktopkeynavcontrol mouse with keyboardactiveDesktop.org
desktoppolybarstatus baractiveDesktop.org
desktoprofigeneric menuactiveDesktop.org
desktopflameshotscreenshotactiveDesktop.org
desktoppicomX11 compositoractiveDesktop.org
desktopi3blocksstatus bararchive-
internettridactylvim bindings for Firefoxactive-templated with yadm
internetnewsboatterminal RSS readerarchive-urls are encrypted
internetqutebrowserbrowser with vim bindingsarchive-
internetbukubookmarks managerarchive-
audiompdmusic player daemonactive-
audioncmpcppMPD frontendactive-
miscyadmdotfiles manageractive-
miscsunwaitsunrise calculatoractive-
miscvnstattraffic statsactive-
-

Posts about my configuration

- -

Some statistics

-

If you are viewing the file in Emacs, eval the following to show the pictures with reasonable width:

-
(setq-local org-image-actual-width '(1024))
-

History

-
-
- -
-
- -
-
- -

Misc

-

Notes

- -

Uses yadm’s post_alt hook to create symlinks

-

Encrypted files

-
.config/newsboat/urls
-.config/filezilla/sitemanager.xml
-.config/filezilla/filezilla.xml
-Mail/thexcloud/.credentials.gmailieer.json
-Mail/progin6304/.credentials.gmailieer.json
-.emacs.d/dired-bookmarks.el
-.emacs.d/private.org
-.emacs.d/private.el
-.emacs.d/.trello/sqrtminusone.el
-
-
- -
- - diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 9604b47..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index a1c0822..0000000 --- a/public/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Index - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

About

-
-
- -

Pavel Korytov

-

Master’s student of Saint-Petersurg State Electrotechical University, Russia at Software Engineering.

- - - -
- -
- - diff --git a/public/index.xml b/public/index.xml deleted file mode 100644 index 9373708..0000000 --- a/public/index.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - Index on SqrtMinusOne - https://sqrtminusone.xyz/ - Recent content in Index on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 06 Oct 2021 00:00:00 +0000 - - Getting a consistent set of keybindings between i3 and Emacs - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Intro One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3. -But why not just use EXWM? - - - - My EMMS and elfeed setup - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Intro This is the current state of my quest to live in Emacs, at least in part of reading RSS and music. -Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs. -The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i. - - - - Replacing Jupyter Notebook with Org Mode - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Sat, 01 May 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Why? Jupyter Notebook and its successor Jupyter Lab providing an interactive development environment for many programming languages are in lots of ways great pieces of software. -But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. &ldquo;As one can get&rdquo; is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem. -A possibility for change appeared with my discovery of Emacs not so long ago. - - - - Multiple Gmail accounts & labels with Emacs - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Sat, 27 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Intro For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox. -Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it&rsquo;s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications. - - - - Hello, world! - https://sqrtminusone.xyz/posts/hello-world/ - Mon, 01 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/hello-world/ - Hello, world! Eventually, there will be something interesting here. Or not. -Regradless, I&rsquo;ll check if I can write some Python here -print(&#34;Hello, world&#34;) Hello, world - - - - diff --git a/public/logo.svg b/public/logo.svg deleted file mode 100644 index 1f5adf6..0000000 --- a/public/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/public/logo_sq.png b/public/logo_sq.png deleted file mode 100644 index 7efea77..0000000 Binary files a/public/logo_sq.png and /dev/null differ diff --git a/public/ox-hugo/all.png b/public/ox-hugo/all.png deleted file mode 100644 index 2f5d813..0000000 Binary files a/public/ox-hugo/all.png and /dev/null differ diff --git a/public/ox-hugo/emacs-vim.png b/public/ox-hugo/emacs-vim.png deleted file mode 100644 index 7e95dfd..0000000 Binary files a/public/ox-hugo/emacs-vim.png and /dev/null differ diff --git a/public/ox-hugo/emms-screenshot.png b/public/ox-hugo/emms-screenshot.png deleted file mode 100644 index 5705833..0000000 Binary files a/public/ox-hugo/emms-screenshot.png and /dev/null differ diff --git a/public/ox-hugo/i3-emacs-demo.mp4 b/public/ox-hugo/i3-emacs-demo.mp4 deleted file mode 100644 index 052081a..0000000 Binary files a/public/ox-hugo/i3-emacs-demo.mp4 and /dev/null differ diff --git a/public/ox-hugo/literate-config.png b/public/ox-hugo/literate-config.png deleted file mode 100644 index fd508de..0000000 Binary files a/public/ox-hugo/literate-config.png and /dev/null differ diff --git a/public/ox-hugo/mail.png b/public/ox-hugo/mail.png deleted file mode 100644 index ed29000..0000000 Binary files a/public/ox-hugo/mail.png and /dev/null differ diff --git a/public/ox-hugo/main.png b/public/ox-hugo/main.png deleted file mode 100644 index 439e060..0000000 Binary files a/public/ox-hugo/main.png and /dev/null differ diff --git a/public/ox-hugo/notification.png b/public/ox-hugo/notification.png deleted file mode 100644 index 33f32d0..0000000 Binary files a/public/ox-hugo/notification.png and /dev/null differ diff --git a/public/ox-hugo/org-python-screenshot.png b/public/ox-hugo/org-python-screenshot.png deleted file mode 100644 index 9b02f36..0000000 Binary files a/public/ox-hugo/org-python-screenshot.png and /dev/null differ diff --git a/public/ox-hugo/screenshot.png b/public/ox-hugo/screenshot.png deleted file mode 100644 index 5705833..0000000 Binary files a/public/ox-hugo/screenshot.png and /dev/null differ diff --git a/public/posts/2021-02-27-gmail/index.html b/public/posts/2021-02-27-gmail/index.html deleted file mode 100644 index 49ccb2f..0000000 --- a/public/posts/2021-02-27-gmail/index.html +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - Multiple Gmail accounts & labels with Emacs - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Intro

-

For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox.

-

Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it’s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications.

-

Any classical IMAP/SMTP client is hard to use in my case, because a message with multiple labels is copied to IMAP folders for each of the label plus the inbox folder, and the copies look like different messages from the client-side. For example, a message can be read in one label and unread in another.

-

For a few years, my solution was Mailspring, which provides first-class support for labels. However, it has a feature to deploy spy pixels on emails (and offers no protection from them, obviously), the client is Electron-based with a mouse-driven interface, and the sync engine was closed-source at the time.

-

So, I found an alternative in Emacs+notmuch+lieer and ditched one more proprietary app (the last big one I can’t let go of is DataGrip).

-
-
- -
-
- -

Notmuch’s tags are just as advanced as Gmail’s labels, so I have basically the same mail structure accessible from Emacs, Gmail Android client and even the web UI when I don’t have access to the first two.

-

Also, I think the setup I describe here is pretty straightforward and less complex than many I encountered, but my impression is not the most reliable source of such knowledge.

-

In any case, what follows is a description of my current workflow with instructions of varying levels of precision of how to get there.

-

Setting up

-

Gmail

-

Before we start, some setup is required for the Gmail account.

-

First, as there is no way to enable SMTP without IMAP on Gmail, you have to set “Enable IMAP” in the “Forwarding and POP/IMAP” tab in the settings. If you use two-factor auth, generate an app password.

-

Also, make sure your labels do not contain whitespaces because if they do, you will have to type them in quotes all the time.

-

lieer

-

lieer (formerly gmailieer) is a program that uses Gmail API to download email and synchronize Gmail labels with notmuch tags. Because of its usage of Gmail API instead of IMAP, there are no problems with duplicating emails in different labels, etc.

-

As I need to use multiple versions of Python & Node.js for other reasons, I manage my installations of them with Anaconda (Miniconda, to be precise). You may instead use venv or even the system-wide installation of Python and omit the conda clauses, but in my experience Anaconda makes life easier in that regard.

-
# Create an environment with the name "mail"
-conda create --name mail
-# Activate the environment
-conda activate mail
-# Install Python
-conda install python
-# Download and install lieer
-git clone https://github.com/gauteh/lieer.git
-cd lieer
-pip install .
-

After which we may check if the gmi executable is available:

-
which gmi
-

Notmuch

-

Notmuch is present in most of the package repositories, so you can install it with your package manager, which is pacman in my case.

-
sudo pacman -S notmuch
-

After the installation, run notmuch setup. That will inquire the parameters and create the .notmuch-config file with the answers.

-
Your full name [Pavel]: Pavel Korytov
-Your primary email address [pavel@pdsk.(none)]: thexcloud@gmail.com
-Additional email address [Press 'Enter' if none]:
-Top-level directory of your email archive [/home/pavel/mail]: /home/pavel/Mail
-Tags to apply to all new messages (separated by spaces) [unread inbox]: new
-Tags to exclude when searching messages (separated by spaces) [deleted spam]:
-

It is important to set the new tag for the new messages instead of the default unread and inbox.

-

Next, add the rule to ignore JSON files to the [new] section of the .notmuch-config file, so it would look like this:

-
[new]
-tags=new
-ignore=/.*[.](json|lock|bak)$/
-

That is needed to ignore the lieer config files. Although, as I have noticed, notmuch is generally pretty good at detecting wrong files in its directories, an explicit ignore rule won’t hurt.

-

Now, create the mail directory and run the notmuch new command. As notmuch has probably already noticed you, it uses the maildir format, which basically means that one message is stored in one file.

-
# The same directory mentioned in the 4th question
-mkdir ~/Mail
-# Initialize notmuch
-notmuch new
-

Add an account

-

After that, we can create a directory for a mail account and initialize lieer.

-
cd ~/Mail
-# Use whatever name you want
-mkdir thexcloud
-cd thexcloud
-# Intialize lieer
-gmi init thexcloud@gmail.com
-

Running gmi init will run an OAuth authentication to your Gmail account. The credentials will be stored in .credentials.gmailieer.json file, so make sure not to expose it somewhere.

-

We also can add a few settings for lieer, which will make life easier. First, dots seem to be less awkward to type than slashes for the nested tags:

-
gmi set --replace-slash-with-dot
-

Then, we don’t want the new tag to be pushed back to Gmail

-
gmi set --ignore-tags-local new
-

Now we can finally download the mail directory. To initiate the download, run

-
gmi sync
-

The first download can easily take several hours, depending on the size of your email and the speed of your internet connection, but subsequent runs will be much faster.

-

The last thing to do here is to add the gmi sync command to notmuch’s pre-new hook, so that the email will be synchronized on the notmuch new command.

-
# Create the hooks folder
-mkdir -p ~/Mail/.notmuch/hooks
-# Create the file
-cd ~/Mail/.notmuch/hooks
-cat > pre-new <<EOF
-#!/bin/bash
-eval "$(conda shell.bash hook)"
-conda activate mail
-(cd /home/pavel/Mail/thexcloud/ && gmi sync)
-EOF
-chmod +x pre-new
-

Side note: as a hook for conda tends to be rather slow, I run the gmi command with system-wide Python as follows:

-
#!/bin/bash
-GMI="/home/pavel/Programs/miniconda3/envs/mail/bin/gmi"
-(cd /home/pavel/Mail/thexcloud/ && $GMI sync)
-

Which doesn’t seem to cause any particular trouble in that case.

-

Emacs

-

There are plenty of different frontends for notmuch (even GUI apps), but the one I’m sticking with the Emacs.

-

Configuration for Emacs is pretty straightforward, but you probably want to use the notmuch package which came with the system package, because otherwise, you may end up with different versions of frontend and backend.

-

That’s how it can be done with use-package:

-
(use-package notmuch
-  :ensure nil
-  :commands (notmuch)
-  :config
-  (add-hook 'notmuch-hello-mode-hook
-            (lambda () (display-line-numbers-mode 0))))
-

The only notable observation here is that display-line-numbers-mode seems to break formatting of the notmuch-hello page.

-

If you use evil-mode, you also should enable the evil-collection mode for notmuch.

-

Now run M-x notmuch and the notmuch-hello page should appear. Running notmuch-poll-and-refresh-this-buffer (gR with evil bindings) will run the notmuch new command and refresh the buffer. All the syncronized messages should be present.

-

I should note that notmuch frontend for Emacs is the most user-friendly Emacs app I have seen so far. UI, commands and keybindings are self-descriptive, all the options can be configured with the build-in customize interface. It may be useful to look through emacs tips at the official site and notmuch man pages, in particular syntax for notmuch queries.

-

Reading mail

-

notmuch-search-show-thread (RET) opens the thread under the cursor.

-

notmuch-show-view-part (. v with evil) opens an attachment with associations defined in .mailcap file. Mine looks like this:

-
audio/*; mpc add %s
-
-image/*; feh %s
-
-application/msword; /usr/bin/xdg-open %s
-application/pdf; zathura %s
-application/postscript ; zathura %s
-
-text/html; /usr/bin/xdg-open %s
-

Here watch out for the last line, default version of which may be set as follows:

-
text/html; /usr/bin/xdg-open %s ; copiousoutput
-

Which causes a temporary file to be deleted before it could be opened because recent versions of xdg-open do not block the input.

-

As expected, Emacs mail reader does not trigger any spy pixels or other tracking contents of email (not any I know of, at least). However, opening an HTML email in a browser will even run embedded JavaScript. Therefore, in no case open emails you do not trust with xdg-open. Even if you use NoScript, the browser will still load all the CSS, videos and even iframes, which can be used to track you.

-

Even Gmail web UI is preferable to view the message in a browser, because the former blocks most of the malicious stuff and does not seem to leak your IP to the sender, for what it’s worth.

-

Sending mail

-

To start composing a message, run notmuch-mua-new-mail (C with evil bindings).

-

After doing so, C-c C-c will run notmuch-mua-send-and-exit, which will invoke the function stated in the message-send-mail-function variable. The default value of the variable is sendmail-query-once, which will inquire the parameters and save them as custom variables.

-

If SMTP is used, send-mail-function will be set to the one from the built-it smtpmail package. SMTP parameters for Gmail are listed here.

-

Authorization parameters will be saved to your authinfo file. If you didn’t have one, the plaintext .authinfo will be created, so it’s reasonable to encrypt it:

-
cd ~
-gpg -o .authinfo.gpg -c --cipher-algo AES256 .authinfo
-

However, if you plan to use multiple accounts with different SMTP servers, it makes more sense to use something like MSMTP to manage multiple accounts. Here are a couple of examples (1, 2) how to do that.

-

Another alternative for Gmail is to use lieer as sendmail program. That may make sense if you don’t want to enable IMAP and SMTP on your account.

-

There are also a bunch of ways to set up address completion if the built-in completion based on notmuch database does not suffice.

-

I also use LanguageTool for Emacs to do a spell checking of important emails (integrations like that really make Emacs shine). For some reason, developers don’t give a link to download the server on the frontpage, so here it is. And here is the relevant part of my Emacs config:

-
(use-package langtool
-  :straight t
-  :commands (langtool-check)
-  :config
-  (setq langtool-language-tool-server-jar "/home/pavel/Programs/LanguageTool-5.1/languagetool-server.jar")
-  (setq langtool-mother-tongue "ru"))
-

As a last note here, to set up a signature create the .signature file in the $HOME directory. If you need more complex logic here, for instance, different signatures for different accounts, you can put an arbitrary expression to the mail-signature variable or apply this gnus-alias tip.

-

Another account

-

Adding an account

-

Now we can send and receive mail from one account. Adding another account is also pretty easy.

-

If another account is Gmail, the process starts the same as before:

-
# Create a directory
-mkdir -p ~/Mail/progin6304
-cd ~/Mail/progin6304
-# OAuth
-gmi init progin6304@gmail.com
-# Settings
-gmi set --replace-slash-with-dot
-

However, before running gmi sync for the second account, we want to make sure that we can distinguish the message from different accounts. To do that, I add the main for the main account and progin for the second account. We also don’t want these labels to be pushed:

-
cd ~/Mail/thexcloud
-gmi set --ignore-tags-local new,mail,progin
-cd ~/Mail/progin6304
-gmi set --ignore-tags-local new,mail,progin
-

Now we can use notmuch’s post-new hook to tag the messages based on their folder as follows:

-
cd ~/Mail/.notmuch/hooks
-cat > post-new <<EOF
-#!/bin/bash
-notmuch tag +main "path:thexcloud/** AND tag:new"
-notmuch tag +progin "path:progin6304/** AND tag:new"
-notmuch tag -new "tag:new"
-EOF
-chmod +x post-new
-

Now it finally makes sense why we wanted to use the new tag in the first place. In principle, any kind of tagging logic can be applied here, but for the reasons I stated earlier, I prefer to set up filters in the Gmail web interface.

-

The last thing to do is to modify the pre-new hook:

-
#!/bin/bash
-GMI="/home/pavel/Programs/miniconda3/envs/mail/bin/gmi"
-(cd /home/pavel/Mail/thexcloud/ && $GMI sync)
-(cd /home/pavel/Mail/progin6304/ && $GMI sync)
-

After which we can finally tag the existing messages and download ones from the new account

-
notmuch tag +main "path:thexcloud/**"
-notmuch new
-

The obvious problem, however, is that the messages are fetched sequentially, which is rather slow. A solution is to use something like GNU Parallel:

-
#!/bin/bash
-GMI="/home/pavel/Programs/miniconda3/envs/mail/bin/gmi"
-parallel -j0 "(cd /home/pavel/Mail/{}/ && $GMI sync)" ::: thexcloud progin6304
-

I haven’t encountered any trouble with that solution so far (and I don’t see anything thread-unsafe in the lieer code), but I’ll keep an eye on that.

-

In principle, it shouldn’t be too hard to add a normal IMAP account as well with mbsync, but I expect it would require something like iterating through the directory structure and assigning notmuch labels based on that. I’ll probably try that some time in the future.

-

Emacs

-

With that done, I also want separate entries on the start page for each of the accounts. Doing that is easy enough, just modify the notmuch-saved-searches variable with customize-group or like this:

-
(setq notmuch-saved-searches
-   '((:name "inbox (main)" :query "tag:inbox AND tag:main")
-     (:name "unread (main)" :query "tag:unread AND tag:main")
-     (:name "sent (main)" :query "tag:sent AND tag:main")
-     (:name "all mail (main)" :query "tag:main")
-     (:name "inbox (progin)" :query "tag:inbox AND tag:progin")
-     (:name "unread (progin)" :query "tag:unread AND tag:progin")
-     (:name "sent (progin)" :query "tag:sent AND tag:progin")
-     (:name "all main (progin)" :query "tag:progin")
-     (:name "drafts" :query "tag:draft")))
-

Notification for new messages

-

Now, we can send and receive mail, but we also probably want notifications for new emails. To do that, I wrote a simple script:

-
#!/bin/bash
-# To run notify-send from cron
-export DISPLAY=:0
-# A file with last time of sync
-CHECK_FILE="/home/pavel/Mail/.last_check"
-QUERY="tag:unread"
-ALL_QUERY="tag:unread"
-# If the file exists, check also the new messages from the last sync
-if [ -f "$CHECK_FILE" ]; then
-    DATE=$(cat "$CHECK_FILE")
-    QUERY="$QUERY and date:@$DATE.."
-fi
-
-notmuch new
-NEW_UNREAD=$(notmuch count "$QUERY")
-ALL_UNREAD=$(notmuch count "$ALL_QUERY")
-
-# I don't really care if there are unread messages for which I've already seen a notification
-if [ $NEW_UNREAD -gt 0 ]; then
-    MAIN_UNREAD=$(notmuch count "tag:unread AND tag:main")
-    PROGIN_UNREAD=$(notmuch count "tag:unread AND tag:progin")
-    read -r -d '' NOTIFICATION <<EOM
-$NEW_UNREAD new messages
-$MAIN_UNREAD thexcloud@gmail.com
-$PROGIN_UNREAD progin6304@gmail.com
-$ALL_UNREAD total
-EOM
-    notify-send "New Mail" "$NOTIFICATION"
-fi
-
-# Save sync timestamp
-echo "$(date +%s)" > $CHECK_FILE
-

The script is launched with cron every 5 minutes:

-
*/5 * * * * bash /home/pavel/bin/scripts/check-email
-

Here’s how the notification looks like: -

-

Caveats

- - -
- -
- - diff --git a/public/posts/2021-05-01-org-python/index.html b/public/posts/2021-05-01-org-python/index.html deleted file mode 100644 index 0d4fa4c..0000000 --- a/public/posts/2021-05-01-org-python/index.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - Replacing Jupyter Notebook with Org Mode - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-
-
- -

Why?

-

Jupyter Notebook and its successor Jupyter Lab providing an interactive development environment for many programming languages are in lots of ways great pieces of software.

-

But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. “As one can get” is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem.

-

A possibility for change appeared with my discovery of Emacs not so long ago. Emacs, a substantially more extensible piece of software, potentially can be used for the mentioned kind of programming. So I decided to try.

-

Sometime past that decision, it’s time to wrap up the results. To start with, I’ll briefly discuss the pros & cons of using Org mode rather than Jupyter Notebook/Lab. Here is my list of advantages:

- -

The first point deserves to be spelled out with more detail. To start with, Emacs is an objectively better text editor than Jupyter, as Emacs offers a considerable superset of Jupyter’s features concerning just writing and editing text. The farthest one can go with Jupyter Lab is to install a vim emulation plugin, which still isn’t as good as Evil mode.

-

The fact that Emacs can be used for different purposes also helps. For instance, I often write LaTeX documents, which are loosely based on the nearby code, e.g. using some generated charts or tables. Switching an Emacs buffer is easier than switching between Emacs and browser, not to mention that Emacs buffers usually have the same set of keybindings.

-

Emacs' buffer management system, which is good enough for a window manager, is barely comparable to the tabs of Jupyter Lab. And so on.

-

As for why one may want to use Jupyter instead, here is my take on cons:

- -

Separation of kernels, server, and client together with non-blocking JavaScript-based UI is a good argument for using Jupyter. And it certainly won’t be a problem for a browser to suddenly print a line a million characters long.

-

As for the richness of the output, while there are ways to work around the limitations of Emacs there, in some cases the best thing one can do is open the output in question with a browser. I’ll discuss doing that further below.

-

The collaboration issue can be alleviated with rich export capabilities, but if someone wants to change something in your Org file, execute it and send it back to you, they have to use Emacs.

-

Basic setup

-

The core package to this whole venture is emacs-jupyter (another notable alternative ein, using which can help with the collaboration problem).

-

Install it however you install packages in Emacs, here is my preferred way with use-package and straight.el:

-
(use-package jupyter
-  :straight t)
-

Then, we have to enable languages for org-babel. Put the following in your org mode config section:

-
(org-babel-do-load-languages
- 'org-babel-load-languages
- '((emacs-lisp . t) ;; Other languages
-   (shell . t)
-   ;; Python & Jupyter
-   (python . t)
-   (jupyter . t)))
-

Now, you should be able to use source blocks with names like jupyter-LANG, e.g. jupyter-python. To use just LANG src blocks, call the following function after org-babel-do-load-languages:

-
(org-babel-jupyter-override-src-block "python")
-

That overrides the built-in python block with jupyter-python.

-

If you use ob-async, you have to set jupyter-LANG blocks as ignored by this package, because emacs-jupyter has async execution of its own.

-
(setq ob-async-no-async-languages-alist '("python" "jupyter-python"))
-

Environments

-

So, we’ve set up a basic emacs-jupyter configuration.

-

The catch here is that Jupyter should be available on Emacs startup (at the time of evaluation of the emacs-jupyter package, to be precise). That means, if you are launching Emacs with something like an application launcher, global Python & Jupyter will be used.

-
import sys
-sys.executable
-
/usr/bin/python3
-

Which is probably not what we want. To resolve that, we have to make the right Python available at the required time.

-

Anaconda

-

If you were using Jupyter Lab or Notebook before, there is a good chance you install it via Anaconda. If not, in a nutshell, it is a package & environment manager, which specializes in Python & R, but also supports a whole lot of stuff like Node.js. In my opinion, it is the easiest way to manage multiple Python installations if you don’t use some advanced package manager like Guix.

-

As one may expect, there is an Emacs package called conda.el to help working with conda environments in Emacs. We have to put it somewhere before the emacs-jupyter package and call conda-env-activate:

-
(use-package conda
-  :straight t
-  :config
-  (setq conda-anaconda-home (expand-file-name "~/Programs/miniconda3/"))
-  (setq conda-env-home-directory (expand-file-name "~/Programs/miniconda3/"))
-  (setq conda-env-subdirectory "envs"))
-
-(unless (getenv "CONDA_DEFAULT_ENV")
-  (conda-env-activate "base"))
-

If you have Anaconda installed on a custom path, as I do, you’d have to add these setq lines in the :config section. Also, there is no point in activating the environment if Emacs is somehow already launched in an environment.

-

That’ll give us Jupyter from a base conda environment.

-

If you use a plain virtual environment, you can use virtualenvwrapper.el, which is similar in its design to conda.el (or, rather, the other way round).

-

Switching an environment

-

However, as you will notice rather soon, emacs-jupyter will always use the Python kernel found on startup. So if you switch to a new environment, the code will still be running in the old one, which is not too convenient.

-

Fortunately, to fix that we have only to force the refresh of Jupyter kernelspecs:

-
(defun my/jupyter-refresh-kernelspecs ()
-  "Refresh Jupyter kernelspecs"
-  (interactive)
-  (jupyter-available-kernelspecs t))
-

Calling M-x my/jupyter-refresh-kernelspecs after an environment switch will give you a new kernel. Just keep in mind that a kernelspec seems to be attached to a session, so you’d also have to change the session name to get a new kernel.

-
import sys
-sys.executable
-
/home/pavel/Programs/miniconda3/bin/python
-
(conda-env-activate "ann")
-
import sys
-sys.executable
-
/home/pavel/Programs/miniconda3/bin/python
-
(my/jupyter-refresh-kernelspecs)
-
import sys
-sys.executable
-
/home/pavel/Programs/miniconda3/envs/ann/bin/python
-

Programming

-

To test if everything is working correctly, run M-x jupyter-run-repl, which should give you a REPL with a chosen kernel. If so, we can finally start using Python in org mode.

-
#+begin_src python :session hello :async yes
-print('Hello, world!')
-#+end_src
-
-#+RESULTS:
-: Hello, world!
-#+end_src
-

To avoid repeating similar arguments for the src block, we can set the header-args property at the start of the file:

-
#+PROPERTY: header-args:python :session hello
-#+PROPERTY: header-args:python+ :async yes
-

When a kernel is initialized, an associated REPL buffer is also created with a name like *jupyter-repl[python 3.9.2]-hello*.

-

One advantage of emacs-jupyter over the standard Org source execution is that kernel requests for input are queried through the minibuffer. So, you can run a code like this:

-
#+begin_src python
-name = input('Name: ')
-print(f'Hello, {name}!')
-#+end_src
-
-#+RESULTS:
-: Hello, Pavel!
-

without any additional setup.

-

Code output

-

Images

-

Image output should work out of the box. Run M-x org-toggle-inline-images (C-c C-x C-v) after the execution to see the image inline.

-
#+begin_src python
-import matplotlib.pyplot as plt
-fig, ax = plt.subplots()
-ax.plot([1, 2, 3, 4], [1, 4, 2, 3])
-pass
-#+end_src
-
-#+RESULTS:
-[[file:./.ob-jupyter/86b3c5e1bbaee95d62610e1fb9c7e755bf165190.png]]
-

There is some room for improvement though. First, you can add the following hook if you don’t want to press this awkward keybinding every time:

-
(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images)
-

Second, we may override the image save path like this:

-
#+begin_src python :file img/hello.png
-import matplotlib.pyplot as plt
-fig, ax = plt.subplots()
-ax.plot([1, 2, 3, 4], [1, 4, 2, 3])
-pass
-#+end_src
-
-#+RESULTS:
-[[file:img/hello.png]]
-

That can save you a savefig call if the image has to be used somewhere further.

-

Finally, by default, the image has a transparent background and a ridiculously small size. That can be fixed with some matplotlib settings:

-
import matplotlib as mpl
-
-mpl.rcParams['figure.dpi'] = 200
-mpl.rcParams['figure.facecolor'] = '1'
-

At the same time, we can set the image width to prevent images from becoming too large. I prefer to do it inside a emacs-lisp code block in the same org file:

-
(setq-local org-image-actual-width '(1024))
-

Basic tables

-

If you are evaluating something like pandas DataFrame, it will be outputted in the HTML format, wrapped in the begin_export block. To view the data in text format, you can set :display plain:

-
#+begin_src python :display plain
-import pandas as pd
-pd.DataFrame({"a": [1, 2], "b": [3, 4]})
-#+end_src
-
-#+RESULTS:
-:    a  b
-: 0  1  3
-: 1  2  4
-

Another solution is to use something like the tabulate package:

-
#+begin_src python
-import pandas as pd
-import tabulate
-df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
-print(tabulate.tabulate(df, headers=df.columns, tablefmt="orgtbl"))
-#+end_src
-
-#+RESULTS:
-: |    |   a |   b |
-: |----+-----+-----|
-: |  0 |   1 |   3 |
-: |  1 |   2 |   4 |
-

HTML & other rich output

-

Yet another solution is to use emacs-jupyter’s option :pandoc t, which invokes pandoc to convert HTML, LaTeX, and Markdown to Org. Predictably, this is slower than the options above.

-
#+begin_src python :pandoc t
-import pandas as pd
-df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
-df
-#+end_src
-
-#+RESULTS:
-:RESULTS:
-|   | a | b |
-|---+---+---|
-| 0 | 1 | 3 |
-| 1 | 2 | 4 |
-:END:
-

Also, every once in a while I have to view an actual, unconverted HTML in a browser, e.g. when using folium or displaCy.

-

To do that, I’ve written a small function, which performs xdg-open on the HTML export block under the cursor:

-
(setq my/org-view-html-tmp-dir "/tmp/org-html-preview/")
-
-(use-package f
-  :straight t)
-
-(defun my/org-view-html ()
-  (interactive)
-  (let ((elem (org-element-at-point))
-        (temp-file-path (concat my/org-view-html-tmp-dir (number-to-string (random (expt 2 32))) ".html")))
-    (cond
-     ((not (eq 'export-block (car elem)))
-      (message "Not in an export block!"))
-     ((not (string-equal (plist-get (car (cdr elem)) :type) "HTML"))
-      (message "Export block is not HTML!"))
-     (t (progn
-          (f-mkdir my/org-view-html-tmp-dir)
-          (f-write (plist-get (car (cdr elem)) :value) 'utf-8 temp-file-path)
-          (start-process "org-html-preview" nil "xdg-open" temp-file-path))))))
-

f.el is used by a lot of packages, including the above-mentioned conda.el, so you probably already have it installed.

-

Put a cursor on the begin_export html block and run M-x my/org-view-html.

-

There also seems to be widgets support in emacs-jupyter, but I wasn’t able to make it work.

-

DataFrames

-

Last but not least option I want to mention here is specifically about pandas' DataFrames. There aren’t many good options to view the full dataframe inside Emacs. One way I can think of is to save the dataframe in CSV and view it with csv-mode.

-

However, there are standalone packages to view dataframes. One I can point out is dtale, which is a Flask + React app designed just for that purpose. It has a rather extensive list of features, including charting, basic statistical instruments, filters, etc. Here is an online demo.

-

The problem here is that it’s a browser app, which means it defies one of the purposes of using Org Mode in the first place. What’s more, this application is a rather huge one with lots of dependencies, and they have to be installed in the same environment as your project.

-

So this approach has its pros and cons as well. Example usage is as follows:

-
import dtale
-d = dtale.show(df)
-d.open_browser() # Or get an URL from d._url
-

Another notable alternative is PandasGUI, which, as one can guess, is a GUI (PyQt5) application, although it uses QtWebEngine inside.

-

Remote kernels

-

There are yet some problems in the current configuration.

- -

Using a “remote” kernel

-

For the reasons above I sometimes prefer to use a standalone kernel. To start a Jupyter kernel, run the following command in the environment and path you need:

-
jupyter kernel --kernel=python
-

After the kernel is launched, write the path to the connection file into the :session header and press C-c C-c to refresh the setup:

-
#+PROPERTY: header-args:python :session /home/pavel/.local/share/jupyter/runtime/kernel-e770599c-2c98-429b-b9ec-4d1ddf5fc16c.json
-

Now python source blocks should be executed in the kernel.

-

To open a REPL, run M-x jupyter-connect-repl and select the given JSON. Or launch a standalone REPL like this:

-
jupyter qtconsole --existing kernel-e770599c-2c98-429b-b9ec-4d1ddf5fc16c.json
-

Executing a piece of code in the REPL allows proper debugging, for instance with %pdb magic. Also, Jupyter QtConsole generally handles large outputs better and even allows certain kinds of rich output in the REPL.

-

Some automation

-

Now, I wouldn’t use Emacs if it wasn’t possible to automate at least some of the listed steps. So here are the functions I’ve written for that.

-

First, we need to get open ports on the system:

-
(defun my/get-open-ports ()
-  (mapcar
-   #'string-to-number
-   (split-string (shell-command-to-string "ss -tulpnH | awk '{print $5}' | sed -e 's/.*://'") "\n")))
-

Then, list the available kernel JSONs:

-
(setq my/jupyter-runtime-folder (expand-file-name "~/.local/share/jupyter/runtime"))
-
-(defun my/list-jupyter-kernel-files ()
-  (mapcar
-   (lambda (file) (cons (car file) (cdr (assq 'shell_port (json-read-file (car file))))))
-   (sort
-    (directory-files-and-attributes my/jupyter-runtime-folder t ".*kernel.*json$")
-    (lambda (x y) (not (time-less-p (nth 6 x) (nth 6 y)))))))
-

And query the user for a running kernel:

-
(defun my/select-jupyter-kernel ()
-  (let ((ports (my/get-open-ports))
-        (files (my/list-jupyter-kernel-files)))
-    (completing-read
-     "Jupyter kernels: "
-     (seq-filter
-      (lambda (file)
-        (member (cdr file) ports))
-      files))))
-

After which we can use the my/select-jupyter-kernel function however we want:

-
(defun my/insert-jupyter-kernel ()
-  "Insert a path to an active Jupyter kernel into the buffer"
-  (interactive)
-  (insert (my/select-jupyter-kernel)))
-
-(defun my/jupyter-connect-repl ()
-  "Open emacs-jupyter REPL, connected to a Jupyter kernel"
-  (interactive)
-  (jupyter-connect-repl (my/select-jupyter-kernel) nil nil nil t))
-
-(defun my/jupyter-qtconsole ()
-  "Open Jupyter QtConsole, connected to a Jupyter kernel"
-  (interactive)
-  (start-process "jupyter-qtconsole" nil "setsid" "jupyter" "qtconsole" "--existing"
-                 (file-name-nondirectory (my/select-jupyter-kernel))))
-

The first function, which simply inserts the path to the kernel, is meant to be used on the :session header. One can go even further and locate the header automatically, but that’s an idea for next time.

-

The second one opens a REPL provided by emacs-jupyter. The t argument is necessary to pop up the REPL immediately.

-

The last one launches Jupyter QtConsole. setsid is required to run the program in a new session, so it won’t close together with Emacs.

-

Cleaning up

-

I’ve also noticed that there are JSON files left in the runtime folder whenever the kernel isn’t stopped correctly. So here is a cleanup function.

-
(defun my/jupyter-cleanup-kernels ()
-  (interactive)
-  (let* ((ports (my/get-open-ports))
-         (files (my/list-jupyter-kernel-files))
-         (to-delete (seq-filter
-                     (lambda (file)
-                       (not (member (cdr file) ports)))
-                     files)))
-    (when (and (length> to-delete 0)
-               (y-or-n-p (format "Delete %d files?" (length to-delete))))
-      (dolist (file to-delete)
-        (delete-file (car file)))))
-

Export

-

An uncountable number of articles have been written already on the subject of Org Mode export, so I will just cover my particular setup.

-

HTML

-

Export to a standalone HTML is an easy way to share the code with someone who doesn’t use Emacs, just remember that HTML may not be the only file you’d have to share if you have images in the document. Although you may use something like htmlark later to get a proper self-contained HTML.

-

To do the export, run M-x org-html-export-to-html. It should work out of the box, however, we can improve the output a bit.

-

First, we can add a custom CSS to the file. I like this one:

-
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="https://gongzhitaao.org/orgcss/org.css"/>
-

To get a syntax highlighting, we need the htmlize package:

-
(use-package htmlize
-  :straight t
-  :after ox
-  :config
-  (setq org-html-htmlize-output-type 'css))
-

If you use the rainbow-delimeters package, as I do, default colors for delimiters may not look good with the light theme. To fix such issues, put an HTML snippet like this in a begin_export html block or a CSS file:

-
<style type="text/css">
-.org-rainbow-delimiters-depth-1, .org-rainbow-delimiters-depth-2, .org-rainbow-delimiters-depth-3, .org-rainbow-delimiters-depth-4 {
-    color: black
-}
-</style>
-

LaTeX -> pdf

-

Even though I use LaTeX quite extensively, I don’t like to add another layer of complexity here and 98% of the time write plain .tex files. LaTeX by itself provides many good options whenever you need to write a document together with some data or source code, contrary to “traditional” text processors.

-

Nevertheless, I want to get at least a tolerable pdf from Org, so here is a piece of my config with some inline comments.

-
(defun my/setup-org-latex ()
-  (setq org-latex-compiler "xelatex") ;; Probably not necessary
-  (setq org-latex-pdf-process '("latexmk -outdir=%o %f")) ;; Use latexmk
-  (setq org-latex-listings 'minted)   ;; Use minted to highlight source code
-  (setq org-latex-minted-options      ;; Some minted options I like
-        '(("breaklines" "true")
-          ("tabsize" "4")
-          ("autogobble")
-          ("linenos")
-          ("numbersep" "0.5cm")
-          ("xleftmargin" "1cm")
-          ("frame" "single")))
-  ;; Use extarticle without the default packages
-  (add-to-list 'org-latex-classes
-               '("org-plain-extarticle"
-                 "\\documentclass{extarticle}
-[NO-DEFAULT-PACKAGES]
-[PACKAGES]
-[EXTRA]"
-                 ("\\section{%s}" . "\\section*{%s}")
-                 ("\\subsection{%s}" . "\\subsection*{%s}")
-                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
-                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
-                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
-
-;; Make sure to eval the function when org-latex-classes list already exists
-(with-eval-after-load 'ox-latex
-  (my/setup-org-latex))
-

In the document itself, add the following headers:

-
#+LATEX_CLASS: org-plain-extarticle
-#+LATEX_CLASS_OPTIONS: [a4paper, 14pt]
-

14pt size is required by certain state standards of ours for some reason.

-

After which you can put whatever you want in the preamble with LATEX_HEADER. My workflow with LaTeX is to write a bunch of .sty files beforehand and import the necessary ones in the preamble. Here is the repo with these files, although quite predictably, it’s a mess. At any rate, I have to write something like the following in the target Org file:

-
#+LATEX_HEADER: \usepackage{styles/generalPreamble}
-#+LATEX_HEADER: \usepackage{styles/reportFormat}
-#+LATEX_HEADER: \usepackage{styles/mintedSourceCode}
-#+LATEX_HEADER: \usepackage{styles/russianLocale}
-

M-x org-latex-export-to-latex should export the document to the .tex file. As an alternative, run M-x org-export-dispatch (by default should be on C-c C-e) an pick the required option there.

-

ipynb

-

One last export backend I want to mention is ox-ipynb, which allows exporting Org documents to Jupyter notebooks. Sometimes it works, sometimes it doesn’t.

-

Also, the package isn’t on MELPA, so you have to install it from the repo directly.

-
(use-package ox-ipynb
-  :straight (:host github :repo "jkitchin/ox-ipynb")
-  :after ox)
-

To (try to) do export, run M-x ox-ipynb-export-org-file-ipynb-file.

- -
- -
- - diff --git a/public/posts/2021-09-07-emms/index.html b/public/posts/2021-09-07-emms/index.html deleted file mode 100644 index ccfa284..0000000 --- a/public/posts/2021-09-07-emms/index.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - My EMMS and elfeed setup - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Intro

-
-
- -

This is the current state of my quest to live in Emacs, at least in part of reading RSS and music.

-

Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs.

-

The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i.e. I had to figure out the source code in both cases. I even submitted a small patch to EMMS to make it parse my MPD library correctly.

-

But in the end, only extensive customization capacities of Emacs enabled me to make a setup where these parts nicely come together and do more or less exactly what I want. However, this means there are a lot of degrees of freedom involved, so I’ll try to cover the important parts and link to the original sources wherever possible.

-

I’d call it “workflow”, but the “work” part does not quite catch the point here.

-

MPD

-

So, we have to start somewhere.

-

MPD is a server for playing music, although it is usually hosted on the local machine, i.e. the one on which you intend to listen to music. There is bunch of clients available (take a look at ncmpcpp is you like terminal-based apps), but here our point of interest is its integration with EMMS.

-

While EMMS is capable of playing music without it, MPD has the advantage of being independent of Emacs. That means it won’t close if Emacs crashes and it can be controlled more easily with other means.

-

MPD configuration is a pretty easy process. First, install MPD and mpc (a minimal MPD CLI client) from your distribution’s package repository. After doing that, you’d have to create a config file at the location ~/.config/mpd/mpd.conf. Mine looks something like this:

-
music_directory     "~/Music"
-playlist_directory  "~/.mpd/playlists"
-db_file             "~/.mpd/database"
-log_file            "~/.mpd/log"
-pid_file            "~/.mpd/pid"
-state_file          "~/.mpd/state"
-sticker_file        "~/.mpd/sticker.sql"
-
-audio_output {
-  type    "pulse"
-  name    "My Pulse Output"
-}
-

Here music_directory is, well, a directory in which MPD will look for music files. Take a look at man mpd.conf and the default config example for more information.

-

Because MPD is a daemon, it has to be started in order to work. The easiest way is to add mpd to your init system, e.g. with GNU Shepherd:

-
(define mpd
-  (make <service>
-    #:provides '(mpd)
-    #:respawn? #t
-    #:start (make-forkexec-constructor '("mpd" "--no-daemon"))
-    #:stop (make-kill-destructor)))
-

You can also launch mpd manually, as it will daemonize itself by default. To check if MPD is working, run mpc status:

-
mpc status
-

Take a look at the official documentation for more information on this subject.

-

Music directory

-

The next question after we’ve set up MPD is how to organize the music directory.

-

MPD itself is not too concerned about the structure of your music_directory, because it uses audio tags to classify the files. However, if you want to have album covers in EMMS, you need to have one folder per one album. Otherwise and in other respects, the structure can be arbitrary.

-

So we need to tag the audio files. My favorite audio-tagging software is MusicBrainz Picard, which can set tags automatically even if the file has no metadata at all, and move the file automatically according to the configuration. The aforementioned ncmpcpp also has a decent tag editor; finally, there is a simple mutagen-based CLI tool.

-

EMMS

-

EMMS is the Emacs Multimedia System, a package that can get play stuff from various sources using various players. It is a part of Emacs, which means you can use the built-in version, but the git version has a few useful patches, so I advise using the latter.

-

Install it however you usually install packages in Emacs; I use use-package + straight:

-
(use-package emms
-  :straight t)
-

Setup & MPD integration

-

Now we have to configure EMMS. The following expressions have to be executed after EMMS is loaded, which means we can add them to the :config section of the use-package expression above.

-

First, EMMS exposes a handy function that loads all the stable EMMS features. You can take a look at its source and pick the features you need or load everything like this:

-
(require 'emms-setup)
-(emms-all)
-

Then we need to set up a directory for EMMS files and the required parameters for emms-player-mpd. Note that emms-player-mpd-music-directory should be set to the same value as music_directory in mpd.conf.

-
(setq emms-source-file-default-directory (expand-file-name "~/Music/"))
-
-(setq emms-player-mpd-server-name "localhost")
-(setq emms-player-mpd-server-port "6600")
-(setq emms-player-mpd-music-directory "~/Music")
-

Add the required functions to EMMS lists:

-
(add-to-list 'emms-info-functions 'emms-info-mpd)
-(add-to-list 'emms-player-list 'emms-player-mpd)
-

Now we can connect EMMS to MPD. For some reason, executing this function stops the MPD playback, but it is not a big issue because it has to be executed only once.

-
(emms-player-mpd-connect)
-

The last thing we may want is to link EMMS playlist clearing to MPD playlist clearing. I’m not sure how this interacts with MPD’s own playlists because I don’t use them, so you may need to watch out here if you do.

-
(add-hook 'emms-playlist-cleared-hook 'emms-player-mpd-clear)
-

Usage

-

One rough edge of EMMS & MPD integration is that EMMS and MPD have separate libraries and playlists.

-

So, first we have to populate the MPD library with M-x emms-player-mpd-update-all. This operation is executed asynchronously by MPD and may take a few minutes for the first run. The subsequent runs are much faster. You can do the same by invoking mpc update from the command line.

-

Second, we have to populate the EMMS library (cache) from the MPD library. To do that, run M-x emms-cache-set-all-from-mpd. If something went wrong with the EMMS cache, you always can clean it with M-x emms-cache-reset.

-

After this is done, we can finally play music! To do that, run M-x emms-browser. The left window should have the EMMS browser buffer with the loaded library, the right one should contain (as for now empty) playlist.

-

In the browser we can use the following commands to add elements to the playlist:

- -

Now, we have a few tracks in the EMMS playlist, but they are not in the MPD playlist yet.

-

In the EMMS playlist buffer, M-x emms-playlist-mode-play-smart (RET) will sync the playlists and start playing the song under the cursor. Also, use

- -

Take a look at the EMMS manual for more information, including sections about playlist and browser.

-

Fetching lyrics

-

One feature of ncmpcpp I was missing here is fetching lyrics, so I’ve written a small package to do just that.

-

Debugging the package turned out to be quite funny because apparently, there is no way around parsing HTML with this task. So I’ve chosen genius.com as the source, but the site turned out to provide different versions of itself (with different DOMs!) to different users.

-

At any rate, I’ve processed the cases I found, and it seems to be working, at least for me. To use the package, get the API key from Genius and install it:

-
(use-package lyrics-fetcher
-  :straight t
-  :after (emms)
-  :config
-  (setq lyrics-fetcher-genius-access-token
-        (password-store-get "My_Online/APIs/genius.com")))
-

To fetch lyrics for the current playing EMMS song, run M-x lyrics-fetcher-show-lyrics. Or run M-x lyrics-fetcher-emms-browser-show-at-point to fetch data for the current point in the EMMS browser. See the package homepage for more information.

-

Album covers

-

I’ve mentioned above that EMMS supports displaying album covers.

-

For this to work, it is necessary to have one album per one folder. By default the cover image should be saved to images named cover_small (100x100 recommended), cover_medium (200x200 recommended) and cover_large. The small version is to be displayed in the EMMS browser, the medium one in the playlist.

-

It’s not required for images to be exactly of these sizes, but they definitely should be of one size across different albums to look nice in the interface.

-

You can resize images with ImageMagick with commands like this:

-
convert cover.jpg -resize 100x100^ -gravity Center -extent 100x100 cover_small.jpg
-convert cover.jpg -resize 200x200^ -gravity Center -extent 200x200 cover_medium.jpg
-

lyrics-fetcher can (try to) do this automatically by downloading the cover from genius.com with M-x lyrics-fetcher-emms-browser-fetch-covers-at-point in EMMS browser.

-

MPV and YouTube

-

MPV is an extensible media player, which integrates with youtube-dl and is controllable by EMMS, thus quite fitting for this setup.

-

MPV and youtube-dl

-

First, install both mpv and youtube-dl from your distribution’s package repository.

-

Then we can add another player to the list:

-
(add-to-list 'emms-player-list 'emms-player-mpv t)
-

EMMS determines which player to use by a regexp. emms-player-mpd sets the default regexp from MPD’s diagnostic output so that regex opens basically everything, including videos, HTTPS links, etc. That is fine if MPD is the only player in EMMS, but as we want to use MPV as well, we need to override the regexes.

-

MPD regexp can look like this:

-
(emms-player-set emms-player-mpd
-                 'regex
-                 (emms-player-simple-regexp
-                  "m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff"))
-

And a regexp for MPV to open videos and youtube URLs:

-
(emms-player-set emms-player-mpv
-                 'regex
-                 (rx (or (: "https://" (* nonl) "youtube.com" (* nonl))
-                         (+ (? (or "https://" "http://"))
-                            (* nonl)
-                            (regexp (eval (emms-player-simple-regexp
-                                           "mp4" "mov" "wmv" "webm" "flv" "avi" "mkv")))))))
-

Then, by default youtube-dl plays the video in the best possible quality, which may be pretty high. To have some control over it, we can modify the --ytdl-format key in the emms-player-mpv-parameters variable. I’ve come up with the following solution:

-
(setq my/youtube-dl-quality-list
-      '("bestvideo[height<=720]+bestaudio/best[height<=720]"
-        "bestvideo[height<=480]+bestaudio/best[height<=480]"
-        "bestvideo[height<=1080]+bestaudio/best[height<=1080]"))
-
-(setq my/default-emms-player-mpv-parameters
-      '("--quiet" "--really-quiet" "--no-audio-display"))
-
-(defun my/set-emms-mpd-youtube-quality (quality)
-  (interactive "P")
-  (unless quality
-    (setq quality (completing-read "Quality: " my/youtube-dl-quality-list nil t)))
-  (setq emms-player-mpv-parameters
-        `(,@my/default-emms-player-mpv-parameters ,(format "--ytdl-format=%s" quality))))
-
-(my/set-emms-mpd-youtube-quality (car my/youtube-dl-quality-list))
-

Run M-x my/set-emms-mpd-youtube-quality to pick the required quality. Take a look at youtube-dl docs for more information about the format selection.

-

Now M-x emms-add-url should work on YouTube URLs just fine. Just keep in mind that it will only add the URL to the playlist, not play it right away.

-

Cleanup EMMS cache

-

All the added URLs stay in the EMMS cache after being played. We probably don’t want them to remain there, so here is a function to remove URLs from the EMMS cache.

-
(defun my/emms-cleanup-urls ()
-  (interactive)
-  (let ((keys-to-delete '()))
-    (maphash (lambda (key value)
-               (when (eq (cdr (assoc 'type value)) 'url)
-                 (add-to-list 'keys-to-delete key)))
-             emms-cache-db)
-    (dolist (key keys-to-delete)
-      (remhash key emms-cache-db)))
-  (setq emms-cache-dirty t))
-

YouTube RSS

-

Where to get URLs?

-

So, we are able to watch YouTube videos by URLs, but where to get URLs from? A natural solution is to use elfeed and RSS feeds.

-

I’ve tried a bunch of options to get feeds for YouTube channels. The first one is Invidious, a FOSS YouTube frontend. The problem here is that various instances I tried weren’t particularly stable (at least when I was using them) and hosting the thing by myself would be overkill. And switching instances is causing duplicate entries in the Elfeed DB.

-

The second option is to use YouTube’s own RSS. The feed URL looks like https://www.youtube.com/feeds/videos.xml?channel_id=<CHANNEL_ID>=. Here are a couple of options of figuring out CHANNEL_ID in case it’s not easily available. The problem with YouTube RSS is that it uses fields that are not supported by elfeed, so the feed entry lacks a preview and description.

-

As my workaround, I’ve written a small web-server which converts an RSS feed from YouTube to an elfeed-compatible Atom feed. It doesn’t do much, so you can just download the thing and launch it:

-
git clone https://github.com/SqrtMinusOne/yt-rss.git
-cd ./yt-rss
-pip install -r requirements.txt
-gunicorn main:app
-

A feed for a particular channel will be available at

-
http://localhost:8000/<channel_id>?token=<token>
-

where <token> is set in .env file to the default value of 12345.

-

Elfeed

-

Elfeed is an Emacs Atom & RSS reader. It’s a pretty popular package with lots of information written over the years, so I’ll cover just my particular setup.

-

My elfeed config, sans keybindings, looks like this:

-
(use-package elfeed
-  :straight t
-  :commands (elfeed)
-  :config
-  (setq elfeed-db-directory "~/.elfeed")
-  (setq elfeed-enclosure-default-dir (expand-file-name "~/Downloads"))
-  (advice-add #'elfeed-insert-html
-              :around
-              (lambda (fun &rest r)
-                (let ((shr-use-fonts nil))
-                  (apply fun r)))))
-

The advice there forces elfeed to use monospace fonts in the show buffer.

-

I also use elfeed-org, which gives an option to store the feed config in an .org file instead of a variable:

-
(use-package elfeed-org
-  :straight t
-  :after (elfeed)
-  :config
-  (setq rmh-elfeed-org-files '("~/.emacs.d/elfeed.org"))
-  (elfeed-org))
-

So, however you’ve got URLs for YouTube channels, put them into elfeed.

-

To fetch the feeds, open elfeed with M-x elfeed and run M-x elfeed-search-fetch in the search buffer. And as usual, take a look at the package documentation for more information.

-

To help with navigating through the long list of entries, I’ve made the following function to narrow the search buffer to the feed of the entry under cursor:

-
(defun my/elfeed-search-filter-source (entry)
-  "Filter elfeed search buffer by the feed under cursor."
-  (interactive (list (elfeed-search-selected :ignore-region)))
-  (when (elfeed-entry-p entry)
-    (elfeed-search-set-filter
-     (concat
-      "@6-months-ago "
-      "+unread "
-      "="
-      (replace-regexp-in-string
-       (rx "?" (* not-newline) eos)
-       ""
-       (elfeed-feed-url (elfeed-entry-feed entry)))))))
-

So I mostly alternate between M-x my/elfeed-search-filter-source and M-x elfeed-search-clear-filter. I tag the entries which I want to watch later with +later, and add the ones I want to watch right now to the playlist.

-

Integrating with EMMS

-

Finally, here’s the solution I came up with to add an entry from elfeed to the EMMS playlist. First, we’ve got to get a URL:

-
(defun my/get-youtube-url (link)
-  (let ((watch-id (cadr
-                   (assoc "watch?v"
-                          (url-parse-query-string
-                           (substring
-                            (url-filename
-                             (url-generic-parse-url link))
-                            1))))))
-    (concat "https://www.youtube.com/watch?v=" watch-id)))
-

This function is intended to work with both Invidious and YouTube RSS feeds. Of course, it will require some adaptation if you want to watch channels from something like PeerTube or Odysee.

-

The easiest way to put the URL to the playlist is to define a new source for EMMS:

-
(define-emms-source elfeed (entry)
-    (let ((track (emms-track
-                  'url (my/get-youtube-url (elfeed-entry-link entry)))))
-      (emms-track-set track 'info-title (elfeed-entry-title entry))
-      (emms-playlist-insert-track track)))
-

Because define-emms-source is an EMMS macro, the code block above has to be evaluated with EMMS loaded. E.g. you can wrap it into (with-eval-after-load 'emms ...) or put in the :config section.

-

The macro defines a bunch of functions to work with the source, which we can use in another function:

-
(defun my/elfeed-add-emms-youtube ()
-  (interactive)
-  (emms-add-elfeed elfeed-show-entry)
-  (elfeed-tag elfeed-show-entry 'watched)
-  (elfeed-show-refresh))
-

Now, calling M-x my/elfeed-add-emms-youtube in the *elfeed-show* buffer will add the correct URL to the playlist and tag the entry with +watched. I’ve bound the function to gm.

- -
- -
- - diff --git a/public/posts/2021-10-04-emacs-i3/index.html b/public/posts/2021-10-04-emacs-i3/index.html deleted file mode 100644 index d334b21..0000000 --- a/public/posts/2021-10-04-emacs-i3/index.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - Getting a consistent set of keybindings between i3 and Emacs - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Intro

-

One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3.

-

But why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn’t feel snappy enough. Also, I really like i3’s tree-based layout structure; I feel like it fits my workflow much better than anything else I tried, including the master/stack paradigm of XMonad​, for instance.

-

One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs that are configured in an actual programing language, like the mentioned XMonad, Qtile, Awesome, etc. But I think i3’s extensibility is underappreciated, although the contents of this article may lie closer to the limits of how far one can go there.

-

Here is a small demo of how it currently works:

- -

Emacs integration

-

What I’m trying to do is actually quite simple, so I’m somewhat surprised I didn’t find anything similar on the Internet. But I didn’t look too hard.

-

The basic idea is to launch a normal i3 command with i3-msg in case the current window is not Emacs, otherwise pass that command to Emacs with emacsclient. In Emacs, execute the command if possible, otherwise pass the command back to i3.

-

This may seem like a lot of overhead, but I didn’t feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard, the interaction feels seamless. The only concern is that this command flow is vulnerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.

-

One interesting observation here is that Emacs windows and X windows are sort of one-level entities, so I can talk just about “windows”.

-

At any rate, we need a script to do the i3 -> Emacs part:

-
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
-    command="(my/emacs-i3-integration \"$@\")"
-    emacsclient -e "$command"
-else
-    i3-msg $@
-fi
-

My Emacs frame title is set to emacs[:<projectile-project-name>]@<hostname>, hence the regex. The script is saved to an executable called emacs-i3-integration.

-

For this to work, we need to make sure that Emacs starts a server, so here is an expression to do just that:

-
(add-hook 'after-init-hook #'server-start)
-

The function my/emacs-i3-integration, which is an entrypoint for the i3 integration, will be defined a bit later.

-

And here is a simple macro to do the Emacs -> i3 part:

-
(defmacro i3-msg (&rest args)
-  `(start-process "emacs-i3-windmove" nil "i3-msg" ,@args))
-

Handling i3 commands

-

Now we have to handle the required set of i3 commands. It is worth noting here that I’m not trying to implement a general mechanism to apply i3 commands to Emacs, rather I’m implementing a small subset that I use in my i3 configuration and that maps reasonably to the Emacs concepts.

-

Also, I use evil-mode and generally configure the software to have vim-style bindings where possible. So if you don’t use evil-mode you’d have to detangle the given functions from evil, but then, I guess, you do not use super+hjkl to manage windows either.

-

focus

-

First, for the focus command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and windmove have the same names for directions, so the function is rather straightforward.

-

One caveat here is that the minibuffer is always the bottom-most Emacs window, so it is necessary to check for that as well.

-
(defun my/emacs-i3-windmove (dir)
-  (let ((other-window (windmove-find-other-window dir)))
-    (if (or (null other-window) (window-minibuffer-p other-window))
-        (i3-msg "focus" (symbol-name dir))
-      (windmove-do-window-select dir))))
-

The relevant section of the i3 config looks like this:

-
bindsym $mod+h exec emacs-i3-integration focus left
-bindsym $mod+j exec emacs-i3-integration focus down
-bindsym $mod+k exec emacs-i3-integration focus up
-bindsym $mod+l exec emacs-i3-integration focus right
-
-bindsym $mod+Left exec emacs-i3-integration focus left
-bindsym $mod+Down exec emacs-i3-integration focus down
-bindsym $mod+Up exec emacs-i3-integration focus up
-bindsym $mod+Right exec emacs-i3-integration focus right
-

The Emacs function has to be called like that:

-
(my/emacs-i3-windmove 'right)
-

move

-

For the move command I want the following behavior:

- -

For the first part, window-swap-states with windmove-find-other-window do well enough.

-

evil-move-window works well for the second part. By itself it doesn’t behave quite like i3, for instance, (evil-move-window 'right) in a three-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described here.

-

So here is a simple predicate which checks whether there is space in the given direction.

-
(defun my/emacs-i3-direction-exists-p (dir)
-  (some (lambda (dir)
-          (let ((win (windmove-find-other-window dir)))
-            (and win (not (window-minibuffer-p win)))))
-        (pcase dir
-          ('width '(left right))
-          ('height '(up down)))))
-

And the implementation of the move command.

-
(defun my/emacs-i3-move-window (dir)
-  (let ((other-window (windmove-find-other-window dir))
-        (other-direction (my/emacs-i3-direction-exists-p
-                          (pcase dir
-                            ('up 'width)
-                            ('down 'width)
-                            ('left 'height)
-                            ('right 'height)))))
-    (cond
-     ((and other-window (not (window-minibuffer-p other-window)))
-      (window-swap-states (selected-window) other-window))
-     (other-direction
-      (evil-move-window dir))
-     (t (i3-msg "move" (symbol-name dir))))))
-

The relevant section of the i3 config:

-
bindsym $mod+Shift+h exec emacs-i3-integration move left
-bindsym $mod+Shift+j exec emacs-i3-integration move down
-bindsym $mod+Shift+k exec emacs-i3-integration move up
-bindsym $mod+Shift+l exec emacs-i3-integration move right
-
-bindsym $mod+Shift+Left exec emacs-i3-integration move left
-bindsym $mod+Shift+Down exec emacs-i3-integration move down
-bindsym $mod+Shift+Up exec emacs-i3-integration move up
-bindsym $mod+Shift+Right exec emacs-i3-integration move right
-

resize and balance windows

-

Next on the line are resize grow and resize shrink. evil-window- functions do nicely for this task.

-

This function also checks whether there is space to resize in the given direction with the help of the predicate defined above. The command is forwarded back to i3 if there is not.

-
(defun my/emacs-i3-resize-window (dir kind value)
-  (if (or (one-window-p)
-          (not (my/emacs-i3-direction-exists-p dir)))
-      (i3-msg "resize" (symbol-name kind) (symbol-name dir)
-              (format "%s px or %s ppt" value value))
-    (setq value (/ value 2))
-    (pcase kind
-      ('shrink
-       (pcase dir
-         ('width
-          (evil-window-decrease-width value))
-         ('height
-          (evil-window-decrease-height value))))
-      ('grow
-       (pcase dir
-         ('width
-          (evil-window-increase-width value))
-         ('height
-          (evil-window-increase-height value)))))))
-

Here I’m following the default configuration of i3, which creates a “submode” to resize windows.

-
mode "resize" {
-
-    bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt
-    bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt
-    bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt
-    bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt
-
-    bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt
-    bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt
-    bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt
-    bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt
-
-    # same bindings, but for the arrow keys
-    bindsym Left  exec emacs-i3-integration resize shrink width 10 px or 10 ppt
-    bindsym Down  exec emacs-i3-integration resize grow height 10 px or 10 ppt
-    bindsym Up    exec emacs-i3-integration resize shrink height 10 px or 10 ppt
-    bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt
-
-    bindsym Shift+Left  exec emacs-i3-integration resize shrink width 100 px or 100 ppt
-    bindsym Shift+Down  exec emacs-i3-integration resize grow height 100 px or 100 ppt
-    bindsym Shift+Up    exec emacs-i3-integration resize shrink height 100 px or 100 ppt
-    bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt
-
-    bindsym equal exec i3-emacs-balance-windows
-
-    # back to normal: Enter or Escape
-    bindsym Return mode "default"
-    bindsym Escape mode "default"
-}
-

Next, Emacs has a built-in function called balance-windows, but i3 doesn’t. Fortunately, there is a Python package called i3-balance-workspace, which performs a similar operation with i3’s IPC. If you use Guix as I do, I’ve written a package definition.

-

So here is a small wrapper which calls i3_balance_workspace and M-x balance-windows if the current window is Emacs.

-
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
-    emacsclient -e "(balance-windows)" &
-fi
-i3_balance_workspace
-

layout toggle split

-

transpose-frame is a package to “transpose” the current Emacs windows layout, which behaves somewhat similar to the layout toggle split command in i3, so I’ll use it as well.

-
(use-package transpose-frame
-  :straight t
-  :commands (transpose-frame))
-

The i3 config for this command:

-
bindsym $mod+e exec emacs-i3-integration layout toggle split
-

The entrypoint

-

Finally, the entrypoint for the Emacs integration. In addition to the commands defined above, it processes split and kill commands and passes every other command back to i3.

-
(defun my/emacs-i3-integration (command)
-  (pcase command
-    ((rx bos "focus")
-     (my/emacs-i3-windmove
-      (intern (elt (split-string command) 1))))
-    ((rx bos "move")
-     (my/emacs-i3-move-window
-      (intern (elt (split-string command) 1))))
-    ((rx bos "resize")
-     (my/emacs-i3-resize-window
-       (intern (elt (split-string command) 2))
-       (intern (elt (split-string command) 1))
-       (string-to-number (elt (split-string command) 3))))
-    ("layout toggle split" (transpose-frame))
-    ("split h" (evil-window-split))
-    ("split v" (evil-window-vsplit))
-    ("kill" (evil-quit))
-    (- (i3-msg command))))
-

The rest of the relevant i3 config to do the splits:

-
bindsym $mod+s exec emacs-i3-integration split h
-bindsym $mod+v exec emacs-i3-integration split v
-

And to kill the window:

-
bindsym $mod+Shift+q exec emacs-i3-integration kill
-

Switching i3 tabs

-

As I use i3’s tabbed layout quite extensively, occasionally I want to switch out of the Emacs tab with one button, and that’s where my integration may interfere.

-

As a workaround, I found a small Rust program called i3-switch-tabs, which also communicates with i3 via its IPC to switch the top-level tab. I’ve written a Guix package definition for that as well.

-
bindsym $mod+period exec i3-switch-tabs right
-bindsym $mod+comma exec i3-switch-tabs left
-

Conclusion

-

So, how does all of that feel? Actually, I got used to that setup pretty quickly. Using <s-Q> to quit windows and the <s-r> submode to resize them is particularly nice. I’ve seen people making hydras in Emacs to do the latter.

-

All of that would probably be easier to do in a WM which is configured in a programming language rather than in a self-cooked DSL, so I may try to replicate that somewhere else in an unknown time in the future. Meanwhile, it’s pretty good.

- -
- -
- - diff --git a/public/posts/hello-world/index.html b/public/posts/hello-world/index.html deleted file mode 100644 index 7d939ed..0000000 --- a/public/posts/hello-world/index.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - Hello, world! - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Hello, world!

-

Eventually, there will be something interesting here. Or not.

-

Regradless, I’ll check if I can write some Python here

-
print("Hello, world")
-
Hello, world
-
-
- -
- - diff --git a/public/posts/index.html b/public/posts/index.html deleted file mode 100644 index 143fe11..0000000 --- a/public/posts/index.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - Posts - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Posts

- -
- -
- - diff --git a/public/posts/index.xml b/public/posts/index.xml deleted file mode 100644 index 4bc929c..0000000 --- a/public/posts/index.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - Posts on SqrtMinusOne - https://sqrtminusone.xyz/posts/ - Recent content in Posts on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 06 Oct 2021 00:00:00 +0000 - - Getting a consistent set of keybindings between i3 and Emacs - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Intro One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3. -But why not just use EXWM? - - - - My EMMS and elfeed setup - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Intro This is the current state of my quest to live in Emacs, at least in part of reading RSS and music. -Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs. -The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i. - - - - Replacing Jupyter Notebook with Org Mode - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Sat, 01 May 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Why? Jupyter Notebook and its successor Jupyter Lab providing an interactive development environment for many programming languages are in lots of ways great pieces of software. -But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. &ldquo;As one can get&rdquo; is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem. -A possibility for change appeared with my discovery of Emacs not so long ago. - - - - Multiple Gmail accounts & labels with Emacs - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Sat, 27 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Intro For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox. -Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it&rsquo;s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications. - - - - Hello, world! - https://sqrtminusone.xyz/posts/hello-world/ - Mon, 01 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/hello-world/ - Hello, world! Eventually, there will be something interesting here. Or not. -Regradless, I&rsquo;ll check if I can write some Python here -print(&#34;Hello, world&#34;) Hello, world - - - - diff --git a/public/sass/researcher.css b/public/sass/researcher.css deleted file mode 100644 index 73447d6..0000000 --- a/public/sass/researcher.css +++ /dev/null @@ -1,114 +0,0 @@ -#content a, .nav-link { - color: #dc3545; - text-decoration: none; } - #content a *, .nav-link * { - color: #dc3545; } - #content a:hover, .nav-link:hover { - color: #dc3545; - text-decoration: underline; } - -#footer a, .navbar-brand { - color: #222222; - text-decoration: none; } - #footer a *, .navbar-brand * { - color: #222222; } - #footer a:hover, .navbar-brand:hover { - color: #222222; - text-decoration: underline; } - -#content table td, #content table th { - border: 1px solid #cccccc; - padding: 6px 12px; - text-align: left; } - -* { - color: #222222; - font-family: Inconsolata; - line-height: 1.2; } - -.container { - max-width: 750px; } - -.navbar-brand { - font-size: 2rem; } - -#content p { - margin-bottom: 0.6rem; } - -#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 { - font-size: medium; - font-weight: bold; - margin: 1rem 0 0.6rem 0; } - -#content h1 { - font-size: 1.8rem; } - -#content h2 { - font-size: 1.6rem; } - -#content h3 { - font-size: 1.4rem; } - -#content h4 { - font-size: 1.2rem; } - -#content img { - display: block; - margin: 1rem auto; - max-width: 100%; } - -#content .avatar > img { - border-radius: 50%; - float: right; - margin: -8px 0 0 16px; - height: 90px; - width: 90px; } - -#content ol { - counter-reset: list; - list-style: none; - padding-left: 2rem; } - #content ol > li:before { - content: "[" counter(list, decimal) "] "; - counter-increment: list; } - -#content .container > ol, #content .footnotes > ol { - padding-left: 0; } - -#content ul { - list-style: inside; - padding-left: 2rem; } - -#content .container > ul, #content .footnotes > ul { - padding-left: 0; } - -#content table { - margin: 1rem auto; - width: 100%; } - #content table th { - font-weight: bold; } - #content table tr:nth-child(2n) { - background-color: #f8f8f8; } - -#content blockquote { - border-left: 4px solid; - font-style: italic; - margin: 1rem 0; - padding: 8px 8px; } - -#content code { - color: #222222; - background-color: #f8f8f8; - border: 1px solid #cccccc; - border-radius: 10%; - padding: 0px 4px; - font-family: Inconsolata !important; } - -#content pre code { - all: unset; - font-size: 110%; } - -#content .highlight { - margin: 1rem auto; } - #content .highlight > pre { - padding: 8px 8px; } diff --git a/public/sass/researcher.min.css b/public/sass/researcher.min.css deleted file mode 100644 index 902a44b..0000000 --- a/public/sass/researcher.min.css +++ /dev/null @@ -1 +0,0 @@ -#content a,.nav-link{color:#dc3545;text-decoration:none}#content a *,.nav-link *{color:#dc3545}#content a:hover,.nav-link:hover{color:#dc3545;text-decoration:underline}#footer a,.navbar-brand{color:#222;text-decoration:none}#footer a *,.navbar-brand *{color:#222}#footer a:hover,.navbar-brand:hover{color:#222;text-decoration:underline}#content table td,#content table th{border:1px solid #ccc;padding:6px 12px;text-align:left}*{color:#222;font-family:Inconsolata;line-height:1.2}.container{max-width:750px}.navbar-brand{font-size:2rem}#content p{margin-bottom:.6rem}#content h1,#content h2,#content h3,#content h4,#content h5,#content h6{font-size:medium;font-weight:700;margin:1rem 0 .6rem}#content h1{font-size:1.8rem}#content h2{font-size:1.6rem}#content h3{font-size:1.4rem}#content h4{font-size:1.2rem}#content img{display:block;margin:1rem auto;max-width:100%}#content .avatar>img{border-radius:50%;float:right;margin:-8px 0 0 16px;height:90px;width:90px}#content ol{counter-reset:list;list-style:none;padding-left:2rem}#content ol>li{display:table-row}#content ol>li:before{content:"[" counter(list,decimal)"] ";counter-increment:list;display:table-cell;text-align:right;padding-right:.5em}#content .container>ol,#content .footnotes>ol{padding-left:0}#content ul{list-style:inside;padding-left:2rem}#content ul>li{list-style-position:outside;margin-left:1em}#content .container>ul,#content .footnotes>ul{padding-left:0}#content table{margin:1rem auto;width:100%}#content table th{font-weight:700}#content table tr:nth-child(2n){background-color:#f8f8f8}#content blockquote{border-left:4px solid;font-style:italic;margin:1rem 0;padding:8px}#content code{color:#222;background-color:#f8f8f8;border:1px solid #ccc;border-radius:10%;padding:0 4px;font-family:inconsolata!important}#content pre code{all:unset;font-size:110%}#content .highlight{margin:1rem auto}#content .highlight>pre{padding:8px} \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml deleted file mode 100644 index 4610a72..0000000 --- a/public/sitemap.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - https://sqrtminusone.xyz/tags/emacs/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/i3wm/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/ - 2021-10-06T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/elfeed/ - 2021-09-08T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/emms/ - 2021-09-08T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - 2021-09-08T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/org/ - 2021-05-01T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - 2021-05-01T00:00:00+00:00 - - https://sqrtminusone.xyz/tags/mail/ - 2021-02-27T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - 2021-02-27T00:00:00+00:00 - - https://sqrtminusone.xyz/posts/hello-world/ - 2021-02-01T00:00:00+00:00 - - https://sqrtminusone.xyz/categories/ - - https://sqrtminusone.xyz/configs/ - - https://sqrtminusone.xyz/configs/console/ - - https://sqrtminusone.xyz/configs/desktop/ - - https://sqrtminusone.xyz/configs/emacs/ - - https://sqrtminusone.xyz/configs/guix/ - - https://sqrtminusone.xyz/configs/mail/ - - https://sqrtminusone.xyz/configs/readme/ - - diff --git a/public/stats/all.png b/public/stats/all.png deleted file mode 100644 index 54b179c..0000000 Binary files a/public/stats/all.png and /dev/null differ diff --git a/public/stats/emacs-vim.png b/public/stats/emacs-vim.png deleted file mode 100644 index e95461d..0000000 Binary files a/public/stats/emacs-vim.png and /dev/null differ diff --git a/public/stats/literate-config.png b/public/stats/literate-config.png deleted file mode 100644 index b7e5e5b..0000000 Binary files a/public/stats/literate-config.png and /dev/null differ diff --git a/public/tags/elfeed/index.html b/public/tags/elfeed/index.html deleted file mode 100644 index f2c6907..0000000 --- a/public/tags/elfeed/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - elfeed - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

elfeed

- -
- -
- - diff --git a/public/tags/elfeed/index.xml b/public/tags/elfeed/index.xml deleted file mode 100644 index 82f40a2..0000000 --- a/public/tags/elfeed/index.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - elfeed on SqrtMinusOne - https://sqrtminusone.xyz/tags/elfeed/ - Recent content in elfeed on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 08 Sep 2021 00:00:00 +0000 - - My EMMS and elfeed setup - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Intro This is the current state of my quest to live in Emacs, at least in part of reading RSS and music. -Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs. -The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i. - - - - diff --git a/public/tags/emacs/index.html b/public/tags/emacs/index.html deleted file mode 100644 index e6554c6..0000000 --- a/public/tags/emacs/index.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - emacs - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

emacs

- -
- -
- - diff --git a/public/tags/emacs/index.xml b/public/tags/emacs/index.xml deleted file mode 100644 index d454cef..0000000 --- a/public/tags/emacs/index.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - emacs on SqrtMinusOne - https://sqrtminusone.xyz/tags/emacs/ - Recent content in emacs on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 06 Oct 2021 00:00:00 +0000 - - Getting a consistent set of keybindings between i3 and Emacs - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Intro One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3. -But why not just use EXWM? - - - - My EMMS and elfeed setup - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Intro This is the current state of my quest to live in Emacs, at least in part of reading RSS and music. -Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs. -The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i. - - - - Replacing Jupyter Notebook with Org Mode - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Sat, 01 May 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Why? Jupyter Notebook and its successor Jupyter Lab providing an interactive development environment for many programming languages are in lots of ways great pieces of software. -But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. &ldquo;As one can get&rdquo; is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem. -A possibility for change appeared with my discovery of Emacs not so long ago. - - - - Multiple Gmail accounts & labels with Emacs - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Sat, 27 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Intro For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox. -Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it&rsquo;s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications. - - - - diff --git a/public/tags/emms/index.html b/public/tags/emms/index.html deleted file mode 100644 index 4b28675..0000000 --- a/public/tags/emms/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - emms - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

emms

- -
- -
- - diff --git a/public/tags/emms/index.xml b/public/tags/emms/index.xml deleted file mode 100644 index ac6e89c..0000000 --- a/public/tags/emms/index.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - emms on SqrtMinusOne - https://sqrtminusone.xyz/tags/emms/ - Recent content in emms on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 08 Sep 2021 00:00:00 +0000 - - My EMMS and elfeed setup - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-09-07-emms/ - Intro This is the current state of my quest to live in Emacs, at least in part of reading RSS and music. -Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs. -The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i. - - - - diff --git a/public/tags/i3wm/index.html b/public/tags/i3wm/index.html deleted file mode 100644 index 44cbf9e..0000000 --- a/public/tags/i3wm/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - i3wm - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

i3wm

- -
- -
- - diff --git a/public/tags/i3wm/index.xml b/public/tags/i3wm/index.xml deleted file mode 100644 index 8534299..0000000 --- a/public/tags/i3wm/index.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - i3wm on SqrtMinusOne - https://sqrtminusone.xyz/tags/i3wm/ - Recent content in i3wm on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 06 Oct 2021 00:00:00 +0000 - - Getting a consistent set of keybindings between i3 and Emacs - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/ - Intro One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred i3wm, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3. -But why not just use EXWM? - - - - diff --git a/public/tags/index.html b/public/tags/index.html deleted file mode 100644 index 5ee3eee..0000000 --- a/public/tags/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - Tags - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

Tags

- -
- -
- - diff --git a/public/tags/index.xml b/public/tags/index.xml deleted file mode 100644 index 5016c49..0000000 --- a/public/tags/index.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - Tags on SqrtMinusOne - https://sqrtminusone.xyz/tags/ - Recent content in Tags on SqrtMinusOne - Hugo -- gohugo.io - en-us - Wed, 06 Oct 2021 00:00:00 +0000 - - emacs - https://sqrtminusone.xyz/tags/emacs/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/emacs/ - - - - - i3wm - https://sqrtminusone.xyz/tags/i3wm/ - Wed, 06 Oct 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/i3wm/ - - - - - elfeed - https://sqrtminusone.xyz/tags/elfeed/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/elfeed/ - - - - - emms - https://sqrtminusone.xyz/tags/emms/ - Wed, 08 Sep 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/emms/ - - - - - org - https://sqrtminusone.xyz/tags/org/ - Sat, 01 May 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/org/ - - - - - mail - https://sqrtminusone.xyz/tags/mail/ - Sat, 27 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/tags/mail/ - - - - - diff --git a/public/tags/mail/index.html b/public/tags/mail/index.html deleted file mode 100644 index e257580..0000000 --- a/public/tags/mail/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - mail - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

mail

- -
- -
- - diff --git a/public/tags/mail/index.xml b/public/tags/mail/index.xml deleted file mode 100644 index c5dea79..0000000 --- a/public/tags/mail/index.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - mail on SqrtMinusOne - https://sqrtminusone.xyz/tags/mail/ - Recent content in mail on SqrtMinusOne - Hugo -- gohugo.io - en-us - Sat, 27 Feb 2021 00:00:00 +0000 - - Multiple Gmail accounts & labels with Emacs - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Sat, 27 Feb 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-02-27-gmail/ - Intro For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox. -Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it&rsquo;s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications. - - - - diff --git a/public/tags/org/index.html b/public/tags/org/index.html deleted file mode 100644 index d4f6612..0000000 --- a/public/tags/org/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - org - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-

org

- -
- -
- - diff --git a/public/tags/org/index.xml b/public/tags/org/index.xml deleted file mode 100644 index 6227203..0000000 --- a/public/tags/org/index.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - org on SqrtMinusOne - https://sqrtminusone.xyz/tags/org/ - Recent content in org on SqrtMinusOne - Hugo -- gohugo.io - en-us - Sat, 01 May 2021 00:00:00 +0000 - - Replacing Jupyter Notebook with Org Mode - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Sat, 01 May 2021 00:00:00 +0000 - - https://sqrtminusone.xyz/posts/2021-05-01-org-python/ - Why? Jupyter Notebook and its successor Jupyter Lab providing an interactive development environment for many programming languages are in lots of ways great pieces of software. -But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. &ldquo;As one can get&rdquo; is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem. -A possibility for change appeared with my discovery of Emacs not so long ago. - - - - diff --git a/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.content b/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.content deleted file mode 100644 index 73447d6..0000000 --- a/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.content +++ /dev/null @@ -1,114 +0,0 @@ -#content a, .nav-link { - color: #dc3545; - text-decoration: none; } - #content a *, .nav-link * { - color: #dc3545; } - #content a:hover, .nav-link:hover { - color: #dc3545; - text-decoration: underline; } - -#footer a, .navbar-brand { - color: #222222; - text-decoration: none; } - #footer a *, .navbar-brand * { - color: #222222; } - #footer a:hover, .navbar-brand:hover { - color: #222222; - text-decoration: underline; } - -#content table td, #content table th { - border: 1px solid #cccccc; - padding: 6px 12px; - text-align: left; } - -* { - color: #222222; - font-family: Inconsolata; - line-height: 1.2; } - -.container { - max-width: 750px; } - -.navbar-brand { - font-size: 2rem; } - -#content p { - margin-bottom: 0.6rem; } - -#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 { - font-size: medium; - font-weight: bold; - margin: 1rem 0 0.6rem 0; } - -#content h1 { - font-size: 1.8rem; } - -#content h2 { - font-size: 1.6rem; } - -#content h3 { - font-size: 1.4rem; } - -#content h4 { - font-size: 1.2rem; } - -#content img { - display: block; - margin: 1rem auto; - max-width: 100%; } - -#content .avatar > img { - border-radius: 50%; - float: right; - margin: -8px 0 0 16px; - height: 90px; - width: 90px; } - -#content ol { - counter-reset: list; - list-style: none; - padding-left: 2rem; } - #content ol > li:before { - content: "[" counter(list, decimal) "] "; - counter-increment: list; } - -#content .container > ol, #content .footnotes > ol { - padding-left: 0; } - -#content ul { - list-style: inside; - padding-left: 2rem; } - -#content .container > ul, #content .footnotes > ul { - padding-left: 0; } - -#content table { - margin: 1rem auto; - width: 100%; } - #content table th { - font-weight: bold; } - #content table tr:nth-child(2n) { - background-color: #f8f8f8; } - -#content blockquote { - border-left: 4px solid; - font-style: italic; - margin: 1rem 0; - padding: 8px 8px; } - -#content code { - color: #222222; - background-color: #f8f8f8; - border: 1px solid #cccccc; - border-radius: 10%; - padding: 0px 4px; - font-family: Inconsolata !important; } - -#content pre code { - all: unset; - font-size: 110%; } - -#content .highlight { - margin: 1rem auto; } - #content .highlight > pre { - padding: 8px 8px; } diff --git a/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.json b/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.json deleted file mode 100644 index 9b6f2b0..0000000 --- a/resources/_gen/assets/scss/sass/researcher.scss_543cd5f5ba38b19c899204884e9b6a7a.json +++ /dev/null @@ -1 +0,0 @@ -{"Target":"sass/researcher.css","MediaType":"text/css","Data":{}} \ No newline at end of file diff --git a/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.content b/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.content deleted file mode 100644 index 902a44b..0000000 --- a/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.content +++ /dev/null @@ -1 +0,0 @@ -#content a,.nav-link{color:#dc3545;text-decoration:none}#content a *,.nav-link *{color:#dc3545}#content a:hover,.nav-link:hover{color:#dc3545;text-decoration:underline}#footer a,.navbar-brand{color:#222;text-decoration:none}#footer a *,.navbar-brand *{color:#222}#footer a:hover,.navbar-brand:hover{color:#222;text-decoration:underline}#content table td,#content table th{border:1px solid #ccc;padding:6px 12px;text-align:left}*{color:#222;font-family:Inconsolata;line-height:1.2}.container{max-width:750px}.navbar-brand{font-size:2rem}#content p{margin-bottom:.6rem}#content h1,#content h2,#content h3,#content h4,#content h5,#content h6{font-size:medium;font-weight:700;margin:1rem 0 .6rem}#content h1{font-size:1.8rem}#content h2{font-size:1.6rem}#content h3{font-size:1.4rem}#content h4{font-size:1.2rem}#content img{display:block;margin:1rem auto;max-width:100%}#content .avatar>img{border-radius:50%;float:right;margin:-8px 0 0 16px;height:90px;width:90px}#content ol{counter-reset:list;list-style:none;padding-left:2rem}#content ol>li{display:table-row}#content ol>li:before{content:"[" counter(list,decimal)"] ";counter-increment:list;display:table-cell;text-align:right;padding-right:.5em}#content .container>ol,#content .footnotes>ol{padding-left:0}#content ul{list-style:inside;padding-left:2rem}#content ul>li{list-style-position:outside;margin-left:1em}#content .container>ul,#content .footnotes>ul{padding-left:0}#content table{margin:1rem auto;width:100%}#content table th{font-weight:700}#content table tr:nth-child(2n){background-color:#f8f8f8}#content blockquote{border-left:4px solid;font-style:italic;margin:1rem 0;padding:8px}#content code{color:#222;background-color:#f8f8f8;border:1px solid #ccc;border-radius:10%;padding:0 4px;font-family:inconsolata!important}#content pre code{all:unset;font-size:110%}#content .highlight{margin:1rem auto}#content .highlight>pre{padding:8px} \ No newline at end of file diff --git a/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.json b/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.json deleted file mode 100644 index 30a9eb6..0000000 --- a/resources/_gen/assets/scss/sass/researcher.scss_81ad576498767608ce504b3564784665.json +++ /dev/null @@ -1 +0,0 @@ -{"Target":"sass/researcher.min.css","MediaType":"text/css","Data":{}} \ No newline at end of file