-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·675 lines (608 loc) · 29.2 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·675 lines (608 loc) · 29.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
#!/usr/bin/env bash
#
# Installs Helix, Alacritty, Zellij, toolchains/LSPs/DAPs for
# Python, Rust, Zig, C++, and C, plus a set of everyday CLI utilities
# (search, navigation, git, system, prompt) — then symlinks the
# configs in this repo into the right XDG locations and sets shell
# aliases.
#
# Supports: macOS (Homebrew), Debian/Ubuntu (apt), Arch (pacman).
# Re-run any time; it's safe/idempotent where the package manager allows it.
set -euo pipefail
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
LOCAL_BIN="$HOME/.local/bin"
echo "==> Detected dotfiles at: $DOTFILES_DIR"
echo "==> Config target: $CONFIG_HOME"
# Remove leftover dev script executable if present from earlier setups
rm -f "$LOCAL_BIN/dev" "$DOTFILES_DIR/scripts/dev.sh"
# -------------------------------------------------------------------
# 1. Detect platform / package manager
# -------------------------------------------------------------------
OS="$(uname -s)"
PKG=""
if [[ "$OS" == "Darwin" ]]; then
PKG="brew"
command -v brew >/dev/null 2>&1 || { echo "Homebrew not found. Install it from https://brew.sh first."; exit 1; }
elif [[ -f /etc/debian_version ]]; then
PKG="apt"
elif [[ -f /etc/arch-release ]]; then
PKG="pacman"
else
echo "Unsupported OS. Please install packages manually — see README.md."
exit 1
fi
echo "==> Using package manager: $PKG"
install_pkgs() {
case "$PKG" in
brew) brew install "$@" ;;
apt) sudo apt update && sudo apt install -y "$@" ;;
pacman) sudo pacman -Sy --needed --noconfirm "$@" ;;
esac
}
ensure_rust() {
if ! command -v cargo >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi
source "$HOME/.cargo/env"
}
# -------------------------------------------------------------------
# 2. Core apps: helix, alacritty, zellij
# -------------------------------------------------------------------
echo "==> Installing helix, alacritty, zellij..."
case "$PKG" in
brew) # bash: macOS ships a frozen bash 3.2 at /bin/bash; install a
# current bash 5.x (used as the Alacritty shell)
install_pkgs bash helix alacritty zellij ;;
apt) install_pkgs alacritty
# Prefer snap for Helix — the maveonair PPA has lagged noticeably
# behind upstream releases (confirmed: PPA stuck at 24.7 while
# snap ships 25.07+). Snap also makes future updates a one-liner.
if command -v snap >/dev/null 2>&1; then
# Remove a stale apt/PPA-installed helix so it doesn't shadow
# the snap binary on PATH
if dpkg -l helix >/dev/null 2>&1; then
echo " Removing older apt-installed helix in favor of snap..."
sudo apt remove -y helix || true
fi
if snap list helix >/dev/null 2>&1; then
echo " Refreshing helix snap to the latest version..."
sudo snap refresh helix
else
sudo snap install helix --classic || sudo snap install helix
fi
if ! command -v hx >/dev/null 2>&1 && command -v helix.hx >/dev/null 2>&1; then
sudo snap alias helix.hx hx
fi
elif [[ -f /etc/os-release ]] && grep -qi '^ID=ubuntu' /etc/os-release; then
echo "!! snapd not found — falling back to the maveonair PPA,"
echo " which can lag behind upstream. Install snapd for a"
echo " more current build: sudo apt install snapd"
command -v add-apt-repository >/dev/null 2>&1 || install_pkgs software-properties-common
sudo add-apt-repository -y ppa:maveonair/helix-editor
sudo apt update
sudo apt install -y helix
else
echo "!! No snap and not Ubuntu. Grab a release from:"
echo " https://github.com/helix-editor/helix/releases"
fi
if ! command -v zellij >/dev/null 2>&1; then
ensure_rust
cargo install --locked zellij || {
echo "!! zellij build failed. Try 'rustup update' then re-run,"
echo " or grab a release from https://github.com/zellij-org/zellij/releases"
}
fi ;;
pacman) install_pkgs helix alacritty zellij ;;
esac
# -------------------------------------------------------------------
# 3. Nerd Fonts (JetBrains Mono)
# -------------------------------------------------------------------
echo "==> Installing JetBrainsMono Nerd Font..."
case "$PKG" in
brew)
brew install --cask font-jetbrains-mono-nerd-font || true
;;
pacman)
install_pkgs ttf-jetbrains-mono-nerd
;;
apt)
install_pkgs curl unzip fontconfig
FONT_DIR="$HOME/.local/share/fonts"
if ! fc-list | grep -qi "JetBrainsMono"; then
mkdir -p "$FONT_DIR"
echo " Downloading JetBrainsMono Nerd Font release..."
curl -fLo /tmp/JetBrainsMono.zip https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip
unzip -o /tmp/JetBrainsMono.zip -d "$FONT_DIR/JetBrainsMonoNerdFont"
rm /tmp/JetBrainsMono.zip
fc-cache -fv
else
echo " JetBrainsMono Nerd Font already present in font cache."
fi
;;
esac
# -------------------------------------------------------------------
# 4. Common build tooling + markdown-preview helpers
# -------------------------------------------------------------------
echo "==> Installing common build tools + glow/entr..."
case "$PKG" in
brew) install_pkgs git cmake llvm glow entr bear ;;
apt) install_pkgs git cmake build-essential clang clangd clang-tidy \
lldb bear entr
if ! command -v glow >/dev/null 2>&1; then
echo " Installing glow via Charm's apt repo..."
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
sudo apt update && sudo apt install -y glow
fi ;;
pacman) install_pkgs git cmake base-devel clang lldb glow entr bear ;;
esac
if ! command -v lldb-dap >/dev/null 2>&1; then
if command -v lldb-vscode >/dev/null 2>&1; then
mkdir -p "$LOCAL_BIN"
ln -sf "$(command -v lldb-vscode)" "$LOCAL_BIN/lldb-dap"
echo " Symlinked lldb-vscode -> lldb-dap in $LOCAL_BIN (make sure it's on PATH)"
else
echo "!! lldb-dap not found. Install your platform's LLVM/lldb package (>=17 ships lldb-dap directly)."
fi
fi
# -------------------------------------------------------------------
# 5. Python: interpreter + pyright + ruff
# -------------------------------------------------------------------
echo "==> Setting up Python tooling..."
case "$PKG" in
brew) install_pkgs python pipx ;;
apt) install_pkgs python3 python3-pip pipx ;;
pacman) install_pkgs python python-pipx ;;
esac
pipx ensurepath || true
pipx install pyright --force
pipx install ruff --force
# uv: the cargo-equivalent for Python — manages virtualenvs, dependencies,
# and `uv run` in one fast tool (Rust-based). Installed via its official
# installer, which drops the binary in ~/.local/bin.
if ! command -v uv >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | sh
fi
# pytest + pytest-watcher (ptw): test runner and watch-mode for `pt`/`pw`/
# `ptk`. pytest-watcher replaces the abandoned pytest-watch (same `ptw`
# command, but the watch path is a required argument: `ptw .`).
# Installed globally as a fallback; prefer adding them as project
# dev-deps via `uv add --dev pytest` for per-project environments.
pipx install pytest --force || echo "!! pytest install failed."
# pytest-watcher's `watchdog` dep ships no CPython 3.14 wheel yet and its
# C-extension source build fails on macOS; fall back to a managed 3.13.
pipx install pytest-watcher --force \
|| pipx install pytest-watcher --force --python 3.13 --fetch-missing-python \
|| echo "!! pytest-watcher install failed."
# -------------------------------------------------------------------
# 6. Rust: rustup toolchain + rust-analyzer + clippy + cargo subcommands
# -------------------------------------------------------------------
echo "==> Setting up Rust tooling..."
ensure_rust
rustup component add rust-analyzer rust-src clippy rustfmt
# Test running + fast incremental feedback
cargo install cargo-watch --locked || echo "!! cargo-watch install failed — you can retry manually later."
cargo install cargo-nextest --locked || echo "!! cargo-nextest install failed — 'test' tab falls back to 'cargo test'."
# bacon: background compiler with a compact, always-on diagnostics panel.
# This is the thing most people actually want for "just show me errors,
# compactly, live" — nicer than piping cargo-watch output through grep.
cargo install bacon --locked || echo "!! bacon install failed — you can retry manually later."
# cargo-edit: adds `cargo add` / `cargo rm` / `cargo upgrade` for managing
# Cargo.toml dependencies from the CLI instead of hand-editing.
cargo install cargo-edit --locked || echo "!! cargo-edit install failed — you can retry manually later."
# cargo-outdated: reports dependencies with newer versions available.
cargo install cargo-outdated --locked || echo "!! cargo-outdated install failed — you can retry manually later."
# cargo-audit: scans Cargo.lock against the RustSec advisory database for
# known security vulnerabilities.
cargo install cargo-audit --locked || echo "!! cargo-audit install failed — you can retry manually later."
# cargo-expand: pretty-prints the output of macro expansion — handy for
# debugging derive macros and proc-macros.
cargo install cargo-expand --locked || echo "!! cargo-expand install failed — you can retry manually later."
# -------------------------------------------------------------------
# 6b. yazi: terminal file manager (tree/columns view, previews) — handy
# for bulk file ops (rename/move/copy/delete) that Helix's built-in
# `space e` explorer deliberately doesn't do.
# -------------------------------------------------------------------
echo "==> Installing yazi..."
case "$PKG" in
brew) install_pkgs yazi ;;
pacman) install_pkgs yazi ;;
apt)
# No apt package. Note: yazi-fm/yazi-cli can't be built directly
# from crates.io via `cargo install` due to a cargo build-script
# limitation — yazi ships a small installer wrapper crate,
# `yazi-build`, that has to be used instead.
ensure_rust
cargo install --force --locked yazi-build || echo "!! yazi install failed — you can retry manually later."
install_pkgs ffmpegthumbnailer poppler-utils unar file || true
;;
esac
# -------------------------------------------------------------------
# 7. Zig: compiler (includes fmt) + zls language server
# -------------------------------------------------------------------
echo "==> Setting up Zig tooling..."
case "$PKG" in
brew) install_pkgs zig ;;
apt) echo "!! Debian/Ubuntu apt's zig package is usually stale."
echo " Grab the latest from https://ziglang.org/download/ and put it on PATH." ;;
pacman) install_pkgs zig ;;
esac
echo "!! zls (Zig LSP) has no universal package — build it from source, matched to your zig version:"
echo " git clone https://github.com/zigtools/zls && cd zls && zig build -Doptimize=ReleaseSafe"
echo " then put zig-out/bin/zls on your PATH."
# -------------------------------------------------------------------
# 8. C / C++: clangd + lldb-dap already installed above
# -------------------------------------------------------------------
echo "==> C/C++ tooling uses clangd + lldb-dap (installed above)."
echo " Tip: generate compile_commands.json per project with 'bear -- make'"
echo " or CMake's -DCMAKE_EXPORT_COMPILE_COMMANDS=ON so clangd has full context."
# -------------------------------------------------------------------
# 9. Extra CLI utilities: search, navigation, git, system, prompt
# -------------------------------------------------------------------
echo "==> Installing extra CLI utilities..."
case "$PKG" in
brew)
install_pkgs ripgrep fd fzf zoxide bat eza jq git-delta \
btop dust procs gh lazygit just direnv \
hyperfine starship tealdeer resvg
;;
pacman)
install_pkgs ripgrep fd fzf zoxide bat eza jq git-delta \
btop dust procs github-cli lazygit just direnv \
hyperfine starship tealdeer resvg
;;
apt)
# Packages that install cleanly under their expected name/binary
install_pkgs ripgrep fzf jq direnv hyperfine
# fd and bat ship under different binary names on Debian/Ubuntu
install_pkgs fd-find bat
mkdir -p "$LOCAL_BIN"
command -v fd >/dev/null 2>&1 || ln -sf "$(command -v fdfind)" "$LOCAL_BIN/fd"
command -v bat >/dev/null 2>&1 || ln -sf "$(command -v batcat)" "$LOCAL_BIN/bat"
# zoxide / btop / git-delta: in repos on recent releases, otherwise
# fall back to their official install scripts / cargo
install_pkgs zoxide btop git-delta || true
if ! command -v zoxide >/dev/null 2>&1; then
curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
fi
if ! command -v delta >/dev/null 2>&1; then
ensure_rust
cargo install git-delta --locked || echo "!! git-delta install failed."
fi
# eza: usually not in the default Ubuntu/Debian repos — add the
# maintainer's apt repo
if ! command -v eza >/dev/null 2>&1; then
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc \
| sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg
echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" \
| sudo tee /etc/apt/sources.list.d/gierens.list
sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list
sudo apt update && sudo apt install -y eza
fi
# gh CLI: needs GitHub's own apt repo
if ! command -v gh >/dev/null 2>&1; then
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/githubcli.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list
sudo apt update && sudo apt install -y gh
fi
# dust, procs, just, tealdeer: rarely packaged for apt — cargo install
ensure_rust
for entry in "dust:du-dust" "procs:procs" "just:just" "tldr:tealdeer"; do
bin="${entry%%:*}"; crate="${entry##*:}"
command -v "$bin" >/dev/null 2>&1 || cargo install "$crate" --locked || echo "!! $crate install failed."
done
# resvg: SVG rasterizer CLI (no apt package) — cargo install
command -v resvg >/dev/null 2>&1 || cargo install resvg --locked || echo "!! resvg install failed."
# lazygit: no apt package — grab the latest release binary
if ! command -v lazygit >/dev/null 2>&1; then
LG_VERSION="$(curl -s https://api.github.com/repos/jesseduffield/lazygit/releases/latest \
| grep -Po '"tag_name": *"v\K[^"]*')"
curl -Lo /tmp/lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LG_VERSION}/lazygit_${LG_VERSION}_Linux_x86_64.tar.gz"
tar xf /tmp/lazygit.tar.gz -C /tmp lazygit
install /tmp/lazygit "$LOCAL_BIN/lazygit"
rm -f /tmp/lazygit.tar.gz /tmp/lazygit
fi
# starship: official install script
command -v starship >/dev/null 2>&1 || curl -sS https://starship.rs/install.sh | sh -s -- -y
;;
esac
# -------------------------------------------------------------------
# 9b. Git diff/merge tooling: difftastic (structural diffs), mergiraf
# (syntax-aware merge driver), rerere (replay past resolutions)
# -------------------------------------------------------------------
echo "==> Installing difftastic + mergiraf..."
case "$PKG" in
brew)
install_pkgs difftastic mergiraf
;;
pacman)
install_pkgs difftastic
# mergiraf is in the AUR; build from source as a fallback
command -v mergiraf >/dev/null 2>&1 || { ensure_rust; cargo install --locked mergiraf || echo "!! mergiraf install failed."; }
;;
apt)
ensure_rust
command -v difft >/dev/null 2>&1 || cargo install --locked difftastic || echo "!! difftastic install failed."
command -v mergiraf >/dev/null 2>&1 || cargo install --locked mergiraf || echo "!! mergiraf install failed."
;;
esac
# Wire up delta as git's pager for log/show/blame + interactive add
if command -v delta >/dev/null 2>&1; then
git config --global core.pager delta
git config --global interactive.diffFilter "delta --color-only"
fi
# rerere: reuse recorded conflict resolutions when the same conflict reappears
git config --global rerere.enabled true
# difftastic: structural (syntax-aware) diff for `git diff`/`show`/`log -p`.
# delta above stays as the pager; difftastic replaces the diff *algorithm*.
if command -v difft >/dev/null 2>&1; then
git config --global diff.external "difft --color=always"
git config --global alias.dlog "log -p --ext-diff"
git config --global alias.dshow "show --ext-diff"
fi
# mergiraf: syntax-aware merge driver. diff3 conflict style gives it the base
# revision it needs to reconstruct all three sides. The driver string is safe
# on git < 2.44 too — mergiraf detects the unexpanded %S/%X/%Y placeholders
# and falls back gracefully (see mergiraf docs).
if command -v mergiraf >/dev/null 2>&1; then
git config --global merge.conflictStyle diff3
git config --global merge.mergiraf.name mergiraf
git config --global merge.mergiraf.driver 'mergiraf merge --git %O %A %B -s %S -x %X -y %Y -p %P -l %L'
mkdir -p "$CONFIG_HOME/git"
if ! grep -Fq 'merge=mergiraf' "$CONFIG_HOME/git/attributes" 2>/dev/null; then
echo '* merge=mergiraf' >> "$CONFIG_HOME/git/attributes"
fi
fi
# The rc-file helpers below only append to files that already exist, so make
# sure they do — on a fresh macOS there's typically no ~/.bashrc or ~/.zshrc.
touch "$HOME/.bashrc" "$HOME/.zshrc"
# macOS quirk: bash *login* shells (what Alacritty/Terminal.app spawn) read
# ~/.bash_profile and never ~/.bashrc, so everything this script appends to
# .bashrc would be silently skipped. Chain them.
if [[ "$OS" == "Darwin" ]]; then
if ! grep -Fq '.bashrc' "$HOME/.bash_profile" 2>/dev/null; then
printf '\n[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"\n' >> "$HOME/.bash_profile"
echo "==> Added ~/.bashrc sourcing to ~/.bash_profile"
fi
fi
add_line() {
local rc_file="$1" line="$2"
if [[ -f "$rc_file" ]] && ! grep -Fq "$line" "$rc_file"; then
echo "$line" >> "$rc_file"
fi
}
if command -v starship >/dev/null 2>&1; then
add_line "$HOME/.bashrc" 'eval "$(starship init bash)"'
add_line "$HOME/.zshrc" 'eval "$(starship init zsh)"'
fi
# zoxide wants to be initialized at the end of the rc file (after anything
# that installs prompt hooks, like starship), otherwise its doctor warns.
if command -v zoxide >/dev/null 2>&1; then
add_line "$HOME/.bashrc" 'eval "$(zoxide init bash)"'
add_line "$HOME/.zshrc" 'eval "$(zoxide init zsh)"'
fi
# Set Helix as the default $EDITOR/$VISUAL — respected by yazi's built-in
# "open in editor" action, git commit/rebase, crontab -e, and anything
# else that shells out to an editor rather than using per-tool config.
add_line "$HOME/.bashrc" 'export EDITOR="hx"'
add_line "$HOME/.bashrc" 'export VISUAL="hx"'
add_line "$HOME/.zshrc" 'export EDITOR="hx"'
add_line "$HOME/.zshrc" 'export VISUAL="hx"'
# -------------------------------------------------------------------
# 10. Symlink configs + helper scripts + shell aliases
# -------------------------------------------------------------------
echo "==> Symlinking configs into $CONFIG_HOME ..."
mkdir -p "$CONFIG_HOME/helix" "$CONFIG_HOME/alacritty" "$CONFIG_HOME/zellij/layouts" "$LOCAL_BIN"
ln -sf "$DOTFILES_DIR/helix/config.toml" "$CONFIG_HOME/helix/config.toml"
ln -sf "$DOTFILES_DIR/helix/languages.toml" "$CONFIG_HOME/helix/languages.toml"
ln -sf "$DOTFILES_DIR/alacritty/alacritty.toml" "$CONFIG_HOME/alacritty/alacritty.toml"
ln -sf "$DOTFILES_DIR/zellij/config.kdl" "$CONFIG_HOME/zellij/config.kdl"
ln -sf "$DOTFILES_DIR/zellij/layouts/dev.kdl" "$CONFIG_HOME/zellij/layouts/dev.kdl"
ln -sf "$DOTFILES_DIR/zellij/layouts/rsdev.kdl" "$CONFIG_HOME/zellij/layouts/rsdev.kdl"
ln -sf "$DOTFILES_DIR/zellij/layouts/pydev.kdl" "$CONFIG_HOME/zellij/layouts/pydev.kdl"
ln -sf "$DOTFILES_DIR/starship/starship.toml" "$CONFIG_HOME/starship.toml"
ln -sf "$DOTFILES_DIR/scripts/mdp.sh" "$LOCAL_BIN/mdp"
ln -sf "$DOTFILES_DIR/scripts/llm.sh" "$LOCAL_BIN/llm"
# yazi: route Enter on text/code files to Helix instead of yazi's default
# opener (block=true hands the terminal fully to hx instead of trying to
# background it, which would otherwise leave Helix in a broken state)
if command -v yazi >/dev/null 2>&1 || command -v ya >/dev/null 2>&1; then
mkdir -p "$CONFIG_HOME/yazi"
ln -sf "$DOTFILES_DIR/yazi/yazi.toml" "$CONFIG_HOME/yazi/yazi.toml"
fi
# lazygit: render diffs through difftastic (structural diff)
if command -v lazygit >/dev/null 2>&1; then
mkdir -p "$CONFIG_HOME/lazygit"
ln -sf "$DOTFILES_DIR/lazygit/config.yml" "$CONFIG_HOME/lazygit/config.yml"
fi
if [[ -d "$DOTFILES_DIR/scripts" ]]; then
chmod +x "$DOTFILES_DIR"/scripts/*.sh
fi
# Add zellij layout aliases to rc files
add_alias() {
local rc_file="$1" alias_cmd="$2"
if [[ -f "$rc_file" ]]; then
if ! grep -Fq "$alias_cmd" "$rc_file"; then
echo "==> Adding $alias_cmd to $rc_file"
echo "$alias_cmd" >> "$rc_file"
fi
fi
}
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
add_alias "$rc" "alias dev='zellij --layout dev'"
add_alias "$rc" "alias rsdev='zellij --layout rsdev'"
add_alias "$rc" "alias pydev='zellij --layout pydev'"
done
# -------------------------------------------------------------------
# Rust / cargo aliases — compact-output build loop
# -------------------------------------------------------------------
# cb - cargo build, compact one-line diagnostics, no progress spam
# cc - cargo check, same but skips codegen (fastest feedback loop)
# ccl - cargo clippy, compact diagnostics
# cw - cargo-watch running `cargo check` on every save, compact format
# cbg - bacon, the live always-on compact diagnostics panel
# ct - run the full test suite via cargo-nextest
# cf - cargo fmt
# cu - cargo update (bump Cargo.lock within semver constraints)
# cr - cargo run (build + run the project's binary), mirrors `pm`
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
add_alias "$rc" "alias cb='cargo build --quiet --message-format=short'"
add_alias "$rc" "alias cc='cargo check --quiet --message-format=short'"
add_alias "$rc" "alias ccl='cargo clippy --quiet --message-format=short'"
add_alias "$rc" "alias cw='cargo watch -x \"check --message-format=short\"'"
add_alias "$rc" "alias cbg='bacon'"
add_alias "$rc" "alias ct='cargo nextest run'"
add_alias "$rc" "alias cf='cargo fmt'"
add_alias "$rc" "alias cu='cargo update'"
add_alias "$rc" "alias cr='cargo run'"
done
# -------------------------------------------------------------------
# Python / uv aliases — the cargo-equivalent workflow
# -------------------------------------------------------------------
# pvenv - create a .venv virtualenv in the current project (uv venv)
# pd - sync/install the project's dependencies + env (uv sync)
# pa - add a dependency (uv add <pkg>), mirrors `cargo add`
# prm - remove a dependency (uv remove <pkg>), mirrors `cargo rm`
# pu - upgrade locked dependencies within constraints (uv lock --upgrade)
# pf - format code (ruff format)
# pl - lint (ruff check)
# pcx - lint with auto-fixes applied (ruff check --fix)
# pt - run the full test suite (uv run pytest)
# pw - run tests on every save (uv run ptw ., via pytest-watcher)
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
add_alias "$rc" "alias pvenv='uv venv'"
add_alias "$rc" "alias pd='uv sync'"
add_alias "$rc" "alias pa='uv add'"
add_alias "$rc" "alias prm='uv remove'"
add_alias "$rc" "alias pu='uv lock --upgrade'"
add_alias "$rc" "alias pf='ruff format .'"
add_alias "$rc" "alias pl='ruff check .'"
add_alias "$rc" "alias pcx='ruff check --fix .'"
add_alias "$rc" "alias pt='uv run pytest'"
add_alias "$rc" "alias pw='uv run ptw .'"
done
# y(): launch yazi, and if you cd'd somewhere inside it, land your shell
# there on quit (the standard wrapper recommended by yazi's own docs —
# without it, exiting yazi drops you back where you started).
add_yazi_function() {
local rc_file="$1"
local marker="# y(): launch yazi, cd to wherever you navigated to on quit"
if [[ -f "$rc_file" ]]; then
if grep -Fq "$marker" "$rc_file"; then
sed -i.bak '/^# y(): launch yazi/,/^}$/d' "$rc_file"
fi
echo "==> Adding y() yazi wrapper to $rc_file"
cat >> "$rc_file" <<'EOF'
# y(): launch yazi, cd to wherever you navigated to on quit
y() {
local tmp cwd
tmp="$(mktemp -t "yazi-cwd.XXXXXX")"
yazi "$@" --cwd-file="$tmp"
if cwd="$(cat -- "$tmp")" && [[ -n "$cwd" && "$cwd" != "$PWD" ]]; then
cd -- "$cwd"
fi
rm -f -- "$tmp"
}
EOF
fi
}
add_yazi_function "$HOME/.bashrc"
add_yazi_function "$HOME/.zshrc"
# rt(): fuzzy-pick a single test and run it with cargo-nextest.
# Uses `cargo test -- --list` as the source of test names (nextest's own
# `list` output is grouped/indented per binary and isn't a clean match
# string) — any argument passed to rt pre-fills the fzf search query,
# e.g. `rt any_type`.
add_function() {
local rc_file="$1"
local marker="# rt(): fuzzy-pick and run a single test"
if [[ -f "$rc_file" ]]; then
# Remove an older/broken version of the function if present, so
# re-running install.sh actually fixes it instead of leaving a
# stale duplicate.
if grep -Fq "$marker" "$rc_file"; then
sed -i.bak '/^# rt(): fuzzy-pick/,/^}$/d' "$rc_file"
fi
echo "==> Adding rt() test-picker function to $rc_file"
cat >> "$rc_file" <<'EOF'
# rt(): fuzzy-pick and run a single test (cargo-nextest + fzf)
rt() {
local test
test=$(cargo test --tests -- --list 2>/dev/null \
| grep ': test$' \
| sed 's/: test$//' \
| fzf --height 40% --query "$*") || return
cargo nextest run "$test"
}
EOF
fi
}
add_function "$HOME/.bashrc"
add_function "$HOME/.zshrc"
# ptk(): fuzzy-pick a single pytest test and run it (uv run pytest + fzf).
# Any argument pre-fills the fzf search query, e.g. `ptk my_test`.
add_ptk_function() {
local rc_file="$1"
local marker="# ptk(): fuzzy-pick and run a single test"
if [[ -f "$rc_file" ]]; then
if grep -Fq "$marker" "$rc_file"; then
sed -i.bak '/^# ptk(): fuzzy-pick/,/^}$/d' "$rc_file"
fi
echo "==> Adding ptk() test-picker function to $rc_file"
cat >> "$rc_file" <<'EOF'
# ptk(): fuzzy-pick and run a single pytest test (uv run pytest + fzf)
ptk() {
local test
test=$(uv run pytest --collect-only -q 2>/dev/null \
| grep '::' \
| fzf --height 40% --query "$*") || return
uv run pytest "$test"
}
EOF
fi
}
add_ptk_function "$HOME/.bashrc"
add_ptk_function "$HOME/.zshrc"
# pm(): run a Python entrypoint with the project's .venv interpreter.
# No args -> runs ./main.py if present. Otherwise pass the script (or
# `-m module`) to run, e.g. `pm src/app.py` or `pm -m mypkg.cli`.
add_pm_function() {
local rc_file="$1"
local marker="# pm(): run a Python entrypoint with the project .venv"
if [[ -f "$rc_file" ]]; then
if grep -Fq "$marker" "$rc_file"; then
sed -i.bak '/^# pm(): run a Python/,/^}$/d' "$rc_file"
fi
echo "==> Adding pm() entrypoint runner to $rc_file"
cat >> "$rc_file" <<'EOF'
# pm(): run a Python entrypoint with the project .venv
pm() {
local py=".venv/bin/python"
if [[ ! -x "$py" ]]; then
echo "pm: no .venv/bin/python here — run 'pvenv' first." >&2
return 1
fi
if [[ $# -eq 0 ]]; then
if [[ -f main.py ]]; then
"$py" main.py
else
echo "pm: no args and no ./main.py — pass a script, e.g. 'pm src/app.py'." >&2
return 1
fi
else
"$py" "$@"
fi
}
EOF
fi
}
add_pm_function "$HOME/.bashrc"
add_pm_function "$HOME/.zshrc"
echo ""
echo "==> Done. Run 'source ~/.bashrc' or 'source ~/.zshrc' to apply."