Compare commits

..

80 Commits

Author SHA1 Message Date
316eefe916 kitty:get_cwd: Use KITTY_LISTEN_ON for socket path
Also verify that the socket exists before using it.
2025-12-11 15:27:39 +01:00
3a821d832b kitty:get_cwd: Use the match flag for performance
Instead of requesting the full tree as a JSON and extracting the focused
window with `jq`, use `kitten-@-ls`'s `--match` flag. This further
improves the performance.

Also remove the commented code using recursive descent because it is not
up-to-date anymore.
2025-12-11 15:27:38 +01:00
b291163a80 kitty:get_cwd: Make faster by not using kitten
kitten-@-ls(1) is unfortunately a bit slow (100ms) which is noticeable
when launching new windows using `get_cwd`. Make `get_cwd` faster (down
to 25ms) by communicating directly with the socket instead of using
`kitten`.
2025-12-11 15:27:23 +01:00
2e2bad65a2 polybar: Change while read loop to an xargs pipe 2025-12-10 14:27:53 +01:00
c46b1c5ea1 i3: Add bindings to switch forward & backward
After unplugging an external monitor I sometimes still have old
workspaces that I can't access through their index.
2025-12-10 14:20:40 +01:00
5f40b97e9a i3: Workspace 0 should be 10 2025-12-10 14:19:48 +01:00
5599ce14d7 i3: Normal numeric workspaces on multi outputs
Use normal workspace numbers with the output name as workspace name, but
strip the name in polybar.

See https://github.com/polybar/polybar/pull/3230 for strip-wsnames.
2025-12-04 14:31:05 +01:00
4008bb543b vim:looks: Don't hardcode the colorscheme colors 2025-11-25 18:06:24 +01:00
d0abb14567 vim: Highlight trailing spaces in colorscheme red 2025-11-25 18:06:13 +01:00
9e18be760c vim:setts: Use the number column for the signcolumn 2025-11-25 17:27:48 +01:00
04d3d6e87f vim:plugs:nrrwrgn: Add padding to small windows 2025-11-25 17:19:34 +01:00
526a37301a vim:plugs:ctags: Don't index venv and __pycache__ 2025-11-25 17:11:14 +01:00
7dc9efc0e4 vim:gitcommit: Fold unstaged and untracked files 2025-11-25 17:09:24 +01:00
9bc8b4b93f vim:ftplug:man: Only trigger one timer
Stop an already running timer similar to how it is done already when
highlighting the selection.
2025-11-25 16:07:52 +01:00
ead724b75b vim:ftplug:man: Fix WinResized support check
The check if the WinResized event is supported was broken from the
beginning on since I used `has()` instead of `exists()`.

Fixes: 558bb0582e (man.vim: Check if WinResized is supported,
       2025-01-29)
2025-11-25 15:56:21 +01:00
1c5beed613 git🐮 Fix stderr redirection
I also swapped the printf and the redirection since I think I prefer it
this way.
2025-09-21 01:34:48 +02:00
2dd1a80036 git🐮 Fix parameter quoting
The parameter expansion outside of the single quotes (or inside, however
you want to see it) was not quoted.
2025-09-21 01:30:56 +02:00
4f2a74e546 less: Remap ^E to edit current screen in vim 2025-09-19 01:47:39 +02:00
cb1821ba33 less: Remap ^Y to copying the whole file 2025-09-19 01:44:23 +02:00
5128d15251 less: Strip all ANSI sequences, not just SGR
Kitty's shell integration uses OSC sequences to mark the prompt and
output in the scrollback buffer. When opening that in vim through less I
want these to be gone as well.
2025-09-19 01:36:54 +02:00
2b31632914 bin:no-ansi: Add a label to each pattern 2025-09-19 01:14:09 +02:00
27791acf50 bin:no-ansi: Correct allowed byte ranges
Correct the allowed byte ranges of the APC, DCS, OSC, PM & SOS
sequences. All but the SOS sequence allow additionally the bytes in the
range `0x8 - 0xD` and SOS may be followed by any sequence of bytes
except for SOS and ST (i.e. `\eX` & `\e\\`).

Link: https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf#page=27
2025-09-19 00:51:47 +02:00
8aa76b3ee6 bin:no-ansi: Don't just match CSI sequences
Try to match all ANSI escape sequences, not just CSI (i.e. `\e[`) - also
the ones that are followed by additional bytes (e.g. CSI, OSC).
2025-09-19 00:26:36 +02:00
6b866f5474 bin:no-ansi*: Use printf for escape byte
I dislike having the raw escape byte in these scripts. Unfortunately
`sed` does not support interpreting it via an escape sequence, so use
printf for that. To remain POSIX compatible use `\033` and not `\e` or
`\x1b` since I couldn't find these in the printf(1p) manpage.
Unfortunately this brings a second layer of escaping.
2025-09-19 00:13:28 +02:00
4f6929bcce less: Keep position when editing in vim via e/E 2025-09-18 23:19:07 +02:00
2495d835b4 kitty: Slightly decrease click_interval to 0.4
Kitty's source code makes me believe that this is always 0.5s on X11.
This decreases the delay when opening links.

Link: https://github.com/kovidgoyal/kitty/blob/3482d084aef1/glfw/x11_window.c#L2360-L2363
2025-09-18 12:56:10 +02:00
dda2ff640e kitty: Increase font size by one point to 12 2025-09-18 12:26:36 +02:00
87398bf712 vim:keys: Use getreginfo() instead of getregtype()
The result from getreginfo can be passed directly to setreg making this
a bit neater. Taken from the neovim defaults (ironically not from
v_star-default).

Link: https://github.com/neovim/neovim/blob/08847a9ea15a/runtime/lua/vim/_defaults.lua#L73-L79
2025-09-18 01:08:18 +02:00
0a5b6d0767 vim:keys: Only map v_* & v_# if they don't exist
I learned just now that neovim added mappings for these by default in
v0.8.0. But instead of checking for neovim, check for the existence of
mappings in case vim adds these in the future too.
2025-09-18 01:07:34 +02:00
5b8f54b0cb vim:keys: Fix newline and tab search for v_*
Multiline search didn't work previously when searching with v_* and tabs
were ignored when searching with v_<leader>*.
2025-09-17 23:01:11 +02:00
ef5b2911bf vim:coc: Select first item before confirming
I believe I had the problem in the past that the completion wouldn't
automatically select the first entry but simply display the menu. Thus
it would simply insert a new line when pressing return to accept it.

Unfortunately I didn't commit it back then and I can't reproduce it now.
But it doesn't not hurt so I commit it to be safe.
2025-09-17 21:55:00 +02:00
768cb4ed4a vim:keys: Add mapping for git-commit-last-msg 2025-09-17 21:46:43 +02:00
f2ce78b6b3 vim:fzf: Use location list instead of the quickfix
Since the quickfix is a global list I can't keep multiple ripgrep
searches open at once. The location list is window-local thus solving
this issue.
2025-09-17 21:44:46 +02:00
a3d2bf985c i3: Use back_and_forth instead of i3-msg+jq 2025-09-17 17:35:19 +02:00
e8c5ec93f1 i3: Re-add stacking and tabbed layout bindings
I removed these in 69d0290afd (i3: Remove bindings for stacked and
tabbed layout, 2023-04-20) since I never need them and I always forgot
how to switch back when I pressed these accidentally.

Bring these back as comments, since the comment above still mentions
these and since I can imagine a rare situation where I might want one
these and simply want to comment them in.
2025-09-17 17:30:51 +02:00
ae52ba20d2 vim: Disable 'cindent' in non-code filetypes 2025-09-17 16:49:34 +02:00
ea2867fc9f dircolors: Color pdfs like images and videos 2025-09-17 16:44:48 +02:00
7f83427749 hooks:pre-commit: Broken link detection on delete
Until now the hook only checked newly added symlinks. This patch is a
first draft of also checking the worktree and index for any dangling
symlinks after staging a deletion.

The whole thing probably breaks when file-names contain newlines and
maybe also a mix of quotes. I plan on making this more robust in the
future but see no urgency for it since this repository has pretty simple
filenames.
2025-09-15 17:40:35 +02:00
8a4029121e hooks:pre-commit: Explicitly propagate pipe error
The `die` in the pipe does not exit the whole script but only the pipe.
This currently works because the pipe is the last thing being executed.
If something would come after the pipe, the hook would happily continue
executing. Properly propagate the exit to prevent this in the future.
2025-09-15 17:34:01 +02:00
7c184ed11e git🐮 Fix GIT_DIR for submodules via git alias
Git sets some environment variables when executing a shell command.
Specifically it sets GIT_DIR when called inside a submodule. This makes
git inside the subshell target the main worktree instead of the
temporary one.
2025-09-15 14:24:36 +02:00
8e8ef29b37 git🐮 Use %s to print worktree path
Don't pass the worktree path directly into the format string - just to
be safe.
2025-09-15 13:49:34 +02:00
d8859bc709 git🐮 Keep as much relative offset as possible 2025-09-15 13:49:34 +02:00
65d99c40e8 git🐮 Keep relative path offset 2025-09-15 13:49:33 +02:00
d28ef61694 git🐮 Rename temporary worktree directory
Shorten the "worktree" and place the random bytes at the end for better
sorting.
2025-09-15 13:23:01 +02:00
d5a95f9ce5 git🐮 Fix name of submodules
Previously git-checkout-worktree would use the name of the superproject
instead of the submodule for the temporary directory.
2025-09-15 13:19:28 +02:00
723899d70f git: Add empty line before include section
I want the section at the very end and this broke the alphabetic
sorting.
2025-09-12 17:06:01 +02:00
126ccb0c7a git: Fix option capitalization
Semantically this changes nothing, but the name with a capital `S` is
used by git-config(1).
2025-09-12 17:04:49 +02:00
dadf344f2b git: Explicitly set core.whitespace.tabwidth=8 2025-09-12 17:03:53 +02:00
f17cde7943 git: Show all refs as decoration in logs 2025-09-12 16:57:00 +02:00
a89ad407ba git: Make all graph colors bold
This looks a bit better in my opinion.
2025-09-12 16:56:05 +02:00
a26a899213 git: Make all bold colors bright
Make all bold colors (also) bright, since kitty does not render bold
colors in their bright version, but I've gotten used to how this looks.

