Skip to content

Latest commit

 

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hx + alacritty + zellij dotfiles

A fast, light-themed dev setup for Python, Rust, Zig, C++, and C, with an LLM CLI tab.

Screenshots

dev layout:

dev

files tab:

files

git blame:

git_blame

rt (run test):

rt

dotfiles/
├── install.sh              # installs everything + symlinks configs
├── uninstall.sh            # reverses install.sh (symlinks, rc edits, git config)
├── helix/
│   ├── config.toml         # editor look & feel (Catppuccin Latte)
│   └── languages.toml      # LSPs, formatters, debug adapters per language
├── alacritty/
│   └── alacritty.toml      # terminal appearance, maximized, opens into zellij
├── zed/
│   └── settings.json       # One Light, JetBrains keymap, autosave-as-you-type, LSP tweaks
├── zellij/
│   ├── config.kdl          # keybindings, theme, mouse behavior
│   └── layouts/
│       ├── dev.kdl         # files (yazi) + editor (hx) + console + llms + git
│       ├── rsdev.kdl       # same as dev, no console tab; 4-console Rust ops tab
│       └── pydev.kdl       # same as dev, no console tab; 4-console Python ops tab
├── lazygit/
│   └── config.yml          # difftastic as the diff renderer
├── screenshots/            # README images
└── scripts/
    ├── llm.sh              # picks the LLM CLI per machine, installed as `llm`
    ├── mdp.sh              # live markdown preview (glow + entr), installed as `mdp`
    └── doctor.sh           # health check, installed as `doctor`

What each language gets

