Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,80 @@ defaults, and so on. Tweak this script, and occasionally run `dot` from
time to time to keep your environment fresh and up-to-date. You can find
this script in `bin/`.

## tmux-sessionizer

`tmux-sessionizer` is a project-picker that lets you jump into any project as a
dedicated tmux session with one keystroke. When tmuxinator is installed it
automatically opens three windows — **nvim** at the project root, and two
plain **terminal** windows — so you land in a ready-to-code environment.

### Prerequisites

| Tool | Required? | Install |
|------|-----------|---------|
| [tmux](https://github.com/tmux/tmux) | **yes** | `brew install tmux` |
| [fzf](https://github.com/junegunn/fzf) | **yes** | `brew install fzf` |
| [fd](https://github.com/sharkdp/fd) | recommended | `brew install fd` |
| [tmuxinator](https://github.com/tmuxinator/tmuxinator) | optional (richer layout) | `brew install tmuxinator` |
| [Neovim](https://neovim.io) | optional (used in window 1) | `brew install neovim` |

### Project directories

By default `tmux-sessionizer` searches **one level deep** inside these two
folders:

```
~/dev/
~/Code/
```

Create either (or both) directories and put your projects inside them:

```sh
mkdir -p ~/dev
git clone [email protected]:you/my-project.git ~/dev/my-project
```

### How to invoke it

| Where | Keys | What happens |
|-------|------|--------------|
| Inside **tmux** | **Alt + F** | Opens an fzf picker in a floating popup |
| Any **shell prompt** | **Ctrl + G** | Runs the picker inline in your terminal |
| **Command line** | `tmux-sessionizer` | Same as above (it's on your `$PATH`) |
| **Pass a path directly** | `tmux-sessionizer ~/dev/my-project` | Skips the picker and opens that project |

### What happens when you pick a project

1. If a tmux session named after the project already exists you are
switched/attached to it immediately — nothing else changes.
2. **With tmuxinator installed** a fresh session is created with three windows:
- `nvim` — starts `nvim .` at the project root
- `terminal` — plain shell at the project root
- `terminal2` — plain shell at the project root
3. **Without tmuxinator** a single-window session is created and `nvim` is
launched automatically.

### Per-project customisation

Drop a `.tmux-sessionizer` shell script in the root of any project (or in
`$HOME` for a global default). The script is sourced inside the new session
after it is created, so you can run extra setup steps:

```sh
# ~/dev/my-project/.tmux-sessionizer
tmux send-keys -t my-project "docker compose up -d" C-m
```

### Window layout reference

```
Session: my-project
0: nvim ← nvim . (opened at project root)
1: terminal ← plain shell
2: terminal2 ← plain shell
```

## bugs

I want this to work for everyone; that means when you clone it down it should
Expand Down
64 changes: 34 additions & 30 deletions bin/tmux-sessionizer
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,37 @@ hydrate() {
fi
}

run_tmuxinator_if_available() {
find_tmuxinator() {
if [ -x "/opt/homebrew/bin/tmuxinator" ]; then
echo "/opt/homebrew/bin/tmuxinator"
elif [ -x "/usr/local/bin/tmuxinator" ]; then
echo "/usr/local/bin/tmuxinator"
elif command -v tmuxinator >/dev/null 2>&1; then
command -v tmuxinator
fi
}

run_with_tmuxinator() {
local selected_path="$1"
local selected_basename
selected_basename="$(basename "$selected_path")"
local selected_name="$2"

local dotfiles_root
dotfiles_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
local tmuxinator_project="$dotfiles_root/tmux/simplepractice.yml"
local tmuxinator_template="$dotfiles_root/tmux/template.yml"

if [[ "$selected_basename" != "simplepractice" ]]; then
if [ ! -f "$tmuxinator_template" ]; then
return 1
fi

if [ ! -f "$tmuxinator_project" ]; then
return 1
fi

local tmuxinator_cmd=""
if [ -x "/opt/homebrew/bin/tmuxinator" ]; then
tmuxinator_cmd="/opt/homebrew/bin/tmuxinator"
elif [ -x "/usr/local/bin/tmuxinator" ]; then
tmuxinator_cmd="/usr/local/bin/tmuxinator"
elif command -v tmuxinator >/dev/null 2>&1; then
tmuxinator_cmd="$(command -v tmuxinator)"
else
local tmuxinator_cmd
tmuxinator_cmd="$(find_tmuxinator)"
if [ -z "$tmuxinator_cmd" ]; then
return 1
fi

if "$tmuxinator_cmd" start -p "$tmuxinator_project"; then
return 0
fi

return 1
TMUXINATOR_PROJECT_NAME="$selected_name" \
TMUXINATOR_PROJECT_ROOT="$selected_path" \
"$tmuxinator_cmd" start -p "$tmuxinator_template"
}

pick_directory() {
Expand Down Expand Up @@ -96,24 +94,30 @@ if [[ -z $selected ]]; then
exit 0
fi

if run_tmuxinator_if_available "$selected"; then
selected_name=$(basename "$selected" | tr . _)
tmux_running=$(pgrep tmux)

# If the session already exists, just switch to it.
if has_session "$selected_name"; then
switch_to "$selected_name"
exit 0
fi

selected_name=$(basename "$selected" | tr . _)
tmux_running=$(pgrep tmux)
# New session: try tmuxinator first for a full window layout.
if run_with_tmuxinator "$selected" "$selected_name"; then
exit 0
fi

# Fallback: create a plain session with nvim open in the first window.
if [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then
tmux new-session -s "$selected_name" -c "$selected"
tmux send-keys -t "$selected_name" "nvim" C-m
hydrate "$selected_name" "$selected"
exit 0
fi

if ! has_session "$selected_name"; then
tmux new-session -ds "$selected_name" -c "$selected"
tmux send-keys -t "$selected_name" "nvim" C-m
hydrate "$selected_name" "$selected"
fi
tmux new-session -ds "$selected_name" -c "$selected"
tmux send-keys -t "$selected_name" "nvim" C-m
hydrate "$selected_name" "$selected"

switch_to "$selected_name"
12 changes: 12 additions & 0 deletions tmux/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generic tmuxinator template used by tmux-sessionizer.
# The project name and root are injected via environment variables
# TMUXINATOR_PROJECT_NAME and TMUXINATOR_PROJECT_ROOT.
name: <%= ENV.fetch("TMUXINATOR_PROJECT_NAME", "project") %>
root: <%= ENV.fetch("TMUXINATOR_PROJECT_ROOT", "~") %>

windows:
- nvim:
panes:
- nvim .
- terminal: ""
- terminal2: ""
4 changes: 4 additions & 0 deletions tmux/tmux.conf.symlink
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@ bind-key -n M-0 select-window -t 0

# Clear pane (screen + scrollback) like Cmd+K
bind-key -n M-k send-keys -R "clear" \; send-keys Enter \; run-shell "tmux clear-history"

# Open project picker (tmux-sessionizer) in a floating popup.
# Uses Alt+F to match the existing Alt-based window navigation pattern.
bind-key -n M-f display-popup -E "tmux-sessionizer"
4 changes: 4 additions & 0 deletions zsh/config.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ bindkey '^[[5C' end-of-line
bindkey '^[[3~' delete-char
bindkey '^?' backward-delete-char

# Open project picker (tmux-sessionizer) with Ctrl+G from any shell prompt.
# Ctrl+G avoids conflicts with zsh navigation bindings (^F is forward-char).
bindkey -s '^g' 'tmux-sessionizer\n'

function y() {
local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd
yazi "$@" --cwd-file="$tmp"
Expand Down
Loading