For the graph I've gotten rid of the boldness and only switched to
bright versions since I think it makes no sense to have different line
weights for the branches (Although I apparently never noticed, so I
can't say that this was misleading in practice).
2025-09-12 16:51:36 +02:00
6db1a710c6 git: Rename zsh-autoload.sh -> external-script.sh
With the last commit 9c1e3f4679 (git:zsh-autoload: Use relative
scripts/ folder, 2025-09-12), `zsh-autoload.sh` could execute any type
of external script. Rename it to a more generic name.
2025-09-12 16:25:39 +02:00
9c1e3f4679 git:zsh-autoload: Use relative scripts/ folder
This makes it a bit more agnostic to the type of scripts being used.
2025-09-12 14:49:59 +02:00
bb7ef3769d git: Add branch-rename
Small script to rename a branch locally and on a given remote.
2025-09-12 14:35:44 +02:00
0534ec493e zsh:alias: Make sizes an anonymous function
This way one can easily redirect stderr for both commands (not just
`sort`) and specify a path. The path defaults to the current directory,
but listing each entry by globbing.
2025-09-07 21:09:11 +02:00
708daa10dc kitty: Disable cursor shell integration
It's super slow and I really don't need it. I'd prefer to have kitty
mess with my shell as little as possible.
2025-09-04 11:14:52 +02:00
6bd13a9b56 kitty: Open url with kitty_mod+f
Similar to zathura's `f` to follow links.
2025-09-04 11:14:45 +02:00
a34cdab694 vim:keys: Revert "Add <leader>g[pP] for system clipboard"
This reverts commit 2bceafeb4e.

`<leader>gp` conflicts with GitGutterPreviewHunk.
2025-09-03 15:01:57 +02:00
456f71d939 vim:keys:TODO: Add <leader>[h 2025-09-03 13:33:09 +02:00
2bceafeb4e vim:keys: Add <leader>g[pP] for system clipboard 2025-09-03 13:18:17 +02:00
af0d9e8fd1 vim:keys: Disable gp mapping for now
It conflicts with the default.
2025-09-03 13:18:17 +02:00
d57fbf0e66 vim:keys: Prepend fzf bindings with f
All fzf related bindings should start with `<leader>f`. This resolves
the clash between `GFiles` and the git actions targeting the full file.
2025-09-03 13:18:16 +02:00
a23159e50d vim:keys: Merge fzf mappings 2025-09-03 13:18:16 +02:00
e8a8f9637a vim:coc: Re-enable backup files
CoC disables backup files due to a problem with a typescript
language-server (it becoming slow when the backup file is written,
triggering a `structureChanged`). But:

1. I want backup files
2. I do not use that language server
3. If I would:
	1. I could disable backup files in a filetype plugin
	2. The linked issue is from 2019 - no idea if this is still
	   present today
4. I don't think this issue is even a thing with a `&backupdir` that
   does not include relative directories (which is the case for me)

So remove the lines disabling backups and go back to vim's defaults:
`nobackup` and `writebackup`.

Link: https://github.com/neoclide/coc.nvim/issues/649
2025-09-03 11:54:50 +02:00
0e5baf5e36 vim:coc: Add coc-go server 2025-09-03 11:36:27 +02:00
fced504674 tmux: Kill dead pane with Escape too 2025-09-03 10:07:53 +02:00
a49cbcae21 git:core.pager: Remove --quit-if-one-screen
The inconsistency annoys me.
2025-09-03 10:02:06 +02:00
9271a293cb git: Enable clone.filterSubmodules
> If a partial clone filter is provided (see --filter in
> git-rev-list(1)) and --recurse-submodules is used, also apply the
> filter to submodules.

I have never used --filter before, but it sounds as if I want this
enabled in the case that I do.
2025-09-03 10:00:48 +02:00
2e4086500d git: Disable rerere
I had this change sitting around so long that I forgot why exactly I
disabled it, but know that some problem arose from rerere. I think that
even though it is super cool, it brings a sneaky new layer of possible
errors. Maybe I'll re-enable it in the future.
2025-09-03 09:53:54 +02:00
ca15f2399e bin:no-ansi: Fix final byte range
Don't know why I put `z`, the range goes until `~` (i.e. 0x7E).
2025-09-01 16:50:03 +02:00
198069fac7 zsh:alias: Add -a to cp
Preserve all attributes (i.e. timestamps, permissions, etc.). This also
implies `--recursive` which I probably could have added already sooner.
2025-08-30 11:48:45 +02:00
4c12418b62 git:track: Fix unnecessary subshell launch
While at it, modify the formatting of the line above slightly.
2025-08-28 12:24:06 +02:00
5767210dce git:track: List remote refs only on request
List only the local heads by default and require the remote refs to be
passed as arguments. For example:

	git track origin

To make this easier, add support of an `--all` flag to list all remote
refs.
2025-08-28 12:24:02 +02:00
ddb279da7b kitty:daemon: Write to log file 2025-08-26 16:54:55 +02:00
07428c661d kitty:daemon: Support optional instance group name 2025-08-26 13:43:03 +02:00
a7e4c2c770 kitty:daemon: Use TMPDIR and support multi-user 2025-08-26 13:41:18 +02:00
c09ed22389 i3: Move kitty daemonization into own script 2025-08-26 13:06:08 +02:00
1f6fb2abf7 zsh:glog: Bind Ctrl-s to toggle-sort
When searching for a specific commit in a long history the chronological
sorting can sometimes be annoying. Map ctrl-s to toggle-sort so that the
best result can be at the top.
2025-08-24 20:57:23 +02:00
b47c91bb5f zsh:glog: Use Ctrl-Alt for preview bindings
This makes it more consistent with the preview scroll bindings.
2025-08-24 20:57:17 +02:00
fec954c30c zsh:glog: Move header into variable 2025-08-24 20:57:10 +02:00
33 changed files with 527 additions and 156 deletions

View File

@@ -185,3 +185,6 @@ EXEC 01;92
*.opus 00;36 *.opus 00;36
*.spx 00;36 *.spx 00;36
*.xspf 00;36 *.xspf 00;36
## Others
*.pdf 01;95

View File

@@ -4,21 +4,22 @@
addIgnoredFile = off addIgnoredFile = off
detachedHead = off detachedHead = off
[alias] [alias]
# NOTE: git-zsh-autoload (./zsh-autoload.sh) is a small wrapper that # NOTE: git-external-script (./external-script.sh) is a small wrapper
# launches autoloadable zsh functions (.config/zsh/autoload/git/*) in # that launches external scripts from the ./scripts/ collection in the
# the right directory, as shell commands in git aliases are executed # right directory, as shell commands in git aliases are executed from
# from the top-level directory of the repository. # the top-level directory of the repository.
abort = "!git-zsh-autoload abort" abort = "!git-external-script abort"
autosquash = -c sequence.editor=/bin/true rebase -i --autosquash autosquash = -c sequence.editor=/bin/true rebase -i --autosquash
autofixup= autosquash autofixup= autosquash
branch-rename = "!git-external-script branch-rename"
c = commit c = commit
changes = flog HEAD...FETCH_HEAD changes = flog HEAD...FETCH_HEAD
checkout-worktree = "!git-zsh-autoload checkout-worktree" checkout-worktree = "!git-external-script checkout-worktree"
cow = checkout-worktree cow = checkout-worktree
co = checkout co = checkout
commit-last-msg = "!git-zsh-autoload commit-last-msg" commit-last-msg = "!git-external-script commit-last-msg"
continue = "!git-zsh-autoload continue" continue = "!git-external-script continue"
cont = continue cont = continue
clm = commit-last-msg clm = commit-last-msg
last-msg = commit-last-msg last-msg = commit-last-msg
@@ -28,27 +29,61 @@
ft = fetch-tags-only ft = fetch-tags-only
filter-repo = !git-filter-repo filter-repo = !git-filter-repo
fixes = log -1 --pretty=fixes fixes = log -1 --pretty=fixes
glog = "!git-zsh-autoload glog" glog = "!git-external-script glog"
https-and-ssh = "!git-zsh-autoload https-and-ssh" https-and-ssh = "!git-external-script https-and-ssh"
ssh-and-https = https-and-ssh ssh-and-https = https-and-ssh
l = log l = log
last-changed = "!git-zsh-autoload last-changed" last-changed = "!git-external-script last-changed"
ls = ls-files ls = ls-files
make-fork = "!git-zsh-autoload make-fork" make-fork = "!git-external-script make-fork"
p = push p = push
perm-stash = "!git-zsh-autoload perm-stash" perm-stash = "!git-external-script perm-stash"
root = rev-parse --show-toplevel root = rev-parse --show-toplevel
signoff = rebase --signoff signoff = rebase --signoff
ss = stash ss = stash
ssync = "!git-zsh-autoload ssync" ssync = "!git-external-script ssync"
submodule-rm = "!git-zsh-autoload submodule-rm" submodule-rm = "!git-external-script submodule-rm"
track = "!git-zsh-autoload track" track = "!git-external-script track"
branches = track branches = track
[blame] [blame]
date = short date = short
[branch] [branch]
autosetuprebase = always autosetuprebase = always
sort = -committerdate sort = -committerdate
[clone]
filterSubmodules = yes
[color "diff"]
# Make all bold colors also bright. See diff_colors in git's diff.c
oldMoved = bold brightmagenta
oldMovedAlternative = bold brightblue
newMoved = bold brightcyan
newMovedAlternative = bold brightyellow
oldBold = bold brightred
newBold = bold brightgreen
[color "decorate"]
# Make all bold colors also bright. See decoration_colors in git's
# log-tree.c
branch = bold brightgreen
remoteBranch = bold brightred
tag = bold brightyellow
stash = bold brightmagenta
HEAD = bold brightcyan
grafted = bold brightblue
[color "grep"]
# Make all bold colors also bright. See GREP_OPT_INIT in git's grep.h
matchContext = bold brightred
matchSelected = bold brightred
[color "interactive"]
# Make all bold colors also bright. See init_add_i_state in git's
# add-interactive.c
help = bold brightred
prompt = bold brightblue
error = bold brightred
[color "remote"]
# Make all bold colors also bright. See keywords in git's sideband.c
warning = bold brightyellow
success = bold brightgreen
error = bold brightred
[color "status"] [color "status"]
added = 076 added = 076
untracked = 014 untracked = 014
@@ -65,9 +100,8 @@
[core] [core]
abbrev = 12 abbrev = 12
#pager = delta #pager = delta
pager = diff-so-fancy \ pager = diff-so-fancy | less --tabs=8 --RAW-CONTROL-CHARS
| less --tabs=8 --RAW-CONTROL-CHARS --quit-if-one-screen whitespace = trailing-spaces,space-before-tab,indent-with-non-tab,tabwidth=8
whitespace = trailing-spaces,space-before-tab,indent-with-non-tab
[delta] [delta]
navigate = true navigate = true
commit-decoration-style = bold yellow box commit-decoration-style = bold yellow box
@@ -100,6 +134,12 @@
singleKey = true singleKey = true
[log] [log]
follow = true follow = true
# Make all colors bold and additionally use bright versions of
# previously bold-only colors. See column_colors_ansi in git's color.c
# used by graph.c
graphColors = bold red,bold green,bold yellow,bold blue,bold magenta,bold cyan,bold brightred,bold brightgreen,bold brightyellow,bold brightblue,bold brightmagenta,bold brightcyan
# Show all refs as decoration (e.g. also notes)
initialDecorationSet = all
[merge] [merge]
conflictstyle = diff3 conflictstyle = diff3
log = true log = true
@@ -114,9 +154,9 @@
[rebase] [rebase]
autostash = true autostash = true
[rerere] [rerere]
enabled = true enabled = false
[status] [status]
submodulesummary = true submoduleSummary = true
[submodule] [submodule]
fetchJobs = 0 fetchJobs = 0
[trailer] [trailer]
@@ -125,5 +165,6 @@
email = julian@druck.dev email = julian@druck.dev
name = Julian Prein name = Julian Prein
signingkey = C0A44F69F2E29F6586C86B96CA6B3A516FAC2555 signingkey = C0A44F69F2E29F6586C86B96CA6B3A516FAC2555
[include] [include]
path = user.config path = user.config

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Copyright (c) 2025 Julian Prein # Copyright (c) 2025 Julian Prein
# #
# Meant to be used in git aliases to launch an autoloadable zsh function in the # Meant to be used by git aliases to easily launch an external script from the
# correct directory. # ./scripts/ collection through its basename and in the correct directory (i.e.
# GIT_PREFIX).
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
printf >&2 "Usage: %s <function>\n" "$(basename "$0")" printf >&2 "Usage: %s <function>\n" "$(basename "$0")"
@@ -12,7 +13,7 @@ fi
name="$1" name="$1"
shift shift
BASE="${XDG_CONFIG_HOME:-$HOME/.config}/zsh/autoload/git" BASE="$(dirname "$(realpath "$0")")/scripts"
# In git aliases, shell commands are executed from the top-level directory of # In git aliases, shell commands are executed from the top-level directory of
# the repo. GIT_PREFIX contains the original directory relative to the # the repo. GIT_PREFIX contains the original directory relative to the

View File

@@ -36,18 +36,11 @@ set $TERM_CMD_FLAG
# a single sprite cache on the GPU"[^1], so that startup is almost instant. # a single sprite cache on the GPU"[^1], so that startup is almost instant.
# #
# For this to work best, launch one hidden "daemon" instance at startup so that # For this to work best, launch one hidden "daemon" instance at startup so that
# the kitty process is always running, even when no OS windows exists. # the kitty process is always running, even when no OS windows exists. See the
# # daemon.sh script in .config/kitty.
# NOTE: `--start-as hidden` needs kitty 0.42.0 or later.
#
# Additionally allow remote_control over a socket, so that kitty-cwd works.
# #
# [^1]: kitty(1) # [^1]: kitty(1)
exec --no-startup-id $TERMINAL \ exec --no-startup-id kitty-daemon
--start-as hidden \
--detach \
-o allow_remote_control=socket-only \
--listen-on unix:/tmp/mykitty
# Multi monitor support # Multi monitor support
exec_always --no-startup-id ~/.config/i3/monitor-setup.sh & exec_always --no-startup-id ~/.config/i3/monitor-setup.sh &
@@ -86,6 +79,8 @@ bindsym $mod+Shift+v split horizontal
bindsym $mod+f fullscreen toggle bindsym $mod+f fullscreen toggle
# change container layout (stacked, tabbed, toggle split) # change container layout (stacked, tabbed, toggle split)
#bindsym $mod+s layout stacking
#bindsym $mod+w layout tabbed
bindsym $mod+e layout toggle split bindsym $mod+e layout toggle split
# toggle tiling / floating # toggle tiling / floating
@@ -107,13 +102,15 @@ bindsym $mod+6 exec ~/.config/i3/multi-monitor-workspaces.sh -s 6
bindsym $mod+7 exec ~/.config/i3/multi-monitor-workspaces.sh -s 7 bindsym $mod+7 exec ~/.config/i3/multi-monitor-workspaces.sh -s 7
bindsym $mod+8 exec ~/.config/i3/multi-monitor-workspaces.sh -s 8 bindsym $mod+8 exec ~/.config/i3/multi-monitor-workspaces.sh -s 8
bindsym $mod+9 exec ~/.config/i3/multi-monitor-workspaces.sh -s 9 bindsym $mod+9 exec ~/.config/i3/multi-monitor-workspaces.sh -s 9
bindsym $mod+0 exec ~/.config/i3/multi-monitor-workspaces.sh -s 0 bindsym $mod+0 exec ~/.config/i3/multi-monitor-workspaces.sh -s 10
# switch back to the previous workspace # switch back to the previous workspace
workspace_auto_back_and_forth yes workspace_auto_back_and_forth yes
bindsym $mod+Tab exec i3-msg workspace "$( \ bindsym $mod+Tab workspace back_and_forth
i3-msg -t get_workspaces | \
jq -r '.[] | select(.focused).name')" # switch workspaces forward and backward
bindsym $mod+Next workspace next
bindsym $mod+Prior workspace prev
# Switch visible workspaces (e.g. multi monitor setup) # Switch visible workspaces (e.g. multi monitor setup)
bindsym $mod+Shift+Tab exec i3-msg workspace "$( \ bindsym $mod+Shift+Tab exec i3-msg workspace "$( \
@@ -131,7 +128,7 @@ bindsym $mod+Shift+6 exec ~/.config/i3/multi-monitor-workspaces.sh -m 6
bindsym $mod+Shift+7 exec ~/.config/i3/multi-monitor-workspaces.sh -m 7 bindsym $mod+Shift+7 exec ~/.config/i3/multi-monitor-workspaces.sh -m 7
bindsym $mod+Shift+8 exec ~/.config/i3/multi-monitor-workspaces.sh -m 8 bindsym $mod+Shift+8 exec ~/.config/i3/multi-monitor-workspaces.sh -m 8
bindsym $mod+Shift+9 exec ~/.config/i3/multi-monitor-workspaces.sh -m 9 bindsym $mod+Shift+9 exec ~/.config/i3/multi-monitor-workspaces.sh -m 9
bindsym $mod+Shift+0 exec ~/.config/i3/multi-monitor-workspaces.sh -m 0 bindsym $mod+Shift+0 exec ~/.config/i3/multi-monitor-workspaces.sh -m 10
# move focused container and switch to workspace # move focused container and switch to workspace
bindsym Mod1+Shift+1 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 1 bindsym Mod1+Shift+1 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 1
@@ -143,7 +140,7 @@ bindsym Mod1+Shift+6 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 6
bindsym Mod1+Shift+7 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 7 bindsym Mod1+Shift+7 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 7
bindsym Mod1+Shift+8 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 8 bindsym Mod1+Shift+8 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 8
bindsym Mod1+Shift+9 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 9 bindsym Mod1+Shift+9 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 9
bindsym Mod1+Shift+0 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 0 bindsym Mod1+Shift+0 exec ~/.config/i3/multi-monitor-workspaces.sh -ms 10
# reload the configuration file # reload the configuration file
bindsym $mod+Shift+c reload bindsym $mod+Shift+c reload

View File

@@ -25,25 +25,12 @@ done
shift $((OPTIND - 1)) shift $((OPTIND - 1))
[ $# -gt 0 ] || usage [ $# -gt 0 ] || usage
outputs="$(i3-msg -t get_outputs | jq -r '.[] | select(.active).name')" name="$(i3-msg -t get_tree \
num_outs="$(printf "%s\n" "$outputs" | wc -l)" | jq -r '.. | objects | select(.focused).output')"
if [ "$num_outs" -lt 2 ]; then # NOTE: See `strip-wsnames` in polybar config. With it every monitor has its own
# only one monitor # 1-10 workspaces
workspace="$1" workspace="$1: $name"
else
name="$(i3-msg -t get_tree \
| jq -r '.. | objects | select(.focused).output')"
num="$(printf "%s\n" "$outputs" \
| grep -Fxn "$name" \
| cut -d: -f1)"
num="$((num - 1))"
# Omit the number on the first monitor
[ "$num" -gt 0 ] || num=
workspace="$num$1"
fi
if [ -z "$switch" ] && [ -z "$move" ]; then if [ -z "$switch" ] && [ -z "$move" ]; then
printf "%s\n" "$workspace" printf "%s\n" "$workspace"

34
.config/kitty/daemon.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 Julian Prein
#
# Usage: kitty-daemon [GROUP_NAME]
#
# Daemonize kitty by launching one hidden instance that new invocations can use
# to create new OS windows. This makes kitty startup a lot faster since all
# windows can now share a single CPU process and GPU sprite cache. Additionally
# allow remote_control over a socket, so that kitty-cwd works.
#
# To launch new invocations using the daemon created by this script use:
#
# kitty --single-instance
#
# You can pass an optional instance-group as first parameter. In that case use:
#
# kitty --single-instance --instance-group <NAME>
#
# NOTE: `--start-as hidden` needs kitty 0.42.0 or later.
TMP_DIR="${TMPDIR:-/tmp}/kitty.$USER"
mkdir -p "$TMP_DIR"
name="kitty${1:+-$1}"
kitty \
--single-instance \
${1:+--instance-group "$1"} \
--start-as hidden \
--detach \
--detached-log "$(mktemp -p "$TMP_DIR" "$name.XXXXXX.log")" \
-o allow_remote_control=socket-only \
--listen-on unix:"$TMP_DIR/$name.sock"

View File

@@ -2,22 +2,24 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Copyright (c) 2025 Julian Prein # Copyright (c) 2025 Julian Prein
# #
# Usage: kitty-cwd [GROUP_NAME]
#
# Print the current working directory of the focused kitty window. Returns 4 if # Print the current working directory of the focused kitty window. Returns 4 if
# none exist or is focused. # none exist or is focused.
# NOTE: the backticks are used for hacky line-continuation, taken from if [ -n "$KITTY_LISTEN_ON" ]; then
# https://stackoverflow.com/a/7729087/2092762c9 socket_path="${KITTY_LISTEN_ON#unix:}"
kitten @ --to unix:/tmp/mykitty ls \ else
| jq -er ".[]` socket_path="${TMPDIR:-/tmp}/kitty.$USER/kitty${1:+-$1}.sock"
` | select(.is_focused).tabs.[]` fi
` | select(.is_focused).windows.[]` [ -e "$socket_path" ] || exit 1
` | select(.is_focused).cwd"
# An alternative version that uses recursive descent to find focused objects # NOTE: Unfortunately kitten-@-ls(1) is slow, so communicate with the socket
# that also have a `.cwd` key: # directly.
# printf '\eP@kitty-cmd{%s,%s,%s}\e\\' \
# | jq -er "..` '"cmd":"ls"' \
# ` | objects` '"version":[0,26,0]' \
# ` | select(.is_focused)` '"payload":{"match":"state:focused"}' \
# ` | to_entries.[]` | nc -U -q0 "$socket_path" \
# ` | select(.key == \"cwd\").value" | awk '{ print substr($0, 13, length($0) - 14) }' \
| jq -er ".data | fromjson | .[].tabs.[].windows.[].cwd"

View File

@@ -20,7 +20,7 @@
#: <https://sw.kovidgoyal.net/kitty/kittens/choose-fonts/#font-spec- #: <https://sw.kovidgoyal.net/kitty/kittens/choose-fonts/#font-spec-
#: syntax>. #: syntax>.
# font_size 11.0 font_size 12.0
#: Font size (in pts). #: Font size (in pts).
@@ -630,7 +630,7 @@ mouse_hide_wait -1
#: If empty (default) select_by_word_characters will be used for both #: If empty (default) select_by_word_characters will be used for both
#: directions. #: directions.
# click_interval -1.0 click_interval 0.4
#: The interval between successive clicks to detect double/triple #: The interval between successive clicks to detect double/triple
#: clicks (in seconds). Negative numbers will use the system default #: clicks (in seconds). Negative numbers will use the system default
@@ -1763,7 +1763,7 @@ close_on_child_death yes
#: special value of ask means that kitty will ask before opening the #: special value of ask means that kitty will ask before opening the
#: link when clicked. #: link when clicked.
# shell_integration enabled shell_integration no-cursor
#: Enable shell integration on supported shells. This enables features #: Enable shell integration on supported shells. This enables features
#: such as jumping to previous prompts, browsing the output of the #: such as jumping to previous prompts, browsing the output of the
@@ -2521,7 +2521,8 @@ map kitty_mod+shift+backspace change_font_size all 0
#: Open URL #: Open URL
map kitty_mod+e open_url_with_hints # map kitty_mod+e open_url_with_hints
map kitty_mod+f open_url_with_hints
#:: Open a currently visible URL using the keyboard. The program used #:: Open a currently visible URL using the keyboard. The program used
#:: to open the URL is specified in open_url_with. #:: to open the URL is specified in open_url_with.

View File

@@ -6,11 +6,17 @@
# NOTE: the current file can be edited with `v` already, but this doesn't work # NOTE: the current file can be edited with `v` already, but this doesn't work
# when reading from stdin # when reading from stdin
# NOTE: ^P omits the "done" message # NOTE: ^P omits the "done" message
# NOTE: '' jumps back to the previous position
# #
# edit in vim without any ANSI SGR sequences (e.g. color) # edit in vim without any ANSI escape sequences (e.g. color)
e noaction g|$no-ansi-sgr | nvim -\n e noaction g|$no-ansi | nvim -\n''
# edit in vim while keeping them # edit in vim while keeping them
E noaction g|$nvim -\n E noaction g|$nvim -\n''
# edit current screen in vim
^E noaction |.no-ansi | nvim -\n
# copy whole file
^Y noaction g|$xclip -selection clipboard\n''
#env #env
# NOTE: Lines need a trailing space when concatenating # NOTE: Lines need a trailing space when concatenating

View File

@@ -40,6 +40,7 @@ enable-ipc = true
type = internal/i3 type = internal/i3
pin-workspaces = true pin-workspaces = true
format = <label-state> <label-mode> format = <label-state> <label-mode>
strip-wsnames = true
index-sort = true index-sort = true
wrapping-scroll = false wrapping-scroll = false

View File

@@ -22,10 +22,11 @@ done
if ! pgrep -ax polybar >/dev/null 2>&1; then if ! pgrep -ax polybar >/dev/null 2>&1; then
# launch Polybar on every monitor # launch Polybar on every monitor
# https://github.com/polybar/polybar/issues/763 # https://github.com/polybar/polybar/issues/763
while read -r m; do polybar --list-monitors \
export MONITOR="${m%%:*}" | cut -d: -f1 \
polybar --reload -c "$BASE_DIR/config" main & | xargs -I'{}' -P0 \
done <<<"$(polybar --list-monitors)" env MONITOR='{}' \
polybar --reload -c "$BASE_DIR/config" main &
echo "Polybar launched..." echo "Polybar launched..."
else else

View File

@@ -361,10 +361,12 @@ bind S set -w synchronize-panes
set -g remain-on-exit on set -g remain-on-exit on
if -F "#{>=:#{version},3.3}" { if -F "#{>=:#{version},3.3}" {
bind -n C-d if -F "#{pane_dead}" { kill-pane } { send } bind -n C-d if -F "#{pane_dead}" { kill-pane } { send }
bind -n Escape if -F "#{pane_dead}" { kill-pane } { send }
bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send } bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send }
} { } {
# omitting the key argument was introduced in 3.3 # omitting the key argument was introduced in 3.3
bind -n C-d if -F "#{pane_dead}" { kill-pane } { send C-d } bind -n C-d if -F "#{pane_dead}" { kill-pane } { send C-d }
bind -n Escape if -F "#{pane_dead}" { kill-pane } { send Escape }
bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send Enter } bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send Enter }
} }