Language LSP Formatter / linter Debugger (DAP)
Python pylsp (jedi, mypy, rope) ruff (format + lint) — (add debugpy if you need it)
Rust rust-analyzer rustfmt, clippy on save lldb-dap
Zig zls zig fmt — (zls doesn't ship DAP support yet)
C++ clangd clang-tidy (via clangd) lldb-dap
C clangd clang-tidy (via clangd) lldb-dap

Debug from Helix with :debug-start (or bind a key to it) once a binary exists — it'll ask for the path to the compiled binary using the templates in languages.toml.

rust-analyzer shows inlay type hints (binding modes, elided lifetimes, closure return types, full function signatures) — tune them under language-server.rust-analyzer.config in languages.toml.

Do you need per-project setup?

Mostly no — the global config handles the LSPs/formatters automatically:

  • Rust: nothing extra. cargo new gives rust-analyzer everything it needs.

  • Zig: works as soon as zls is on your PATH.

  • Python: in Zed, basedpyright and the Debugpy debugger use the .venv Zed detects in the project automatically (toolchain picker in the status bar if there are several). In Helix, start hx from an activated venv (or uv run hx) so jedi resolves third-party imports, and to let mypy see them too add a project-local .helix/languages.toml:

    [language-server.pylsp.config.pylsp.plugins.pylsp_mypy]
    overrides = ["--python-executable", ".venv/bin/python", true]
  • C / C++: this is the one exception — clangd needs a compile_commands.json per project to know your include paths and flags. bear is installed for exactly this: run bear -- make, or with CMake add -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. With it, clangd also auto-inserts missing #includes (--header-insertion=iwyu); without it, that and accurate completion/diagnostics for anything outside the standard library are lost.

Install

git clone <this repo> ~/dotfiles   # or unzip it there
cd ~/dotfiles
chmod +x install.sh
./install.sh

Detects macOS (Homebrew), Debian/Ubuntu (apt), or Arch (pacman); installs Helix/Alacritty/Zellij, Zed (GUI editor, with its zed CLI on PATH), each language's toolchain/LSP, lldb-dap, cargo-watch, glow, entr, bear, difftastic, mergiraf, and resvg (SVG rasterizer); symlinks everything into ~/.config/, puts ~/.local/bin on your PATH and drops the mdp/llm/doctor scripts there.

It also installs bash-completion (bash-completion@2 on Homebrew, bash-completion on apt/pacman) and sources it from ~/.bashrc. Without it bash only tab-completes filenames; with it you get make/gmake targets, ssh hosts (from ~/.ssh/config and known_hosts), git subcommands and branches, and so on.

Notes on packages it can't get from apt directly:

  • Helix on Ubuntu — not in the default repos, so the script prefers the official snap (current releases), falling back to the maveonair PPA (ppa:maveonair/helix-editor) when snapd isn't installed. On non-Ubuntu Debian it points you at the release tarballs on GitHub.
  • Zed — no apt package; installed with Zed's official installer (https://zed.dev/install.sh, lands in ~/.local/zed.app with the CLI linked to ~/.local/bin/zed). On macOS it's the zed Homebrew cask; if Zed.app was already downloaded by hand, the script just links its CLI to ~/.local/bin/zed.
  • glow — installed from Charm's apt repo.
  • eza, gh, lazygit, starship — installed from their official apt repos / release tarballs as needed.

Zed

zed . from a project root opens the whole tree as a workspace. The settings in zed/settings.json are symlinked to ~/.config/zed/settings.json (your previous file is kept as settings.json.bak) and set:

  • One Light theme, JetBrainsMono Nerd Font, 100-column guide, no relative line numbers — the same look as the Helix/Alacritty setup.
  • JetBrains keymap plus IntelliJ-style autosave: the buffer is written 0.5 s after you stop typing and on focus loss, so there is no "unsaved" state to think about. Formatting (rustfmt/ruff) only runs on an explicit Cmd-S so autosave never rewrites what you are typing.
  • rust-analyzer runs clippy on check. Python is set up to feel like PyCharm: basedpyright (Zed downloads it) for go-to-definition, find usages, rename, auto-import completions, inlay type hints and type checking at PyCharm's "standard" strictness, plus ruff for lint, import sorting and formatting. Helix keeps pylsp instead.
  • Run / debug like PyCharm: zed/tasks.json holds run configurations (run file or module with uv run, pytest for the file, the whole suite or the test under the cursor, ruff), zed/debug.json the matching Debugpy launch/attach configurations (adapter downloaded by Zed on first use), and tests / __main__ blocks get gutter run icons. zed/keymap.json adds the macOS PyCharm keys on top of the JetBrains keymap: Ctrl-R run last, Ctrl-Alt-R pick a run config, Ctrl-D debug last, Ctrl-Alt-D pick a debug config, Cmd-Shift-B type definition, Cmd-U declaration, Cmd-F12 file structure, Cmd-O symbol, Cmd-P parameter info, F1 quick docs. Shift-F10 / Shift-F9 / Alt-T from the JetBrains keymap keep working.
  • New integrated terminals start with the project's .venv activated.
  • Inline git blame on the current line.
  • Extensions auto-installed on first launch: zig, toml, kdl, just, dockerfile, make, sql, env, log, csv.
  • Privacy: telemetry off and edit prediction disabled (it would stream buffer contents to Zed's servers).

On Ubuntu the script also installs libvulkan1 + mesa-vulkan-drivers: Zed renders through Vulkan, and the Mesa package provides a software fallback so it still starts in VMs without a GPU driver.

Doctor

install.sh symlinks scripts/doctor.sh onto your PATH as doctor. It verifies the setup is still intact — binaries present, config symlinks pointing back into this repo, git diff/merge config wired, and the aliases/ functions present in your shell rc — and prints a PASS/FAIL per check, exiting non-zero if anything is missing. Run it any time to catch drift after a package-manager update:

doctor

Uninstall

uninstall.sh reverses everything install.sh did: removes the config symlinks, strips the aliases/functions it appended to ~/.bashrc/~/.zshrc, and reverts the git config it touched. It only removes entries it recognizes, so your own additions are left alone:

cd ~/dotfiles
./uninstall.sh

Day-to-day use

cd ~/some-project
dev

dev is an alias (zellij attach --create dev options --default-layout dev) added to your shell rc by install.sh. It attaches to a session named dev if one is already running, and only creates it (with the dev layout) when it isn't — so your panes, running builds, and the llms tab pick up right where you left off, and closing the terminal never leaks a fresh randomly-named session. Note that the layout is applied only on creation and the session is shared across directories: run kill_devs first if you want a clean dev session in a different project.

kill_devs force-deletes the dev, rsdev and pydev sessions and then sweeps any exited sessions, so zellij list-sessions stays short.

If you'd rather launch straight from your desktop instead of an existing terminal, open Alacritty (maximized, light Catppuccin Latte colors) and run dev inside it — same result.

For Rust projects, use rsdev instead — it's the same wrapper but opens the rsdev layout. There's no console tab; instead the ops tab is a 2×2 grid of four consoles — single test (rt), run (cr), full test suite (ct/cargo test), and clippy (ccl) — all run manually.

For Python projects, use pydev — the same idea, but its ops tab is a 2×2 grid of four consoles: single test (ptk), run (pm), full test suite (pt), and lint (pl).

In both layouts the editor tab is just Helix (no console), like dev.

Useful commands

These aliases and functions are added to ~/.bashrc / ~/.zshrc by install.sh:

Command What it does
dev Open a Zellij session with the dev layout (named after the current folder)
rsdev Same, but with the rsdev layout (adds an ops tab)
pydev Same, but with the pydev layout (adds a Python ops tab)
y Launch yazi; on quit, cd to wherever you navigated to
mdp Live markdown preview (glow + entr)

Rust / cargo aliases:

Command What it does
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
cbg bacon — live, always-on compact diagnostics panel
ct Run the full test suite via cargo nextest run
cf cargo fmt
cu cargo update (bump Cargo.lock within semver constraints)
cr cargo run (build + run the project binary), mirrors pm
rt Fuzzy-pick and run a single Rust test (cargo-nextest + fzf)

Python aliases (the cargo-equivalent workflow). They work in Poetry and uv projects alike: prun (scripts/prun.sh, on PATH) looks for poetry.lock / [tool.poetry] upward from the current directory and runs poetry run, otherwise uv run; the functions below dispatch the same way.

Command What it does
pvenv Create the project env (poetry env use python3 / uv venv)
pd Install/sync the project's deps + env (poetry install / uv sync)
pa Add a dependency (poetry add / uv add <pkg>)
prm Remove a dependency (poetry remove / uv remove <pkg>)
pu Upgrade locked deps (poetry update / uv lock --upgrade)
prun Run any command in the project env (poetry run / uv run)
pf Format code (ruff format .)
pl Lint (ruff check .)
pcx Lint with auto-fixes (ruff check --fix .)
pt Run pytest: whole suite, or whatever args you pass (prun pytest). Drawings show by default; export PT_DIS_VIS=1 adds --dis-vis in repos whose conftest defines it
pw Run tests on every save (prun ptw ., via pytest-watcher)
ptk Fuzzy-pick test(s) with fzf and run them (ptk login pre-fills the query, Tab multi-selects, ptk -c lists parametrized ids)
ptf Run all tests in one file by name (ptf logintests/test_login.py; ambiguous or no name opens an fzf file picker)
pm Run a Python entrypoint via .venv/bin/python (defaults to main.py)

ptk lists test node ids (tests/test_x.py::TestA::test_b) from a static scan of the test_*.py / *_test.py files, which takes ~0.2s where pytest --collect-only takes 10-30s on a big suite (it imports every test module and conftest). The scan does not expand names generated at runtime: ptk -c switches to real pytest collection when you want to pick a single parametrized case, and if a picked id turns out not to exist (e.g. parameterized renames the method) ptk re-runs the picks as pytest file -k name. Running the tests needs pytest in the project's dev-dependencies: poetry add --group dev pytest or uv add --dev pytest (add pytest-watcher too for pw). The Zed run configurations in zed/tasks.json go through the same prun.

Git diffs, merges & conflict resolution

Three tools make reviewing and merging code less tedious, wired up by install.sh:

Tool Role
difftastic syntax-aware (structural) diffs — git diff and lazygit
mergiraf syntax-aware merge driver — auto-resolves merge/rebase/cherry-pick conflicts
rerere (git built-in) records how you resolved a conflict and replays it next time
  • difftastic is set as git's external diff (diff.external), so plain git diff shows structural diffs. delta stays on as the pager for git log/show/blame and git add -p. lazygit renders its diff pane through difftastic too.
  • mergiraf is registered as the mergiraf merge driver and applied to all files via the global gitattributes (~/.config/git/attributes); diff3 conflict style is enabled so it can reconstruct all three sides. When it auto-resolves a conflict it asks you to review with mergiraf review <id>.
  • rerere is enabled (rerere.enabled), so conflicts you've solved once are re-applied automatically the next time they show up.
Command What it does
git diff structural diff via difftastic (opt out with --no-ext-diff)
git dlog git log -p with difftastic
git dshow git show with difftastic
mergiraf review <id> review a conflict mergiraf auto-resolved
mergiraf solve <f> attempt to auto-resolve an existing conflicted file

Inside the session (dev):

  • Tab 1 — files: launches y (yazi) automatically — a terminal file manager for browsing, previewing, and editing files without leaving the session.
  • Tab 2 — editor: Helix open on ., full pane — no shell underneath.
  • Tab 3 — console: a scratch shell for builds and ad-hoc commands.
  • Tab 4 — llms: launches llm (scripts/llm.sh), which picks whichever LLM CLI this machine has — claude or opencode, in that order. Set export LLM_CLI=<cmd> in your shell rc to force a specific one, so the shared layouts stay identical across machines.
  • Tab 5 — git: lazygit, for status/diff/stage/commit without leaving the session.

In rsdev/pydev there's no console tab: the editor tab is just Helix, and the ops tab (a 2×2 grid of four consoles — single test, run, full test suite, clippy/lint) sits between editor and llms.

Helix keybindings

Line numbers are absolute (1-based from the top of the file, not relative to the cursor) — see line-number in helix/config.toml. Extra bindings added on top of the Helix defaults (in helix/config.toml):

Key What it does
Ctrl-s Save (:w)
Ctrl-q Quit (:q)
space Picker menu (space → file picker, space b → buffer picker, w → save, q → quit)
space B Git blame the current line — prints the commit/author that last touched it to the statusline
m Render the current markdown file through glow
Ctrl-e Open the current file in VS Code at the cursor line (code -g)

Note: Ctrl-e runs in the foreground, so Helix suspends until you close VS Code.

About

dot files

Topics

Resources

Stars

26 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages