From 280f7799be30cba8fa893b489c49ac511cefe229 Mon Sep 17 00:00:00 2001 From: srdusr Date: Wed, 24 Sep 2025 00:26:00 +0200 Subject: Tmux config changes --- linux/home/.config/tmux/file_manager.sh | 108 +++++++++++++++ linux/home/.config/tmux/fzf-menu.sh | 60 ++++++++ linux/home/.config/tmux/notes.sh | 113 +++++++++++++++ linux/home/.config/tmux/tmux-popup-pane-manager.sh | 152 +++++++++++++++++++++ linux/home/.config/tmux/tmux.conf | 19 ++- linux/home/.config/tmux/tmux_number.sh | 11 ++ 6 files changed, 458 insertions(+), 5 deletions(-) create mode 100755 linux/home/.config/tmux/file_manager.sh create mode 100755 linux/home/.config/tmux/fzf-menu.sh create mode 100755 linux/home/.config/tmux/notes.sh create mode 100755 linux/home/.config/tmux/tmux-popup-pane-manager.sh create mode 100755 linux/home/.config/tmux/tmux_number.sh (limited to 'linux/home') diff --git a/linux/home/.config/tmux/file_manager.sh b/linux/home/.config/tmux/file_manager.sh new file mode 100755 index 0000000..b3a70a5 --- /dev/null +++ b/linux/home/.config/tmux/file_manager.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# tmux file opener with fallback file manager (no preview) + +# Mark this pane as the file manager immediately +tmux select-pane -T "FILE_MANAGER" +# Also set the option as backup +tmux set-option -pq @file_manager 1 + +orig_pane="$1" +chooser_file="$HOME/.cache/tmux-fm-selected" +rm -f "$chooser_file" + +# Function: pick available file manager +pick_fm() { + if command -v lf >/dev/null 2>&1; then + echo "lf" + elif command -v nnn >/dev/null 2>&1; then + echo "nnn" + elif command -v ranger >/dev/null 2>&1; then + echo "ranger" + else + echo "" + fi +} + +fm=$(pick_fm) +if [[ -z "$fm" ]]; then + echo "No file manager found (lf, nnn, ranger)." >&2 + cleanup + exit 1 +fi + +# Cleanup function to reset both title and option +cleanup() { + tmux select-pane -T "" + tmux set-option -puq @file_manager + rm -f "$chooser_file" +} + +# Set trap to cleanup on exit (including when user presses 'q') +trap cleanup EXIT INT TERM + +# Launch the chosen file manager with no preview where possible +case "$fm" in + nnn) + # Disable preview completely and use picker mode + # -A: disable dir auto-select, -e: open text files in editor + # -o: open files with opener, -x: show only selection + NNN_OPENER="tee \"$chooser_file\"" nnn -Axo + ;; + lf) + # Disable preview and set selection path + lf -command 'set preview false' -selection-path="$chooser_file" + ;; + ranger) + # Disable all previews + ranger --choosefile="$chooser_file" \ + --cmd='set preview_files false' \ + --cmd='set preview_directories false' \ + --cmd='set preview_images false' + ;; +esac + +# Exit if no file chosen (user pressed 'q' or cancelled) +if [[ ! -s "$chooser_file" ]]; then + exit 0 +fi + +file="$(head -n 1 "$chooser_file")" +rm -f "$chooser_file" + +# Restrict to current window panes and exclude the file manager pane +current_window=$(tmux display-message -p '#I') +mapfile -t panes < <( + tmux list-panes -t "$current_window" -F '#S:#I.#P' | + grep -v "^$(tmux display-message -p '#S:#I').$(tmux display-message -p '#P')$" +) + +# Choose target pane +if [[ ${#panes[@]} -eq 0 ]]; then + exit 1 +elif [[ ${#panes[@]} -eq 1 ]]; then + target="${panes[0]}" +else + echo "Select target pane:" + for i in "${!panes[@]}"; do + letter=$(printf "\\$(printf '%03o' $((97 + i)))") # a, b, c... + echo "$letter) ${panes[$i]}" + done + read -n 1 -p "Choice: " choice + echo + idx=$(( $(printf "%d" "'$choice") - 97 )) + if [[ $idx -ge 0 && $idx -lt ${#panes[@]} ]]; then + target="${panes[$idx]}" + else + exit 1 + fi +fi + +# Decide if file is text or binary +if file --mime-type "$file" 2>/dev/null | grep -q 'text/'; then + opener="${EDITOR:-$(command -v nvim || command -v vim || echo 'vi')}" +else + opener="$(command -v xdg-open || command -v open || echo 'cat')" +fi + +# Send open command to target pane +tmux send-keys -t "$target" "$opener \"$file\"" C-m diff --git a/linux/home/.config/tmux/fzf-menu.sh b/linux/home/.config/tmux/fzf-menu.sh new file mode 100755 index 0000000..d7863d9 --- /dev/null +++ b/linux/home/.config/tmux/fzf-menu.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +# fzf session name +FZF_SESSION_NAME="fzf" + +# Print error messages +error() { + echo "Error: $1" >&2 +} + +# Check if tmux is installed +if ! command -v tmux >/dev/null 2>&1; then + error "tmux is not installed." + exit 1 +fi + +# Function to handle file or directory opening +open_selected_item() { + # Use fzf to select a file from the specified directory + SELECTED_FILE=$(find ~ -type f | fzf --preview "bat --style=numbers --color=always --line-range=:500 {}" \ + --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi) + + if [ "$SELECTED_FILE" != "" ]; then + # Ask whether to open the file or its directory + read -p "Open file (f) or directory (d)? " choice + case "$choice" in + f | F) + # Open the selected file in nvim + nvim "$SELECTED_FILE" + ;; + d | D) + # Change to the directory containing the selected file + cd "$(dirname "$SELECTED_FILE")" + ;; + *) + echo "Invalid choice. Please enter 'f' for file or 'd' for directory." + ;; + esac + else + echo "No file selected." + fi +} + +# Check if the fzf session exists +if tmux has-session -t "$FZF_SESSION_NAME" 2>/dev/null; then + # Get the current tmux session name + CURRENT_SESSION=$(tmux display-message -p '#S') + + # If currently in the fzf session, detach; otherwise, attach to it + if [ "$CURRENT_SESSION" = "$FZF_SESSION_NAME" ]; then + tmux detach-client + else + tmux set -gF '@last_session_name' '#S' # Store the current session name + tmux display-popup -E -x200% -y0 -w50% -h99% "tmux attach-session -t $FZF_SESSION_NAME" + fi +else + # If the fzf session doesn't exist, create it and run file selection logic in a popup + tmux set -gF '@last_session_name' '#S' # Store the current session name + tmux display-popup -E -w100% -h100% -y0 -x0 "tmux new-session -A -s fzf '$0 open_selected_item'" +fi diff --git a/linux/home/.config/tmux/notes.sh b/linux/home/.config/tmux/notes.sh new file mode 100755 index 0000000..71a8dc7 --- /dev/null +++ b/linux/home/.config/tmux/notes.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +# Notes/TODO management & quick search engine via tmux + +NOTES_DIR="$HOME/documents/main" +TODO_FILE="$NOTES_DIR/inbox/tasks/TODO.md" +EDITOR="nvim" +NOTE_SESSION_NAME="note" +BROWSER_PREFERENCES=("firefox" "chromium" "google-chrome" "brave-browser" "chrome") +SEARCH_URL="https://www.google.com/search?q=" + +# simple error printing +error() { + echo "Error: $1" >&2 +} + +# add a TODO entry with timestamp +add_todo() { + local todo_text="$1" + [ -z "$todo_text" ] && return 1 + + [ ! -f "$TODO_FILE" ] && echo -e "# TODO List\n" > "$TODO_FILE" + + echo "- [ ] $todo_text ($(date '+%Y-%m-%d %H:%M'))" >> "$TODO_FILE" + tmux display-message "Added TODO: $todo_text" +} + +# open a web search +search_web() { + local query="$1" + [ -z "$query" ] && return 1 + + local encoded_query=$(printf '%s' "$query" | sed 's/ /+/g' | sed 's/[^a-zA-Z0-9+._-]//g') + local search_url="${SEARCH_URL}${encoded_query}" + + if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$search_url" >/dev/null 2>&1 & + else + for browser in "${BROWSER_PREFERENCES[@]}"; do + command -v "$browser" >/dev/null 2>&1 && $browser "$search_url" >/dev/null 2>&1 & break + done + fi + + tmux display-message "Opening search for: $query" +} + +# display the notes menu (in-editor or popup) +open_menu() { + tmux set -gF '@last_session_name' '#S' + + if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null && tmux list-panes -t "$NOTE_SESSION_NAME" -F "#{pane_current_command}" | grep -q "^nvim$"; then + # menu for active nvim session + tmux display-menu -T "#[align=center] Notes (nvim-mode)" \ + "New note" n "command-prompt -p 'Enter note title:' 'send-keys -t $NOTE_SESSION_NAME \":e $NOTES_DIR/%%.md\" Enter'" \ + "Open note" o "send-keys -t $NOTE_SESSION_NAME \":cd $NOTES_DIR | FzfLua files\" Enter" \ + "TODO List" t "send-keys -t $NOTE_SESSION_NAME \":e $TODO_FILE\" Enter" \ + "Add Quick TODO" T "command-prompt -p 'Enter TODO:' 'run-shell \"$0 --add-todo %%\"'" \ + "Grep/find patterns" g "send-keys -t $NOTE_SESSION_NAME \":cd $NOTES_DIR | FzfLua live_grep\" Enter" \ + "Web Search" s "command-prompt -p 'Search query:' 'run-shell \"$0 --search %%\"'" \ + "Quit (q)" q "" + else + # popup menu outside of nvim + tmux display-menu -T "#[align=center] Notes (popup-mode)" \ + "New note" n "command-prompt -p 'Enter note title:' \"display-popup -w 100% -h 100% -E 'tmux new-session -A -s $NOTE_SESSION_NAME \\\"$EDITOR $NOTES_DIR/%%.md\\\"'\"" \ + "Open note" o "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME 'fzf --preview \\\"bat --style=numbers --color=always --line-range=:500 {}\\\" --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi < <(find $NOTES_DIR -type f -name \\\"*.md\\\") | xargs -r $EDITOR'\"" \ + "TODO List" t "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME \\\"$EDITOR $TODO_FILE\\\"\"" \ + "Add Quick TODO" T "command-prompt -p 'Enter TODO:' 'run-shell \"$0 --add-todo %%\"'" \ + "Grep/find patterns" g "display-popup -w 100% -h 100% -E \"tmux new-session -A -s $NOTE_SESSION_NAME 'rg --color=always --line-number --no-heading --smart-case . $NOTES_DIR | fzf --delimiter=: --preview \\\"bat --style=numbers --color=always --line-range=:500 {1}\\\" --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi | cut -d ':' -f 1 | xargs -r $EDITOR'\"" \ + "Web Search" s "command-prompt -p 'Search query:' 'run-shell \"$0 --search %%\"'" \ + "Quit (q)" q "" + fi +} + +# make sure tmux is installed +command -v tmux >/dev/null 2>&1 || { error "tmux is not installed."; exit 1; } + +# handle CLI arguments +if [ "$1" = "--add-todo" ]; then + shift + add_todo "$*" + exit 0 +fi + +if [ "$1" = "--search" ]; then + shift + search_web "$*" + exit 0 +fi + +if [ "$1" = "--new" ]; then + if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null; then + # reuse existing session + tmux display-popup -w 100% -h 100% -E " + FILE=\$(find $NOTES_DIR -type f -name '*.md' \ + | fzf --preview 'bat --style=numbers --color=always --line-range=:500 {}' \ + --preview-window=up:60% --height=90% --layout=reverse --border=sharp --ansi) + [ -n \"\$FILE\" ] && tmux send-keys -t $NOTE_SESSION_NAME \":e \$FILE\" Enter + " + else + open_menu + fi + exit 0 +fi + +# default behavior: toggle or open menu +if [ -z "$1" ]; then + if tmux has-session -t "$NOTE_SESSION_NAME" 2>/dev/null; then + CURRENT_SESSION=$(tmux display-message -p '#S') + [ "$CURRENT_SESSION" = "$NOTE_SESSION_NAME" ] && tmux detach-client || tmux display-popup -E -x200% -y0 -w50% -h99% "tmux attach-session -t $NOTE_SESSION_NAME" + else + open_menu + fi +fi diff --git a/linux/home/.config/tmux/tmux-popup-pane-manager.sh b/linux/home/.config/tmux/tmux-popup-pane-manager.sh new file mode 100755 index 0000000..bb0cfef --- /dev/null +++ b/linux/home/.config/tmux/tmux-popup-pane-manager.sh @@ -0,0 +1,152 @@ +#!/usr/bin/bash +# tmux-popup-pane-manager.sh - menu driven tmux pane activities +# github repo: https://github.com/pl643/tmux-scripts +# resize, selection, syncronize, layout, splits, kill, break + +# sample tmux.conf binding: +# bind-key -n M-p tmux-popup-pane-manager.sh + +[ "$TMUX" = "" ] && echo "NOTE: needs to be run inside a tmux sessions" && exit 1 + +run_after_popup="/tmp/.run_after_popup" +realpath="$(realpath "$0")" +if [ "$1" != "--no-popup" ]; then + tmux popup -E -T "────────────── Pane Manager ─────" -w 46 -h 35 "$realpath --no-popup" + + [ -f "$run_after_popup" ] && bash "$run_after_popup" && rm "$run_after_popup" + exit 0 +fi + +pane_border_status="off" +display_menu() { + clear + tmux list-windows | grep active | awk '{print $2}' | tail -c2 | grep -q Z && zoom_status="on" || zoom_status="off" + tmux show-options -w | grep -q 'synchronize-panes.*on' && synchronize_panes="on" || synchronize_panes="off" + tmux show-options -w | grep -q 'pane-border-status.*top' && pane_border_status="top" + tmux show-options -w | grep -q 'pane-border-status.*bottom' && pane_border_status="bottom" + printf " + Resize + + hjkl x 5 HJKL x 1 + 1 - 9 | x 10%% ! - ) ─ x 10%% + = equally | + equally ─ + + Split + + s - spilt - v | spilt | + + Navigation + + n p next/prev pane + N P next/prev layout + u d swap pane up/down + + Toggles + + b border [ %s ] + S syncronize [ %s ] + z zoom [ %s ] + + Misc + + B break (make pane into window) + o join this pane to window + D send C-d + e display panes / exit + t rename pane + X kill (no confirm!) + q quit" "$pane_border_status" "$synchronize_panes" "$zoom_status" +} +display_menu + +# https://www.reddit.com/r/tmux/comments/g9nr01/how_to_show_message_or_effect_when/ +# Uncomment this setting if want status of pane sync on the status bar +tmux set -ag status-right '#{?pane_synchronized, #[fg=red]IN_SYNC#[default],}' + +# https://www.reddit.com/r/tmux/comments/dfj5ye/rename_pane_not_window_is_there_a_builtin/ +tmux set -g pane-border-format " [ ###P #T ] " + +# If C-c is press in the while [ true ] loop, a run runaway process occurs, limiting +# it to 20 will cause the loop to exit after 20 loops. Modify MAXNUMLOOP if you +# need more keys presses. +MAXNUMLOOP=100 +COUNTER=0 +while [ "$COUNTER" -lt "$MAXNUMLOOP" ]; do + + read -sn1 c || exit + + # Resize x 1 + [ "$c" = "H" ] && tmux resize-pane -L 1 + [ "$c" = "L" ] && tmux resize-pane -R 1 + [ "$c" = "J" ] && tmux resize-pane -D 1 + [ "$c" = "K" ] && tmux resize-pane -U 1 + + # Resize x 5 + [ "$c" = "h" ] && tmux resize-pane -L 5 + [ "$c" = "l" ] && tmux resize-pane -R 5 + [ "$c" = "j" ] && tmux resize-pane -D 5 + [ "$c" = "k" ] && tmux resize-pane -U 5 + + # Resize X percent + [ "$c" = "1" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 10 / 100)) + [ "$c" = "2" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 20 / 100)) + [ "$c" = "3" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 30 / 100)) + [ "$c" = "4" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 40 / 100)) + [ "$c" = "5" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 50 / 100)) + [ "$c" = "6" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 60 / 100)) + [ "$c" = "7" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 70 / 100)) + [ "$c" = "8" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 80 / 100)) + [ "$c" = "9" ] && tmux resize-pane -x $(($(tmux display-message -p "#{window_width}") * 90 / 100)) + + # Resize Y percent + [ "$c" = "!" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 10 / 100)) + [ "$c" = "@" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 20 / 100)) + [ "$c" = "#" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 30 / 100)) + [ "$c" = "$" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 40 / 100)) + [ "$c" = "%" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 50 / 100)) + [ "$c" = "^" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 60 / 100)) + [ "$c" = "&" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 70 / 100)) + [ "$c" = "*" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 80 / 100)) + [ "$c" = "(" ] && tmux resize-pane -y $(($(tmux display-message -p "#{window_height}") * 90 / 100)) + + # Pane layout cycle + [ "$c" = "N" ] || [ "$c" = " " ] && tmux next-layout + [ "$c" = "P" ] && tmux previous-layout + + # Pane selection cycle + [ "$c" = "n" ] && tmux select-pane -t :.+ + [ "$c" = "p" ] && tmux select-pane -t :.- + + # Pane layout selection even horizontal/vertical + [ "$c" = "=" ] && tmux select-layout even-horizontal + [ "$c" = "+" ] && tmux select-layout even-vertical + + # Rotate pane + [ "$c" = "u" ] && tmux swap-pane -U + [ "$c" = "d" ] && tmux swap-pane -D + + # Syncronize pane + [ "$c" = "S" ] && tmux setw synchronize-pane && display_menu + + # border status ( 3 toggle off, top, bottom ) + [ "$c" = "b" ] && [ "$pane_border_status" = "off" ] && tmux set pane-border-status && display_menu && continue + [ "$c" = "b" ] && [ "$pane_border_status" = "top" ] && tmux set pane-border-status bottom && display_menu && continue + [ "$c" = "b" ] && [ "$pane_border_status" = "bottom" ] && tmux set pane-border-status off && + pane_border_status="off" && display_menu && continue + + # Split panes + [ "$c" = "s" ] || [ "$c" = "-" ] && tmux split -v + [ "$c" = "v" ] || [ "$c" = "|" ] && tmux split -h + + # Misc + [ "$c" = "B" ] && tmux break-pane + [ "$c" = "o" ] && printf "\n\n join window: " && read window && tmux join-pane -t "$window" + [ "$c" = "X" ] && tmux kill-pane + [ "$c" = "D" ] && tmux send-key C-d + display_menu + [ "$c" = "q" ] && exit + [ "$c" = "e" ] && echo tmux display-panes >"$run_after_popup" && exit + [ "$c" = "z" ] && tmux resize-pane -Z && display_menu + [ "$c" = "t" ] && printf "\n\n pane name: " && read pane_name && tmux select-pane -T "$pane_name" && display_menu + let COUNTER=COUNTER+1 +done diff --git a/linux/home/.config/tmux/tmux.conf b/linux/home/.config/tmux/tmux.conf index 8a821e6..a62e3e3 100644 --- a/linux/home/.config/tmux/tmux.conf +++ b/linux/home/.config/tmux/tmux.conf @@ -46,9 +46,7 @@ set -sg escape-time 10 bind r source-file ~/.config/tmux/tmux.conf \; display "Reloaded!" # Use L to clear terminal -#bind -r L send-keys C-l \; send-keys -R \; clear-history -bind -r L send-keys C-l \; send-keys 'Enter' -#bind-key -n C-L if-shell "$is_vim" "send-keys C-l" "send-keys C-l" +bind -r L send-keys "clear" Enter # Rename current window (Ctrl + A, A) bind R rename-window '' \; \ @@ -392,8 +390,11 @@ bind -n M-t if-shell -F '#{==:#{session_name},term}' { ## Toggle popup "note" session -bind-key -n M-n run-shell ~/.config/tmux/tmux-notes-menu.sh -bind-key -n M-N run-shell "~/.config/tmux/tmux-notes-menu.sh --new" +bind-key -n M-n if-shell -F "#{client_in_popup}" \ + "detach-client -P" \ + "run-shell ~/.config/tmux/notes.sh" + +bind-key -n M-N run-shell "~/.config/tmux/notes.sh --new" # Toggle popup "pack" session bind-key -n M-p if-shell -F '#{==:#{session_name},pack}' { @@ -413,6 +414,14 @@ bind-key -n M-o if-shell -F '#{==:#{session_name},todo}' { display-popup -E -x200% -y0 -w50% -h99% "tmux new-session -A -s todo 'nvim ~/documents/main/inbox/tasks/TODO.md'" } +# M-y → Toggle VM popup (starts/attaches session "virt" inside popup) +bind-key -n M-y if-shell -F '#{==:#{session_name},virt}' { + detach-client -P +} { + set -gF '@last_session_name' '#S' + display-popup -E -x200% -y0 -w40% -h60% "tmux new-session -A -s virt bash -lc 'echo \"### VM Manager ###\"; echo; echo \"Available VM scripts:\"; ls -1 ~/.scripts/env/virt/ 2>/dev/null || echo \"No scripts found in ~/.scripts/env/virt/\"; echo; echo \"Run your VM by typing its script name (e.g., ubuntu, fedora, win11).\"; exec \$SHELL'" +} + # M-H → Open history in popup bind-key -n M-H if-shell -F '#{==:#{session_name},hist}' { detach-client diff --git a/linux/home/.config/tmux/tmux_number.sh b/linux/home/.config/tmux/tmux_number.sh new file mode 100755 index 0000000..5d239d0 --- /dev/null +++ b/linux/home/.config/tmux/tmux_number.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -x +dest="$1" +[ "x""$dest" != "x" ] +tmux list-windows -F "#{window_index}" | grep "^${dest}$" 2>&1 >/dev/null +ret="$?" +if [ "x""$ret" = "x0" ]; then + tmux swap-window -t ":${dest}" +else + tmux move-window -t ":${dest}" +fi -- cgit v1.2.3