View File

@@ -37,18 +37,30 @@ setlocal showbreak=NONE
" This is very hacky. " This is very hacky.
" Only if WinResized is supported " Only if WinResized is supported
if has('##WinResized') if exists('##WinResized')
augroup man_resized augroup man_resized
" The reload has to be delayed slightly, since with an `edit` directly in " The reload has to be delayed slightly, since with an `edit` directly in
" the autocmd, the buffer will just be cleared? (TODO) " the autocmd, the buffer will just be cleared? (TODO)
" NOTE: One could add a wrapper function that checks for the existence of au! WinResized <buffer> call s:redraw_man(v:event)
" already running timers similar to how it's done in the
" highlight_current group (see autocommands.vim), but this feels
" overkill here.
au! WinResized <buffer> call timer_start(10, function("s:redraw_delayed"))
augroup END augroup END
function s:redraw_man(event)
if exists('w:disable_man_resizing')
return
endif
if exists('w:man_resizing_timer_id')
call timer_stop(w:man_resizing_timer_id)
endif
echo a:event
" TODO: make sure that a:event contains window number and that the width
" actually changed with getwininfo()
let w:man_resizing_timer_id = timer_start(10, function("s:redraw_delayed"))
endfunction
" The function can't be redefined during the reload of the ftplugin (i.e. " The function can't be redefined during the reload of the ftplugin (i.e.
" triggered by `edit`), since it is currently executing (i.e. `edit`): " triggered by `edit`), since it is currently executing (i.e. `edit`):
" "
@@ -56,6 +68,8 @@ augroup END
" "
if !exists("*s:redraw_delayed") if !exists("*s:redraw_delayed")
function s:redraw_delayed(timer_id) function s:redraw_delayed(timer_id)
unlet w:man_resizing_timer_id
" Try to keep the position as close as possible, since edit will move to " Try to keep the position as close as possible, since edit will move to
" the start of the file " the start of the file
" TODO: this should be more accurate " TODO: this should be more accurate
@@ -72,5 +86,5 @@ endif
" lines, messing up the whole buffer. Since this is distracting, turn it off. " lines, messing up the whole buffer. Since this is distracting, turn it off.
setlocal nowrap setlocal nowrap
endif " has('##WinResized') endif " exists('##WinResized')
" ------------------------------------------------------------------------------ " ------------------------------------------------------------------------------

