| -- Copy file path / selection reference for pasting into AI chats | | | local function copy_ref(opts) | | | -- "%" is the current buffer's file name; ":." makes it relative to the cwd | | | local path = vim.fn.expand("%:.") | | | -- ref is what ends up in the clipboard; start with just the path | | | local ref = path | | | if opts.visual then | | | -- '< and '> are only set after leaving visual mode, so read the live selection: | |
| -- "v" is the line where visual mode was started (the anchor) | |
| local start_line = vim.fn.line("v") | |
| -- "." is the line the cursor is on now (the moving end of the selection) | |
| local end_line = vim.fn.line(".") | |
| -- if the selection was made upward, swap so start is always the smaller line | | | if start_line > end_line then | | | start_line, end_line = end_line, start_line | | | end | |
| -- append the range, e.g. "lua/config/keymaps.lua:1:23" | |
| ref = path .. ":" .. start_line .. ":" .. end_line | |
| end | |
| -- ask for an optional free-text note on the command line (Enter to skip) | |
| local note = vim.fn.input("Prompt (optional): ") | |
| if note ~= "" then | | | -- append the note after the ref, separated by a space | | | ref = ref .. " " .. note | | | end | | | -- write ref into the "+" register, which is the system clipboard | | | vim.fn.setreg("+", ref) | | | -- show a confirmation message with what was copied | | | vim.notify("Copied: " .. ref) | | | end | |
| -- normal mode: copy just the file path | |
| vim.keymap.set("n", "<leader>cp", function() | |
| copy_ref({}) | |
| end, { desc = "Copy file path" }) | |
| -- visual mode: copy the file path plus the selected line range | |
| vim.keymap.set("v", "<leader>cp", function() | |
| copy_ref({ visual = true }) | |
| end, { desc = "Copy file path with line range" }) |
| #!/usr/bin/env bash | | | set -euo pipefail | | | PROJECTS_DIR="$HOME/Projects" | | | # Menu lines: display_label<TAB>session_name<TAB>path<TAB>type | | | menu="" | | | # 1. Existing tmux sessions (skip worktree sessions, handled in section 3) | |
| if tmux ls &>/dev/null; then | |
| while IFS= read -r sess; do | |
| [[ "$sess" == *"("* ]] && continue | |
| menu+="[switch] ${sess}"$'\t'"${sess}"$'\t'"$sess"$'\t'"switch"$'\n' | |
| done < <(tmux list-sessions -F '#{session_name}') | |
| fi | | | # 2. Top-level projects not already a session | | | for dir in "$PROJECTS_DIR"/*/; do | |
| [ -d "$dir" ] || continue | |
| name=$(basename "$dir") | |
| tmux has-session -t "$name" 2>/dev/null && continue | |
| menu+="[new] ${name}"$'\t'"${name}"$'\t'"${dir}"$'\t'"new"$'\n' | |
| done | | | # 3. Worktrees inside each project's .worktrees/ | | | for dir in "$PROJECTS_DIR"/*/; do | |
| { [ -d "$dir/.git" ] || [ -f "$dir/.git" ]; } || continue | |
| (cd "$dir" && git worktree prune 2>/dev/null) || true | |
| wt_dir="${dir}.worktrees" | |
| [ -d "$wt_dir" ] || continue | |
| project_name=$(basename "$dir") | |
| for branch_dir in "$wt_dir"/*/; do | |
| [ -d "$branch_dir" ] || continue | |
| branch_name=$(basename "$branch_dir") | |
| sess_name="${project_name}(${branch_name})" | |
| if tmux has-session -t "$sess_name" 2>/dev/null; then | |
| label="[switch] ${project_name} (${branch_name})" | |
| type="switch" | | | else | | | label="[new] ${project_name} (${branch_name}) [worktree]" | | | type="new" | | | fi | | | menu+="${label}"$'\t'"${sess_name}"$'\t'"${branch_dir}"$'\t'"${type}"$'\n' | | | done | | | done | |
| [ -z "$menu" ] && exit 0 | |
| selection=$(printf '%s' "$menu" | LC_ALL=C sort -t$'\t' -k4,4r -k1,1 | fzf --height 100% --delimiter=$'\t' --with-nth=1) | |
| [ -z "$selection" ] && exit 0 | |
| sess=$(printf '%s' "$selection" | cut -f2) | |
| path=$(printf '%s' "$selection" | cut -f3) | |
| type=$(printf '%s' "$selection" | cut -f4) | |
| if [ "$type" = "switch" ]; then | |
| if [ -z "${TMUX:-}" ]; then | |
| tmux attach -t "$sess" | | | else | | | tmux switch-client -t "$sess" | | | fi | | | else | |
| if [ -z "${TMUX:-}" ]; then | |
| tmux new-session -s "$sess" -c "$path" | |
| else | |
| tmux new-session -d -s "$sess" -c "$path" | |
| tmux switch-client -t "$sess" | |
| fi | | | fi |
| #!/usr/bin/env bash | | | set -euo pipefail | | | PROJECTS_DIR="$HOME/Projects" | |
| project_dir=$(find "$PROJECTS_DIR" -mindepth 1 -maxdepth 1 -type d | fzf --height 100% --prompt="Project> ") | |
| [ -z "$project_dir" ] && exit 0 | |
| project_name=$(basename "$project_dir") | |
| cd "$project_dir" | | | if [ ! -d .git ] && [ ! -f .git ]; then | | | echo "Not a git repo: $project_dir" | | | read -r -p "Press enter to close..." | | | exit 1 | | | fi | | | git worktree prune 2>/dev/null || true | |
| branches=$(git branch --format='%(refname:short)') | |
| selection=$(printf '%s\n[new branch]\n' "$branches" | fzf --height 100% --prompt="Branch> ") | |
| [ -z "$selection" ] && exit 0 | |
| if [ "$selection" = "[new branch]" ]; then | |
| read -r -p "New branch name: " branch | |
| [ -z "$branch" ] && exit 0 | |
| is_new=true | | | else | | | branch="$selection" | | | is_new=false | | | fi | | | wt_root="$project_dir/.worktrees" | | | mkdir -p "$wt_root" | | | if ! grep -qxF '.worktrees/' .gitignore 2>/dev/null; then | | | echo '.worktrees/' >> .gitignore | | | fi | | | wt_path="$wt_root/$branch" | |
| existing_path=$(git worktree list --porcelain | awk -v b="refs/heads/$branch" ' | |
| /^worktree / { path=$2 } | |
| $0 == "branch " b { print path } | |
| ') | | | if [ -n "$existing_path" ]; then | | | wt_path="$existing_path" | | | elif [ -d "$wt_path" ]; then | | | echo "Directory exists but isn't a registered worktree: $wt_path" | | | read -r -p "Press enter to close..." | | | exit 1 | | | else | | | if [ "$is_new" = true ]; then | | | git worktree add "$wt_path" -b "$branch" | | | else | | | git worktree add "$wt_path" "$branch" | | | fi | | | fi | |
| sanitized_branch=$(echo "$branch" | tr '.:/ ' '----') | |
| sess_name="${project_name}(${sanitized_branch})" | |
| tmux has-session -t "$sess_name" 2>/dev/null || tmux new-session -d -s "$sess_name" -c "$wt_path" | |
| if [ -z "${TMUX:-}" ]; then | |
| tmux attach -t "$sess_name" | | | else | | | tmux switch-client -t "$sess_name" | | | fi |