Hi, great you're here. If you like, make use of what I have put together here. For me, coming from pure bash and vim, this was like a change from horse riding to spaceship flying. 😌 I hope this is also helpful for your daily work and passion.
To get you started easily with nvim, here in particular with LazyVim, make use of two awesome features provided by the LazyVim distribution used here:
<leader>: Open a pop up to see the next best key to press.<leader>sk: Open a search for keymappings.- C+s for saving changes in a buffer. Yes, you did not expect that in a Vim-style editor ;-)
What does <leader> mean? This is the key to start a command. It is configured
here to be the <space> key. Thus to search for keymappings, type <space>sk.
The fact this setup works for me does not mean it works for you, too. Dotfiles are highly individual.
If you use MacPorts, this here won't work. Sorry. It uses Homebrew (which will be installed in case you haven't before).
Don't install rustup via Homebrew. It will be installed via the contained
install script, to have proper use of Cargo. This is the one deliberate
exception to installing global tools with Homebrew. Run ./install --skip-rust
to skip it.
You might run into problems with stow, in case some dotfiles already exist in
your home directory. Best is to move them into some backup place, before you run
./install. A typical example is a ~/.zprofile or ~/.zshenv created by the
Homebrew or Rust installers: their content is already part of the zsh package.
For a detailed look on what you will install, please have a look into
- the script
./install - the
Brewfile - the mise config
dotfiles/mise/.config/mise/config.toml
On a high level after the install you will have
- NeoVim, highly configured to be a fully fledged IDE
- Ghostty, a highly modern TTY
- zsh as login shell, with vi mode, autosuggestions, syntax highlighting and carapace completions
- Nushell, a modern shell for structured data, for occasional use: run
nufrom zsh - Starship, a shell prompt on steorids
- tmux, a massive productivity booster when you are working on multiple projects
- mise, managing language runtimes (Node, Ruby, Java, Erlang, Elixir)
- Rustup and Rust, to inspire you to code in Rust ;-)
- and more basic stuff that you need
- Please note, a dotfiles config is highly individual. Use this for creating your own dotfiles repository.
Open your shell and run ./install There is no guarantee that this runs
through, but, as I want it to work, it is likely...
The install script also installs the tool stow. It is used for symlinking the
dotfile folders in this repository into your homefolder. In case you need to run
the command once manually, do it as follows:
cd <some_parent_folder>/shell_setup/dotfiles
stow --target="$HOME" <package>So let's assume, you have cloned the repository into a folder like
~/projects/shell_setup and you wish to symlink the package ghostty:
cd ~/projects/shell_setup/dotfiles
ls
# output:
# carapace colima ghostty lazygit mise nushell nvim starship tmux zellij zk zsh
# now run:
stow --target="$HOME" ghosttyls -la ~/.config/ghostty
# output:
# lrwxr-xr-x 1 paschelino staff 50 Mar 22 10:00 /Users/paschelino/.config/ghostty -> ../projects/shell_setup/dotfiles/ghostty/.config/ghosttyThe interactive shell is zsh in vi mode: Esc switches to normal mode (block
cursor), v in normal mode opens the command line in nvim. Further helpers:
Ctrl-r: fuzzy history search,Ctrl-t: insert a file path,Alt-c: cd into a subdirectory (all via fzf)→: accept the grey autosuggestion from your historywt <branch>: create a git worktree and open it in a new tmux window or Zellij tab;wtalone selects an existing one (see Git worktrees)
wt is a zsh function (in .zshrc) on top of
git-wt, which makes it easy to work on
several branches of one repository side by side, each in its own directory and
tmux window.
| Command | What it does |
|---|---|
wt <branch> |
Create the worktree (and the branch, if needed) and open it in a new tmux window or Zellij tab named <repo>/<branch>; outside both, cd into it |
wt |
Pick one of the other worktrees with fzf and cd into it |
wt -d [<branch>] |
Delete the worktree; an unmerged branch is kept. Without a branch, the current one is deleted |
wt -D [<branch>] |
Force delete: also removes the branch, even if unmerged |
wt-rm, wt-rm! |
Aliases for wt --delete and wt --force |
Tab completion offers your local branches as well as the flags.
git-wt, fzf and jq come with the Brewfile. ./install does not set the
git config wt relies on, so run this once:
# put worktrees next to the repository, e.g. ~/projects/shup-worktrees/<branch>
git config --global wt.basedir "../{gitroot}-worktrees"
# copy ignored files such as .env or mise.local.toml into new worktrees
git config --global wt.copyignored trueFor full developer productivity, I advise you to remap your CAPS_LOCK key to CTRL, short C. Then it is super simple to use the according key mappings and for example to press C+a,w to open the window selector. It feels a little bit like emacs, as every command you will be using will be preceded with C+a.
Zellij is a modern alternative to tmux. Its config
(dotfiles/zellij/.config/zellij/config.kdl) drops Zellij's default keys, which
clash with nvim (e.g. Ctrl-s to save), and copies the tmux bindings instead.
Everything starts with the prefix Ctrl-a:
Keys after Ctrl-a |
What it does |
|---|---|
h j k l |
Move to the pane on the left, below, above, right |
H J K L |
Resize; keep pressing h j k l, Esc to stop |
| or %, - or " |
Split right, split down |
z, x, f |
Toggle fullscreen, close pane, toggle floating panes |
c, ,, & |
New tab, rename tab, close tab |
Ctrl-h / Ctrl-l, 1…9, Tab |
Previous / next tab, go to tab, last tab |
[, / |
Scroll mode (vi keys), search |
e |
Open the scrollback in nvim, to search and copy with vi |
s, d, q |
Session manager, detach, quit |
Ctrl-a |
Send Ctrl-a to the program |
Selecting text with the mouse copies it. The config reloads on save.
The full cheat sheet lives in dotfiles/zellij/.config/zellij/cheatsheet.md:
zp -c (--cheat) opens it in nvim, zp -cb (--cheat --browser) renders it
in the browser.
zp <project> opens one Zellij session per project with three tabs: nvim, a
coding agent and a shell (the layout in layouts/project.kdl). If the session
exists, it attaches to it; inside Zellij it switches to it. zp alone picks a
project with fzf. zp -a (--all) starts the sessions of all projects in the
background, e.g. after a reboot, and then attaches to the first one. Sessions
that are already running are left alone.
The projects differ from machine to machine and stay private, so they're not
part of this repository. List them in ~/.config/zp/projects, one per line:
name, path and optionally the agent (default: opencode):
# name path agent
shup ~/shup opencode
my-api ~/work/my-api claude
A project that needs different tabs gets its own layout:
~/.config/zp/<name>.kdl is used instead of the generic one. Start from
dotfiles/zellij/.config/zellij/layouts/project.kdl.
It will take you some time to become familiar with nvim. Two things to ease things for you:
- When you are inside a buffer in normal mode, you can hit the SPACE bar. It then expects to type in shortcuts to execute the needed commands. But after short time, it opens a pop up with the available keymappings, so that you can learn them slowly.
- When you are in edit mode in a buffer and want to get back to normal mode,
instead of hitting ESC, you can type short after another
jk. This will enter your muscle memory very quickly and you will never want to miss it again. - When you want to know all capabilities available, it is best to look into the
~/.config/nvimfolder and to browse the plugin configs. Each of the plugin configs relates to a GitHub project where you can find more details on how to use those. Tools you might love the most:
- LazyGit (hit SPACE+lg)
- Toggle file explorer (hit SPACE+ee to open or SPACE+et to close it.)
- Opening or closing folders in the file explorer: Hit enter to open, hit backspace to close. Or use double clicks with the mouse :-D
- Open the key bindings for the file explorer: When in the file explorer hit
g?
Both agents share one set of global instructions, which apply in every project
next to the project's own AGENTS.md or CLAUDE.md:
dotfiles/opencode/.config/opencode/AGENTS.mdholds the general instructions and is read by opencode.dotfiles/opencode/.config/opencode/instructions/*.mdhold instructions about this very setup, e.g. how to use thewtworktree helper. opencode loads them viainstructionsinopencode.jsonc.dotfiles/claude/.claude/CLAUDE.mdonly imports those files with@~/...lines, as Claude Code doesn't readAGENTS.md. Unlike opencode it can't import a glob, so add a line there for each new file ininstructions/.
Edit those files to change the instructions for both.
~/.claude and ~/.config/opencode also hold the tools' runtime data
(sessions, history, plugins, node_modules). So ./install stows these two
packages with --no-folding: stow then links single files and never symlinks
the whole directory into this repository. When stowing them manually, pass the
flag as well:
stow --no-folding --target="$HOME" claude opencodeTwo package managers, with a clear split:
- Homebrew (
Brewfile) installs everything global: CLIs, libraries, apps and fonts. Prefer a homebrew-core formula, even for npm or PyPI tools: every release is reviewed, its sources are pinned by checksum and its bottles carry build provenance attestations, which.zprofileverifies (HOMEBREW_VERIFY_ATTESTATIONS; needsghto be logged in, seegh auth login). Add a third-party tap only when it is the vendor's own, and trust its formula in thebrew trust --formulaline of./install: Homebrew refuses to load formulae from untrusted taps. - mise (
dotfiles/mise/.config/mise/config.toml) installs language runtimes only (node = "26",ruby = "3.4", ...) and the per-project versions in a project'smise.tomlor.tool-versions;.nvmrcand.ruby-versionfiles are honoured as well. Stick to thecoreandaquabackends: the npm, pipx, cargo and http backends install unreviewed releases, and asdf/vfox plugins (disabled) run community shell scripts.
mise runs in paranoid mode, so run mise trust in a project before mise uses
its config, and again after the config changes. Versions and checksums of the
global runtimes are pinned in mise.lock: after editing the config, run
mise lock --global --bump and mise install, and commit both files.