View File

@@ -3,6 +3,9 @@ setlocal colorcolumn+=51
setlocal textwidth=72 setlocal textwidth=72
" Spell checking always enabled " Spell checking always enabled
setlocal spell spelllang=en setlocal spell spelllang=en
" Disable C-indentation as it messes up formatting of paragraphs containing
" parentheses
setlocal nocindent
" Disable gutentags as it seems to regenerate the entire tags file when editing " Disable gutentags as it seems to regenerate the entire tags file when editing
" git-commits... " git-commits...
@@ -17,3 +20,27 @@ let g:gutentags_enabled = 0
" When aborting a commit I usually use :cq which I can't when committing through " When aborting a commit I usually use :cq which I can't when committing through
" fugitive. Abbreviate it to something that works. " fugitive. Abbreviate it to something that works.
cabbrev <buffer> cq %d <Bar> x cabbrev <buffer> cq %d <Bar> x
" Fold file listings (staged, unstaged, untracked, ...) and diff
setlocal foldmethod=syntax
" Unfold staged files and diff. inspired by:
" https://vi.stackexchange.com/questions/4050/how-to-search-for-pattern-in-certain-syntax-regions/27008#27008
if has("patch-8.2.0915") || has("nvim-0.7.0")
function s:open_fold(group, content)
" Find line containing `content` that is highlighted with `group`
let l:line_nr = search(a:content, "n", 0, 0, { ->
\ synstack('.', col('.'))
\ ->map('synIDattr(v:val, "name")')
\ ->match(a:group) < 0 })
if l:line_nr <= 0
return
endif
execute l:line_nr->string() .. "foldopen"
endfunction
call s:open_fold("gitcommitSelected", "Changes to be committed:")
call s:open_fold("gitcommitDiff", "diff --")
endif " has("patch-8.2.0915") || has("nvim-0.7.0")

View File

@@ -1,5 +1,8 @@
" Turn on line-wrapping " Turn on line-wrapping
setlocal wrap setlocal wrap
" Disable C-indentation as it messes up formatting of paragraphs containing
" parentheses
setlocal nocindent
" Fold by sections " Fold by sections
function! MdSectionFold() function! MdSectionFold()

View File

@@ -1,5 +1,8 @@
" Turn on line-wrapping " Turn on line-wrapping
setlocal wrap setlocal wrap
" Disable C-indentation as it messes up formatting of paragraphs containing
" parentheses
setlocal nocindent
" Close the quickfix window after a cursor movement " Close the quickfix window after a cursor movement
let g:vimtex_quickfix_autoclose_after_keystrokes = 1 let g:vimtex_quickfix_autoclose_after_keystrokes = 1

View File

@@ -1,3 +1,7 @@
" Disable C-indentation as it messes up formatting of paragraphs containing
" parentheses
setlocal nocindent
" Don't highlight Unicode table separator (e.g. vertical box drawing character). " Don't highlight Unicode table separator (e.g. vertical box drawing character).
" See vimrc.d/looks.vim for w:ignore_non_ascii_chars " See vimrc.d/looks.vim for w:ignore_non_ascii_chars
if vimwiki#vars#get_syntaxlocal('rxTableSep') !~ '[\d0-\d127]' if vimwiki#vars#get_syntaxlocal('rxTableSep') !~ '[\d0-\d127]'

View File

@@ -15,8 +15,10 @@ endfor
" needs vim >= 8.1.1719 to support features like popup and text property as well " needs vim >= 8.1.1719 to support features like popup and text property as well
" as nodejs. " as nodejs.
if ((has('patch-8.1.1719') || has('nvim')) && executable('node')) if ((has('patch-8.1.1719') || has('nvim')) && executable('node'))
let g:coc_global_extensions = let g:coc_global_extensions = [
\ ['coc-clangd', 'coc-sh', 'coc-pyright', 'coc-vimtex', 'coc-vimlsp', 'coc-json'] \ 'coc-clangd', 'coc-sh', 'coc-pyright', 'coc-vimtex',
\ 'coc-vimlsp', 'coc-json', 'coc-go'
\ ]
let g:coc_config_home = $XDG_CONFIG_HOME .. "/vim" let g:coc_config_home = $XDG_CONFIG_HOME .. "/vim"
packadd coc.nvim packadd coc.nvim
endif endif
@@ -24,10 +26,13 @@ endif
" ctags " ctags
if (executable('ctags')) if (executable('ctags'))
packadd vim-gutentags packadd vim-gutentags
" Don't index these folders
let g:gutentags_ctags_exclude = [ let g:gutentags_ctags_exclude = [
\ 'node_modules/*', \ 'node_modules/*',
\ '.git/*', \ '.git/*',
\ 'build/*' \ 'build/*',
\ 'venv/*',
\ '__pycache__/*'
\] \]
endif endif
@@ -36,6 +41,12 @@ if (exists("g:loaded_tmux_navigator"))
let g:tmux_navigator_disable_when_zoomed = 1 let g:tmux_navigator_disable_when_zoomed = 1
endif endif
if (get(g:, 'loaded_fzf_vim'))
let g:fzf_vim = {}
" Use location list instead of quickfix list
let g:fzf_vim.listproc = { list -> fzf#vim#listproc#location(list) }
endif
if (get(g:, 'loaded_vimwiki')) if (get(g:, 'loaded_vimwiki'))
" Use vertical box drawing character as table separator " Use vertical box drawing character as table separator
call vimwiki#vars#set_syntaxlocal('rxTableSep', '│') call vimwiki#vars#set_syntaxlocal('rxTableSep', '│')
@@ -45,4 +56,6 @@ if exists("g:loaded_nrrw_rgn")
" Open narrow window above or to the left of the current window (default " Open narrow window above or to the left of the current window (default
" is topleft). See :h aboveleft etc. " is topleft). See :h aboveleft etc.
let g:nrrw_topbot_leftright = 'aboveleft' let g:nrrw_topbot_leftright = 'aboveleft'
" Leave one more line of padding when the window is small
let g:nrrw_rgn_pad = 1
endif endif

View File

@@ -6,10 +6,6 @@ if !exists('g:did_coc_loaded')
finish finish
endif endif
" Some servers have issues with backup files, see #649.
set nobackup
set nowritebackup
" Always show the signcolumn, otherwise it would shift the text each time " Always show the signcolumn, otherwise it would shift the text each time
" diagnostics appear/become resolved. " diagnostics appear/become resolved.
set signcolumn=yes set signcolumn=yes
@@ -68,7 +64,7 @@ endif
" Make <CR> to accept selected completion item or notify coc.nvim to format " Make <CR> to accept selected completion item or notify coc.nvim to format
" <C-g>u starts a new undo break, please make your own choice. " <C-g>u starts a new undo break, please make your own choice.
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm() inoremap <silent><expr> <CR> coc#pum#visible() ? coc#_select_confirm()
\: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>" \: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"
" Use `[g` and `]g` to navigate diagnostics " Use `[g` and `]g` to navigate diagnostics

View File

@@ -119,6 +119,15 @@ if (exists('g:loaded_gitgutter'))
" lines have been changed. " lines have been changed.
set foldtext=gitgutter#fold#foldtext() set foldtext=gitgutter#fold#foldtext()
endif endif
" Use the number column for the signcolumn (e.g. gitgutter, lsp diagnostics),
" but don't fallback to 'auto' when &number is off
" TODO: install autocommand to set signcolumn to yes when number is turned off
" (and back to number when turned back on)
if &number
set signcolumn=number
else
set signcolumn=yes
endif
" Netrw " Netrw
" Use tree style listing " Use tree style listing

View File

@@ -91,40 +91,56 @@ elseif (has('terminal'))
nmap <leader><CR> <Cmd>terminal<CR> nmap <leader><CR> <Cmd>terminal<CR>
endif endif
" Plugin specific bindings
if (get(g:, 'loaded_fzf')) if (get(g:, 'loaded_fzf'))
nmap <leader>f <Cmd>Files<CR> nmap <leader>ff <Cmd>Files<CR>
nmap <leader>j <Cmd>Lines<CR> nmap <leader>fj <Cmd>Lines<CR>
nmap <leader>/ <Cmd>Lines<CR> nmap <leader>f/ <Cmd>Lines<CR>
nmap <leader>h <Cmd>Helptags<CR> nmap <leader>fh <Cmd>Helptags<CR>
" TODO: fix this? nmap <leader>ft <Cmd>Tags<CR>
if (get(g:, 'loaded_gutentags') || 1) nmap <leader>fbt <Cmd>BTags<CR>
nmap <leader>t <Cmd>Tags<CR> " git files that `git status` lists
nmap <leader>bt <Cmd>BTags<CR> nmap <leader>fgf <Cmd>GFiles?<CR>
endif " 'git log (log?)' and 'git log buffer '
map <leader>fgll <Cmd>Commits<CR>
map <leader>fglb <Cmd>BCommits<CR>
" TODO: <leader>fglb should restrict the log to all staged files when called
" in .git/COMMIT_EDITMSG, maybe with an ftplugin?
endif endif
" Search for selected text. " Search for selected text.
" Modified from https://vim.fandom.com/wiki/Search_for_visually_selected_text " Modified version of:
function! GetVisualSelection() " https://vim.fandom.com/wiki/Search_for_visually_selected_text
let l:old_reg = getreg('"') " and https://github.com/neovim/neovim/blob/08847a9ea15a/runtime/lua/vim/_defaults.lua#L73-L79
let l:old_regtype = getregtype('"') function! GetVisualSelection(escape = "", byteescape = 'n')
let l:save_reg = getreginfo('"')
norm gvy norm gvy
let l:sel = getreg('"') let l:sel = getreg('"')
call setreg('"', l:old_reg, l:old_regtype) call setreg('"', l:save_reg)
let l:sel = l:sel->escape(a:escape)
for l:char in a:byteescape
let l:sel = l:sel->substitute('\'..l:char, '\\'..l:char, 'g')
endfor
return l:sel return l:sel
endfunction endfunction
vmap * /\V<C-R>=escape(GetVisualSelection(),'/\')<CR><CR> " In case these do not exist already (At the time of writing only Neovim adds
vmap # ?\V<C-R>=escape(GetVisualSelection(),'?\')<CR><CR> " mappings for these)
if maparg('*', 'v', 0, 1) == {}
vmap * /\V<C-R>=GetVisualSelection('/\')<CR><CR>
vmap # ?\V<C-R>=GetVisualSelection('?\')<CR><CR>
endif
" Extended `*`. Starts vim search (without jump) and ripgrep " Extended `*`. Starts vim search (without jump) and ripgrep
nmap <leader>* :let @/ = '\<' . expand('<cword>') . '\>' <bar> nmap <leader>* :let @/ = '\<' . expand('<cword>') . '\>' <bar>
\ set hlsearch <bar> \ set hlsearch <bar>
\ Rg \b<C-R>=expand('<cword>')<CR>\b<CR> \ Rg \b<C-R>=expand('<cword>')<CR>\b<CR>
vmap <leader>* :<C-U>let @/ = "\\V<C-R>=escape(escape(GetVisualSelection(), '\'), '"\')<CR>" <bar> " TODO: pass --multiline to rg when multiple lines selected
" TODO: Use ^ and $ anchors in visual-line mode
vmap <leader>* :<C-U>let @/ = "\\V<C-R>=escape(GetVisualSelection('\'), '"\')<CR>" <bar>
\ set hlsearch <bar> \ set hlsearch <bar>
\ Rg <C-R>=escape(GetVisualSelection(), '.\[]<bar>*+?{}^$()')<CR><CR> \ Rg <C-R>=GetVisualSelection('.\[]<bar>*+?{}^$()', 'nt')<CR><CR>
nmap <leader>g* :let @/ = expand('<cword>') <bar> nmap <leader>g* :let @/ = expand('<cword>') <bar>
\ set hlsearch <bar> \ set hlsearch <bar>
\ Rg <C-R>=expand('<cword>')<CR><CR> \ Rg <C-R>=expand('<cword>')<CR><CR>
@@ -136,7 +152,8 @@ vmap <leader>/ <Esc><leader>v/
" Select last pasted text in same visual mode as it was selected (v, V, or ^V) " Select last pasted text in same visual mode as it was selected (v, V, or ^V)
" Taken from: https://vim.fandom.com/wiki/Selecting_your_pasted_text " Taken from: https://vim.fandom.com/wiki/Selecting_your_pasted_text
nnoremap <expr> gp '`[' . strpart(getregtype(), 0, 1) . '`]' " TODO: I want the gp default back - find new mapping
" nnoremap <expr> gp '`[' . strpart(getregtype(), 0, 1) . '`]'
" Git bindings " Git bindings
@@ -153,10 +170,9 @@ nmap <leader>grc :let subject=system('git show -s --date=short --pretty="format:
nmap <leader>gso :r!git config --get user.name<CR>:r!git config --get user.email<CR>I<<ESC>A><ESC>kJISigned-off-by: <ESC> nmap <leader>gso :r!git config --get user.name<CR>:r!git config --get user.email<CR>I<<ESC>A><ESC>kJISigned-off-by: <ESC>
" Add, stash or checkout the current file " Add, stash or checkout the current file
" TODO: Conflict with <leader>gf nmap <leader>gfa <Cmd>!git add -- %<CR>
"nmap <leader>gfa <Cmd>!git add -- %<CR> nmap <leader>gfs <Cmd>!git stash -- %<CR>
"nmap <leader>gfs <Cmd>!git stash -- %<CR> nmap <leader>gfu <Cmd>!git checkout -- %<CR>
"nmap <leader>gfu <Cmd>!git checkout -- %<CR>
if exists('g:loaded_fugitive') if exists('g:loaded_fugitive')
" Interactive `git status` " Interactive `git status`
@@ -165,6 +181,8 @@ if exists('g:loaded_fugitive')
nmap <leader>gcc <Cmd>G commit<CR> nmap <leader>gcc <Cmd>G commit<CR>
" Amend the current commit and open the message in a split " Amend the current commit and open the message in a split
nmap <leader>gca <Cmd>G commit --amend<CR> nmap <leader>gca <Cmd>G commit --amend<CR>
" Commit with the last commit message as template
nmap <leader>gclm <Cmd>G commit-last-msg<CR>
" Move to root of directory " Move to root of directory
nmap <leader>gcd <Cmd>Gcd<CR> nmap <leader>gcd <Cmd>Gcd<CR>
" git blame in scroll bound vertical split (only the commit hashes, see " git blame in scroll bound vertical split (only the commit hashes, see
@@ -191,19 +209,12 @@ if exists('g:loaded_gitgutter')
xmap ih <Plug>(GitGutterTextObjectInnerVisual) xmap ih <Plug>(GitGutterTextObjectInnerVisual)
xmap ah <Plug>(GitGutterTextObjectOuterVisual) xmap ah <Plug>(GitGutterTextObjectOuterVisual)
" Same for hunk navigation bindings + center line " Same for hunk navigation bindings + center line
" TODO: <leader>[h to jump between **all** hunks across all modified
" files. (similar to `add -p`)
nmap [h <Plug>(GitGutterPrevHunk)zz nmap [h <Plug>(GitGutterPrevHunk)zz
nmap ]h <Plug>(GitGutterNextHunk)zz nmap ]h <Plug>(GitGutterNextHunk)zz
endif endif
if (get(g:, 'loaded_fzf'))
" git files that `git status` lists
nmap <leader>gf <Cmd>GFiles?<CR>
" 'git log (log?)' and 'git log buffer '
map <leader>gll <Cmd>Commits<CR>
map <leader>glb <Cmd>BCommits<CR>
" TODO: <leader>glb should restrict the log to all staged files when called
" in .git/COMMIT_EDITMSG, maybe with an ftplugin?
endif
" Y should behave like D & C does " Y should behave like D & C does
nnoremap Y y$ nnoremap Y y$
@@ -441,7 +452,7 @@ vnoremap <leader><C-L> gu
" `ExpandVisualSelection(1)` which results in a block selection that spans over " `ExpandVisualSelection(1)` which results in a block selection that spans over
" all other TODOs as well. " all other TODOs as well.
function! ExpandVisualSelection(direction) function! ExpandVisualSelection(direction)
let l:sel = escape(GetVisualSelection(), '\') let l:sel = GetVisualSelection('\')
normal gv normal gv
" Move the cursor onto the side of the selection that points in the " Move the cursor onto the side of the selection that points in the

View File

@@ -50,14 +50,24 @@ if (get(g:, 'loaded_fzf'))
endif endif
endif endif
" Get red from my colorscheme
let s:red = {}
if exists("*onedark#GetColors")
let s:red = onedark#GetColors()->get("red", {})
endif
let s:red_cterm = s:red->get("cterm", "red")
let s:red_gui = s:red->get("gui", "red")
" Highlight trailing whitespaces " Highlight trailing whitespaces
if match(&listchars, 'trail: \@!') > -1 && match(&listchars, '\vtab:( +)@!') > -1 if match(&listchars, 'trail: \@!') > -1 && match(&listchars, '\vtab:( +)@!') > -1
" Use foreground for coloring if tabs and trailing spaces are displayed " Use foreground for coloring if tabs and trailing spaces are displayed
" as non-space characters " as non-space characters
highlight TrailingWhitespace ctermfg=red guifg=red execute "highlight TrailingWhitespace ctermfg=" .. s:red_cterm
\ .. " guifg=" .. s:red_gui
else else
" Background otherwise " Background otherwise
highlight TrailingWhitespace ctermbg=red guibg=red execute "highlight TrailingWhitespace ctermbg=" .. s:red_cterm
\ .. " guibg=" .. s:red_gui
endif endif
augroup HighlightTrailingWhitespace augroup HighlightTrailingWhitespace
au! au!
@@ -74,8 +84,10 @@ let g:spl_special_chars = {
\ 'fr': 'àâæçèéêëîïôœùûüÿÀÂÆÇÈÉÊËÎÏÔŒÙÛÜŸ', \ 'fr': 'àâæçèéêëîïôœùûüÿÀÂÆÇÈÉÊËÎÏÔŒÙÛÜŸ',
\ } \ }
" Highlight non-ASCII characters in the red used by my color scheme "OneDark" " Highlight non-ASCII characters in red
highlight NonASCIIChars ctermfg=white guifg=white ctermbg=204 guibg=#e06c75 execute "highlight NonASCIIChars ctermfg=white guifg=white "
\ .. "ctermbg=" .. s:red_cterm .. " guibg=" .. s:red_gui
" Do not highlight special characters that are valid in the respective spelllang " Do not highlight special characters that are valid in the respective spelllang
function! HighlightNonASCIIChars() function! HighlightNonASCIIChars()
if exists('w:non_ascii_match_id') if exists('w:non_ascii_match_id')

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env zsh
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 Julian Prein
#
# Rename a branch locally and on a given remote.
emulate -L zsh -o err_return -o no_unset
if (( # < 2 || # > 3 )); then
printf >&2 "Usage: %s OLD NEW [REMOTE]\n" "${0:t}"
return 1
fi
local old new remote
old="$1"
new="$2"
remote="${3:-origin}"
if ! git remote -v | awk '{ print $1 }' | uniq | grep -q "$remote"; then
printf >&2 "Remote '%s' does not exist\n" "$remote"
return 1
fi
git checkout-worktree "$old" <<EOF
git branch -m "$new" && git push -u && git push -d "$remote" "$old"
EOF

View File

@@ -31,11 +31,13 @@
emulate -L zsh -o err_return -o no_unset emulate -L zsh -o err_return -o no_unset
local REPO_NAME WORKTREE_PATH local git_dir cwd_offset REPO_NAME WORKTREE_PATH
# Use the folder name of the main working tree to make calls from another # Use the folder name of the main working tree to make calls from another
# temporary working tree possible # temporary working tree possible
REPO_NAME="${${${$(git rev-parse --git-dir):A}%%/.git*}:t}" git_dir="${$(git rev-parse --git-dir):A}"
WORKTREE_PATH="$(mktemp -d -p "" "worktree.XXX.$REPO_NAME.${1//\//_}")" [[ $git_dir == */.git/modules/* ]] || git_dir="${git_dir%%/.git*}"
REPO_NAME="${git_dir:t}"
WORKTREE_PATH="$(mktemp -d -p "" "wtree.$REPO_NAME.${1//\//_}.XXX")"
local errc ret=0 local errc ret=0
git worktree add "$WORKTREE_PATH" "$@" || ret=$? git worktree add "$WORKTREE_PATH" "$@" || ret=$?
@@ -47,11 +49,27 @@ fi
trap ' trap '
errc=$? errc=$?
<&2 printf "Exiting abnormally. Check and possibly remove '$WORKTREE_PATH' manually.\n" printf >&2 "Exiting abnormally. Check and possibly remove \"%s\" manually.\n" "'"$WORKTREE_PATH"'"
return $errc return $errc
' INT QUIT TERM EXIT ' INT QUIT TERM EXIT
cwd_offset="${${PWD#$(git rev-parse --show-toplevel)}#/}"
pushd -q "$WORKTREE_PATH" pushd -q "$WORKTREE_PATH"
until [[ -d $cwd_offset || -z $cwd_offset ]]; do
cwd_offset="${cwd_offset:h}"
done
[[ -z $cwd_offset ]] || cd "$cwd_offset"
# Discard some environment variables that were set by git when calling this
# script through an alias.
#
# Is set for submodules and will confuse git in the subshell
unset GIT_DIR
# Not sure if this can bring any issues, but better be safe
unset GIT_PREFIX
# TODO: Do we want to unset this too? Could have been set on purpose by the user
# and not git. Maybe only for the subshell via `env`?
#unset GIT_EXEC_PATH
"$SHELL" && errc=$? || errc=$? "$SHELL" && errc=$? || errc=$?
(( !errc )) || echo "shell exited with $errc" (( !errc )) || echo "shell exited with $errc"
@@ -59,7 +77,7 @@ pushd -q "$WORKTREE_PATH"
# Restart the shell (forcefully interactive) until the worktree is removed # Restart the shell (forcefully interactive) until the worktree is removed
until [[ ! -e "$WORKTREE_PATH" ]] || git worktree remove "$WORKTREE_PATH"; do until [[ ! -e "$WORKTREE_PATH" ]] || git worktree remove "$WORKTREE_PATH"; do
[[ -t 0 ]] || [[ -t 0 ]] ||
>&2 printf "Dropping into interactive shell to resolve conflicts\n" printf >&2 "Dropping into interactive shell to resolve conflicts\n"
"$SHELL" -i && errc=$? || errc=$? "$SHELL" -i && errc=$? || errc=$?
(( !errc )) || echo "shell exited with $errc" (( !errc )) || echo "shell exited with $errc"
done done

View File

@@ -1,6 +1,20 @@
#!/usr/bin/env zsh #!/usr/bin/env zsh
git for-each-ref --format='%(upstream),%(refname)' refs/heads refs/remotes \ local idx="${@[(ei)--help]}"
local flags_end="${@[(ei)--]}"
if (( idx < flags_end )); then
printf "Usage: $0 [--all] [REMOTE]..."
return 0
fi
idx="${@[(ei)--all]}"
if (( idx < flags_end )); then
# Replace --all flag with empty string. Will be replaced with
# refs/remotes/ later so that all remotes are listed
set -- "${@[1,$((idx-1))]}" "" "${@[$((idx+1)),-1]}"
fi
git for-each-ref --format='%(upstream),%(refname)' refs/heads "${@/#/refs/remotes/}" \
| sort -d \ | sort -d \
| sed -Ez ' | sed -Ez '
s:(^|\n|,)refs/(heads|remotes/):\1:g s:(^|\n|,)refs/(heads|remotes/):\1:g
@@ -8,9 +22,8 @@ git for-each-ref --format='%(upstream),%(refname)' refs/heads refs/remotes \
s:,/:,:g s:,/:,:g
s:(^|\n)([^,]+),\n\2:\1\2:g s:(^|\n)([^,]+),\n\2:\1\2:g
s:(^|\n)([^/,]*)([^\n]*\n\2(,|/))*:\n,\n&:g s:(^|\n)([^/,]*)([^\n]*\n\2(,|/))*:\n,\n&:g
s:\n,\n+$:\n: s:\n,\n+$:\n:' \
' \ | { echo remote,local; cat; } \
| (echo remote,local; cat) \
| sed -E 's:(.*),(.*):\2,\1:g; s:^,: ,:; s:,$:, :' \ | sed -E 's:(.*),(.*):\2,\1:g; s:^,: ,:; s:,$:, :' \
| column -ts, \ | column -ts, \
| sed '2d; 1{p;s/./―/g}' | sed '2d; 1{p;s/./―/g}'

View File

@@ -105,6 +105,7 @@ local -A binds=(
"end" "last" "end" "last"
"ctrl-d" "half-page-down" "ctrl-d" "half-page-down"
"ctrl-u" "half-page-up" "ctrl-u" "half-page-up"
"ctrl-s" "toggle-sort"
"ctrl-t" "toggle-track" "ctrl-t" "toggle-track"
# Keep the current line selected while deleting the query # Keep the current line selected while deleting the query
"bspace" "track-current+backward-delete-char" "bspace" "track-current+backward-delete-char"
@@ -120,11 +121,11 @@ local -A binds=(
# TODO: This assumes less to be used in core.pager # TODO: This assumes less to be used in core.pager
"enter" "execute@$fzf_preview[patch] | $pager -+F@" "enter" "execute@$fzf_preview[patch] | $pager -+F@"
# Preview stats # Preview stats
"ctrl-s" "change-preview($fzf_preview[stat])" "ctrl-alt-s" "change-preview($fzf_preview[stat])"
# Preview patch # Preview patch
"ctrl-p" "change-preview($fzf_preview[patch])" "ctrl-alt-p" "change-preview($fzf_preview[patch])"
# Files only # Files only
"ctrl-f" "change-preview($fzf_preview[files_only])" "ctrl-alt-f" "change-preview($fzf_preview[files_only])"
# For ctrl-space see below # For ctrl-space see below
) )
@@ -132,13 +133,21 @@ local -A binds=(
# fzf_preview[stat] in this case). It does not really make sense to pass # fzf_preview[stat] in this case). It does not really make sense to pass
# it to `git log` but can be an indicator for the preview function # it to `git log` but can be an indicator for the preview function
local color_brown=$'\e[38;5;144m'
local color_grey=$'\e[38;5;59m'
local header=""
header+="${color_brown}Enter${color_grey}: fullscreen, "
header+="${color_brown}C-y${color_grey}: yank hash, "
header+="${color_brown}C-Space${color_grey}: cycle preview, "
header+="${color_brown}C-A-[spf]${color_grey}: preview stats/patch/files"
local -a fzf_args=( local -a fzf_args=(
# Understand ansi color escape sequences. # Understand ansi color escape sequences.
"--ansi" "--ansi"
# Expand the binds array in the format "key1:value1,key2:value2". # Expand the binds array in the format "key1:value1,key2:value2".
"--bind" "${(@kj:,:)binds/(#m)*/$MATCH:$binds[$MATCH]}" "--bind" "${(@kj:,:)binds/(#m)*/$MATCH:$binds[$MATCH]}"
# Display key-bindings in a sticky header # Display key-bindings in a sticky header
"--header" $'\e[38;5;144mEnter\e[38;5;59m: fullscreen, \e[38;5;144mC-y\e[38;5;59m: yank hash, \e[38;5;144mC-Space\e[38;5;59m: cycle preview, \e[38;5;144mC-[spf]\e[38;5;59m: preview stats/patch/files' "--header" "$header"
# Keep header above prompt line # Keep header above prompt line
"--header-first" "--header-first"
# Execute git show on the commit as preview. # Execute git show on the commit as preview.

View File

@@ -128,7 +128,7 @@ fi
# Use a reasonable time format # Use a reasonable time format
alias date='env LC_TIME=tk_TM date' alias date='env LC_TIME=tk_TM date'
# List human readable sizes in order # List human readable sizes in order
alias sizes='du -sch * | sort -h' alias sizes='() { du -sch ${1:-*} "${@[2,-1]}" | sort -h }'
# Count number of occurrences for every line in stdin # Count number of occurrences for every line in stdin
alias count='sort | uniq -c | sort -n' alias count='sort | uniq -c | sort -n'
# Inspired by https://stackoverflow.com/a/54541337 # Inspired by https://stackoverflow.com/a/54541337
@@ -183,7 +183,7 @@ fi
# Default flags # Default flags
add_flags ls --color=auto --group-directories-first -p -v add_flags ls --color=auto --group-directories-first -p -v
add_flags grep --color=auto --exclude-dir=.git --exclude=tags add_flags grep --color=auto --exclude-dir=.git --exclude=tags
add_flags cp -i add_flags cp -ia
add_flags mv -i add_flags mv -i
# Only add flags if rm is not aliased to a different command (e.g. trash). # Only add flags if rm is not aliased to a different command (e.g. trash).
# NOTE: This also works if rm is not yet aliased. # NOTE: This also works if rm is not yet aliased.

View File

@@ -0,0 +1 @@
../../.config/git/external-script.sh

View File

@@ -1 +0,0 @@
../../.config/git/zsh-autoload.sh

1
.local/bin/kitty-daemon Symbolic link
View File

@@ -0,0 +1 @@
../../.config/kitty/daemon.sh

View File

@@ -3,5 +3,26 @@
# Copyright (c) 2025 Julian Prein # Copyright (c) 2025 Julian Prein
# #
# Remove ANSI escape sequences. # Remove ANSI escape sequences.
#
# Additionally to the general form `\e[ -/]*[0-~]` this also tries to cover
# sequences that are followed by additional bytes (e.g. CSI, OSC). This script
# covers no C1 sequences.
#
# The patterns cover the following escape sequences (from top to bottom):
# - CSI
# - OSC, DCS, PM & APC
# - SOS
# - All remaining sequences
#
# See:
# - https://en.wikipedia.org/wiki/ANSI_escape_code
# - https://www.ecma-international.org/wp-content/uploads/ECMA-35_6th_edition_december_1994.pdf
# - https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
env LC_ALL=C sed 's/\[[0-?]*[ -/]*[@-z]//g' env LC_ALL=C sed -E "$(printf "%b" \
's/' \
'\033\\[[0-?]*[ -/]*[@-~]' '|' \
'\033[]P^_][\010-\015 -~]*\033\\\\' '|' \
'\033X([^\033]|\033+[^\033X\\])*\033+\\\\' '|' \
'\033[ -/]*[0-~]' \
'//g')"

View File

@@ -5,4 +5,4 @@
# Remove ANSI SGR (Select Graphic Rendition) escape sequences, e.g. setting # Remove ANSI SGR (Select Graphic Rendition) escape sequences, e.g. setting
# color or bold font. # color or bold font.
env LC_ALL=C sed 's/\[[0-?]*[ -/]*m//g' env LC_ALL=C sed "$(printf 's/\033\\[[0-?]*[ -/]*m//g')"

View File

@@ -62,4 +62,119 @@ git diff --staged --name-only --diff-filter=AT $against \
fi fi
done done
[ "$abort" -eq 0 ] || die [ "$abort" -eq 0 ] || die
} } || exit
# Make sure that a deletion does not break any symlinks (including renaming a
# file)
# TODO: switch all these to null-terminated lines
deleted_files="$(git diff-index --cached --name-only --diff-filter=D $against)"
if [ -n "$deleted_files" ]; then
# First, check for broken symlinks in the tree
all_broken_links="$(find . -xtype l -exec stat -c '%N' '{}' '+')"
# NOTE: The cat could be replaced by instead adding the heredoc to the
# `done` of the loop, but would make the code much less readable
cat <<EOF \
| while read -r deletion
$deleted_files
EOF
do
# As a first heuristic, check if there is a broken symlink with
# a target with the same basename as the deleted file
#
# TODO: stat escapes quotes sometimes. Does everything work
# then?
possible_links="$(
grep "[\"'/]$(
basename "$deletion" \
| sed 's/[.[^$*\\]/\\&/g'
)[\"']\$" <<EOF
$all_broken_links
EOF
)"
[ -n "$possible_links" ] || continue
cat << EOF \
| while read -r link
$possible_links
EOF
do
# TODO: this is probably quite brittle, depending on how
# `stat` quotes source and target
target="${link##* -> [\"\']}"
target="${target%[\"\']}"
source="${link%%[\"\'] -> *}"
source="${source#[\"\']}"
if [ -z "${target##/*}" ]; then
# absolute link
if [ "$target" = "$PWD/$deletion" ]; then
die "You broke the symlink $link"
fi
else
# relative link
target="$(realpath -m "$source/../$target")"
if [ "$target" = "$PWD/$deletion" ]; then
die "You broke the symlink $link"
fi
fi
done || exit
done || exit
# Second, check all symlinks in the index if they still point to the
# deleted file
all_links_in_index="$(
git ls-files --format="%(objectmode) %(objectname) %(path)" \
| grep '^120000'
)"
cat <<EOF \
| while read -r deletion
$deleted_files
EOF
do
# As a first heuristic, get all links in the tree with a target
# with the same basename as the deleted file
possible_links="$(
cut -d' ' -f2 <<EOF \
| git cat-file --batch \
| grep -B1 "\(^\|/\)$(
basename "$deletion" \
| sed 's/[.[^$*\\]/\\&/g'
)\$" \
| paste - -
$all_links_in_index
EOF
)"
[ -n "$possible_links" ] || continue
cat << EOF \
| while read -r link
$possible_links
EOF
do
target="${link#* }"
source="$(
grep -F "${link%% *}" <<EOF \
| cut -d' ' -f3-
$all_links_in_index
EOF
)"
if [ -z "${target##/*}" ]; then
# absolute link
if [ "$target" = "$PWD/$deletion" ]; then
die "You broke the symlink \"$source\" -> \"$target\""
fi
else
# relative link
target="$(realpath -m "$source/../$target")"
if [ "$target" = "$PWD/$deletion" ]; then
die "You broke the symlink \"$source\" -> \"$target\""
fi
fi
done || exit
done || exit
# TODO: also check potential symlinks pointing to now empty directories
fi