# Dotfiles Management System if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then # Core git wrapper with repository as work-tree _config() { git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@" } # Detect OS case "$(uname -s)" in Linux) CFG_OS="linux" ;; Darwin) CFG_OS="macos" ;; MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;; *) CFG_OS="other" ;; esac # Map system path to repository path _repo_path() { local f="$1" # If it's an absolute path that's not in HOME, handle it specially if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then echo "$CFG_OS/${f#/}" return fi # Check for paths that should go to the repository root case "$f" in common/*|linux/*|macos/*|windows/*|profile/*|README.md) echo "$f" return ;; "$HOME/"*) f="${f#$HOME/}" ;; esac # Default: put under OS-specific home echo "$CFG_OS/home/$f" } _sys_path() { local repo_path="$1" local os_path_pattern="$CFG_OS/" # Handle OS-specific files that are not in the home subdirectory if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then echo "/${repo_path#$os_path_pattern}" return fi case "$repo_path" in # Common configs → OS-specific config dirs common/config/*) case "$CFG_OS" in linux) local base="${XDG_CONFIG_HOME:-$HOME/.config}" echo "$base/${repo_path#common/config/}" ;; macos) echo "$HOME/Library/Application Support/${repo_path#common/config/}" ;; windows) echo "$LOCALAPPDATA\\${repo_path#common/config/}" ;; *) echo "$HOME/.config/${repo_path#common/config/}" ;; esac ;; # Common assets → stay in repo common/assets/*) echo "$HOME/.cfg/$repo_path" ;; # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME common/*) echo "$HOME/${repo_path#common/}" ;; # OS-specific home */home/*) echo "$HOME/${repo_path#*/home/}" ;; # Profile configs and README → stay in repo profile/*|README.md) echo "$HOME/.cfg/$repo_path" ;; # Default fallback *) echo "$HOME/.cfg/$repo_path" ;; esac } # Prompts for sudo if needed and runs the command _sudo_prompt() { if [[ $EUID -eq 0 ]]; then "$@" else if command -v sudo >/dev/null; then sudo "$@" elif command -v doas >/dev/null; then doas "$@" elif command -v pkexec >/dev/null; then pkexec "$@" else echo "Error: No privilege escalation tool found." return 1 fi fi } # Main config command config() { local cmd="$1"; shift local target_dir="" # Parse optional --target flag for add if [[ "$cmd" == "add" ]]; then while [[ "$1" == --* ]]; do case "$1" in --target|-t) target_dir="$2" shift 2 ;; *) echo "Unknown option: $1" return 1 ;; esac done fi case "$cmd" in add) local file_path for file_path in "$@"; do local repo_path if [[ -n "$target_dir" ]]; then local rel_path if [[ "$file_path" == /* ]]; then rel_path="$(basename "$file_path")" else rel_path="$file_path" fi repo_path="$target_dir/$rel_path" else repo_path="$(_repo_path "$file_path")" fi local full_repo_path="$HOME/.cfg/$repo_path" mkdir -p "$(dirname "$full_repo_path")" cp -a "$file_path" "$full_repo_path" git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path" echo "Added: $file_path -> $repo_path" done ;; rm) local rm_opts="" local file_path_list=() for arg in "$@"; do if [[ "$arg" == "-"* ]]; then rm_opts+=" $arg" else file_path_list+=("$arg") fi done for file_path in "${file_path_list[@]}"; do local repo_path="$(_repo_path "$file_path")" if [[ "$rm_opts" == *"-r"* ]]; then _config rm --cached -r "$repo_path" else _config rm --cached "$repo_path" fi eval "rm $rm_opts \"$file_path\"" echo "Removed: $file_path" done ;; sync) local direction="${1:-to-repo}"; shift _config ls-files | while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" if [[ "$direction" == "to-repo" ]]; then if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then cp -a "$sys_file" "$full_repo_path" echo "Synced to repo: $sys_file" fi elif [[ "$direction" == "from-repo" ]]; then if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then local dest_dir="$(dirname "$sys_file")" if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then _sudo_prompt mkdir -p "$dest_dir" _sudo_prompt cp -a "$full_repo_path" "$sys_file" else mkdir -p "$dest_dir" cp -a "$full_repo_path" "$sys_file" fi echo "Synced from repo: $sys_file" fi fi done ;; status) local auto_synced=() while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" local full_repo_path="$HOME/.cfg/$repo_file" if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then cp -fa "$sys_file" "$full_repo_path" auto_synced+=("$repo_file") fi fi done < <(_config ls-files) if [[ ${#auto_synced[@]} -gt 0 ]]; then echo "=== Auto-synced Files ===" for repo_file in "${auto_synced[@]}"; do echo "synced: $(_sys_path "$repo_file") -> $repo_file" done echo fi _config status echo ;; deploy) _config ls-files | while read -r repo_file; do local full_repo_path="$HOME/.cfg/$repo_file" local sys_file="$(_sys_path "$repo_file")" # destination only # Only continue if the source exists if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then local dest_dir dest_dir="$(dirname "$sys_file")" # Create destination if needed if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then _sudo_prompt mkdir -p "$dest_dir" _sudo_prompt cp -a "$full_repo_path" "$sys_file" else mkdir -p "$dest_dir" cp -a "$full_repo_path" "$sys_file" fi echo "Deployed: $repo_file -> $sys_file" fi done ;; checkout) echo "Checking out dotfiles from .cfg..." _config ls-files | while read -r repo_file; do local full_repo_path="$HOME/.cfg/$repo_file" local sys_file="$(_sys_path "$repo_file")" if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then local dest_dir dest_dir="$(dirname "$sys_file")" # Create destination if it doesn't exist if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then _sudo_prompt mkdir -p "$dest_dir" _sudo_prompt cp -a "$full_repo_path" "$sys_file" else mkdir -p "$dest_dir" cp -a "$full_repo_path" "$sys_file" fi echo "Checked out: $repo_file -> $sys_file" fi done ;; backup) local timestamp=$(date +%Y%m%d%H%M%S) local backup_dir="$HOME/.dotfiles_backup/$timestamp" echo "Backing up existing dotfiles to $backup_dir..." _config ls-files | while read -r repo_file; do local sys_file="$(_sys_path "$repo_file")" if [[ -e "$sys_file" ]]; then local dest_dir_full="$backup_dir/$(dirname "$repo_file")" mkdir -p "$dest_dir_full" cp -a "$sys_file" "$backup_dir/$repo_file" fi done echo "Backup complete. To restore, copy files from $backup_dir to their original locations." ;; *) _config "$cmd" "$@" ;; esac } fi # Make SUDO_ASKPASS agnostic: pick the first available askpass binary. # You can predefine SUDO_ASKPASS env var to force a particular path. : "${SUDO_ASKPASS:=""}" # list of common askpass binaries (order: preferred -> fallback) _askpass_candidates=( "$SUDO_ASKPASS" # user-specified (if absolute path) "/usr/lib/ssh/x11-ssh-askpass" "/usr/libexec/openssh/ssh-askpass" "/usr/lib/ssh/ssh-askpass" "/usr/bin/ssh-askpass" "/usr/bin/ssh-askpass-gtk" "/usr/bin/ssh-askpass-gnome" "/usr/bin/ssh-askpass-qt" "/usr/bin/ksshaskpass" "/usr/bin/zenity" # use zenity --entry as wrapper (see below) "/usr/bin/mate-ssh-askpass" "/usr/bin/xdg-open" # last-resort GUI helper (not ideal) ) find_askpass() { for p in "${_askpass_candidates[@]}"; do [ -z "$p" ] && continue # if user gave a path in SUDO_ASKPASS we accept it only if it's executable if [ -n "$SUDO_ASKPASS" ] && [ "$p" = "$SUDO_ASKPASS" ]; then [ -x "$p" ] && { printf '%s\n' "$p"; return 0; } continue fi # if candidate is an absolute path, test directly if [ "${p#/}" != "$p" ]; then [ -x "$p" ] && { printf '%s\n' "$p"; return 0; } continue fi # otherwise try to resolve via PATH if command -v "$p" >/dev/null 2>&1; then # For zenity, we will use a small wrapper (see below) printf '%s\n' "$(command -v "$p")" return 0 fi done return 1 } # If zenity is chosen, use a thin wrapper script so sudo -A can call it like an askpass binary. # This wrapper will be created in $XDG_RUNTIME_DIR or /tmp (non-persistent). create_zenity_wrapper() { local wrapper wrapper="${XDG_RUNTIME_DIR:-/tmp}/.sudo_askpass_zenity.sh" cat >"$wrapper" <<'EOF' #!/bin/sh # simple zenity askpass wrapper for sudo # prints password to stdout so sudo -A works zenity --entry --title="Authentication" --text="Elevated privileges are required" --hide-text 2>/dev/null || exit 1 EOF chmod 700 "$wrapper" printf '%s\n' "$wrapper" } # Set askpass if [ -z "$SUDO_ASKPASS" ]; then candidate="$(find_askpass || true)" if [ -n "$candidate" ]; then if command -v zenity >/dev/null 2>&1 && [ "$(command -v zenity)" = "$candidate" ]; then # create the wrapper and export it wrapper="$(create_zenity_wrapper)" export SUDO_ASKPASS="$wrapper" else export SUDO_ASKPASS="$candidate" fi else # optional: leave unset or set to empty to avoid mistakes unset SUDO_ASKPASS fi fi # debug: (uncomment to print what was chosen) # printf 'SUDO_ASKPASS -> %s\n' "${SUDO_ASKPASS:-}" # Git # No arguments: `git status` # With arguments: acts like `git` g() { if [ $# -gt 0 ]; then git "$@" # If arguments are provided, pass them to git else git status # Otherwise, show git status fi } # Complete g like git compdef g=git # Git alias commands ga() { g add "$@"; } # ga: Add files to the staging area gaw() { g add -A && g diff --cached -w | g apply --cached -R; } # gaw: Add all changes to the staging area and unstage whitespace changes grm() { g rm "$@"; } gb() { g branch "$@"; } # gb: List branches gbl() { g branch -l "$@"; } # gbl: List local branches gbD() { g branch -D "$@"; } # gbD: Delete a branch gbu() { g branch -u "$@"; } # gbu: Set upstream branch ge() { g clone "$@"; } gc() { g commit "$@"; } # gc: Commit changes gcm() { g commit -m "$@"; } # gcm: Commit with a message gca() { g commit -a "$@"; } # gca: Commit all changes gcaa() { g commit -a --amend "$@"; } # gcaa: Amend the last commit gcam() { g commit -a -m "$@"; } # gcam: Commit all changes with a message gce() { g commit -e "$@"; } # gce: Commit with message and allow editing gcfu() { g commit --fixup "$@"; } # gcfu: Commit fixes in the context of the previous commit gco() { g checkout "$@"; } # gco: Checkout a branch or file gcob() { g checkout -b "$@"; } # gcob: Checkout a new branch gcoB() { g checkout -B "$@"; } # gcoB: Checkout a new branch, even if it exists gcp() { g cherry-pick "$@"; } # gcp: Cherry-pick a commit gcpc() { g cherry-pick --continue "$@"; } # gcpc: Continue cherry-picking after resolving conflicts gd() { g diff "$@"; } # gd: Show changes #gd^() { g diff HEAD^ HEAD "$@"; } # gd^: Show changes between HEAD^ and HEAD gds() { g diff --staged "$@"; } # gds: Show staged changes gl() { g lg "$@"; } # gl: Show a customized log glg() { g log --graph --decorate --all "$@"; } # glg: Show a customized log with graph gls() { # Query `glog` with regex query. query="$1" shift glog --pickaxe-regex "-S$query" "$@" } gdc() { g diff --cached "$@"; } # gdc: Show changes between the working directory and the index gu() { g pull "$@"} # gu: Pull gp() { g push "$@"} # gp: Push gpom() { g push origin main "$@"; } # gpom: Push changes to origin main gr() { g remote "$@"; } # gr: Show remote gra() { g rebase --abort "$@"; } # gra: Abort a rebase grb() { g rebase --committer-date-is-author-date "$@"; } # grb: Rebase with the author date preserved grbom() { grb --onto master "$@"; } # grbom: Rebase onto master grbasi() { g rebase --autosquash --interactive "$@"; } # grbasi: Interactive rebase with autosquash grc() { g rebase --continue "$@"; } # grc: Continue a rebase grs() { g restore --staged "$@"; } # grs: Restore changes staged for the next commit grv() { g remote -v "$@"; } # grv: Show remote URLs after each name grh() { g reset --hard "$@"; } # grh: Reset the repository and the working directory grH() { g reset HEAD "$@"; } # grH: Reset the index but not the working directory #grH^() { g reset HEAD^ "$@"; } # grH^: Reset the index and working directory to the state of the HEAD's first parent gs() { g status -sb "$@"; } # gs: Show the status of the working directory and the index gsd() { g stash drop "$@"; } # gsd: Drop a stash gsl() { g stash list --date=relative "$@"; } # gsl: List all stashes gsp() { g stash pop "$@"; } # gsp: Apply and remove a single stash gss() { g stash show "$@"; } # gss: Show changes recorded in the stash as a diff gst() { g status "$@"; } # gst: Show the status of the working directory and the index gsu() { g standup "$@"; } # gsu: Customized standup command gforgotrecursive() { g submodule update --init --recursive --remote "$@"; } # gforgotrecursive: Update submodules recursively gfp() { g commit --amend --no-edit && g push --force-with-lease "$@"; } # gfp: Amending the last commit and force-pushing # Temporarily unset GIT_WORK_TREE function git_without_work_tree() { # Only proceed if a git command is being run if [ "$1" = "git" ]; then shift # Check if the current directory is inside a Git work tree if git rev-parse --is-inside-work-tree &>/dev/null; then # If inside a work tree, temporarily unset GIT_WORK_TREE GIT_WORK_TREE_OLD="$GIT_WORK_TREE" unset GIT_WORK_TREE git "$@" export GIT_WORK_TREE="$GIT_WORK_TREE_OLD" else # If not inside a work tree, call git command directly git "$@" fi else # If it's not a git command, just execute it normally command "$@" fi } # Set alias conditionally #alias git='git_without_work_tree git' # Set bare dotfiles repository git environment variables dynamically function set_git_env_vars() { # Do nothing unless ~/.cfg exists and is a bare git repo [[ -d "$HOME/.cfg" ]] || return git --git-dir="$HOME/.cfg" rev-parse --is-bare-repository &>/dev/null || return # Skip if last command was a package manager if [[ "${(%)${(z)history[1]}}" =~ ^(pacman|yay|apt|dnf|brew|npm|pip|gem|go|cargo) ]]; then return fi # Only set env vars if not already inside another Git repo if ! git rev-parse --is-inside-work-tree &>/dev/null; then export GIT_DIR="$HOME/.cfg" export GIT_WORK_TREE="$(realpath ~)" else unset GIT_DIR unset GIT_WORK_TREE fi } # Hook and initial call function chpwd() { set_git_env_vars } set_git_env_vars # Git Subtrees function gsp() { # Config file for subtrees # # Format: # ;; # # Lines starting with '#' will be ignored GIT_SUBTREE_FILE="$PWD/.gitsubtrees" if [ ! -f "$GIT_SUBTREE_FILE" ]; then echo "Nothing to do - file <$GIT_SUBTREE_FILE> does not exist." return fi if ! command -v config &> /dev/null; then echo "Error: 'config' command not found. Make sure it's available in your PATH." return fi OLD_IFS=$IFS IFS=$'\n' for LINE in $(cat "$GIT_SUBTREE_FILE"); do # Skip lines starting with '#'. if [[ $LINE = \#* ]]; then continue fi # Parse the current line. PREFIX=$(echo "$LINE" | cut -d';' -f 1) REMOTE=$(echo "$LINE" | cut -d';' -f 2) BRANCH=$(echo "$LINE" | cut -d';' -f 3) # Pull from the remote. echo "Executing: git subtree pull --prefix=$PREFIX $REMOTE $BRANCH" if git subtree pull --prefix="$PREFIX" "$REMOTE" "$BRANCH"; then echo "Subtree pull successful for $PREFIX." else echo "Error: Subtree pull failed for $PREFIX." fi done IFS=$OLD_IFS } # Print previous command into a file getlast () { fc -nl $((HISTCMD - 1)) } # Copy the current command to a file copy_command_to_file() { # Only write the last command if BUFFER is not empty if [[ -n "$BUFFER" ]]; then echo "$BUFFER" > ~/command_log.txt # Overwrite with the latest command else # If the buffer is empty, remove the previous log file command rm -f ~/command_log.txt # Optionally remove the log if no command is present fi } # Display the latest command from the log in the user input display_latest_command() { if [[ -f ~/command_log.txt ]]; then # Read the last command from the log local last_command last_command=$(< ~/command_log.txt) # Only display if the last command is not empty if [[ -n "$last_command" ]]; then BUFFER="$last_command" # Set the BUFFER to the last command CURSOR=${#BUFFER} # Set the cursor to the end of the command fi fi zle reset-prompt # Refresh the prompt } # Go up a directory go_up() { copy_command_to_file # Copy the current command to a file BUFFER="" # Clear the current command line cd .. || return # Change directory and return if it fails display_latest_command # Display the latest command in the user input } # Initialize a variable to store the previous directory previous_dir="" # Function to change directories go_into() { copy_command_to_file # Copy the current command to a file # Use fzf or another tool to choose the directory local dir dir=$( (ls -d */; echo "Go Last directory:") | fzf --height 40% --reverse --tac) # Include previous directory as an option if [[ -n "$dir" ]]; then # Check if the user selected the previous directory if [[ "$dir" == Previous:* ]]; then cd - || return # Change to the previous directory else cd "${dir%/}" || return # Change directory if a selection is made (remove trailing slash) fi # Save the current directory to previous_dir previous_dir=$(pwd) # Update previous_dir to current directory after changing BUFFER="" # Clear the current command line display_latest_command # Display the last command if available fi } # Register functions as ZLE widgets zle -N go_up zle -N go_into # XDG_GAMES_DIR: # Path to user-dirs config USER_DIRS_FILE="$HOME/.config/user-dirs.dirs" if [ -f "$USER_DIRS_FILE" ]; then # Extract directory names from user-dirs config _dirs=( ${(f)"$(grep '^XDG_.*_DIR=' "$USER_DIRS_FILE" \ | cut -d= -f2 \ | tr -d '"' \ | sed "s|^\$HOME/||")"} ) _lowercase_count=0 _total_count=0 for d in "${_dirs[@]}"; do [ -n "$d" ] || continue _total_count=$(( _total_count + 1 )) case "$d" in [a-z0-9]*) _lowercase_count=$(( _lowercase_count + 1 )) ;; esac done # Require majority lowercase (≥70%) if [ "$_total_count" -gt 0 ]; then _percent=$(( 100 * _lowercase_count / _total_count )) if [ "$_percent" -ge 70 ]; then # Ensure the lowercase games directory exists if [ ! -d "$HOME/games" ]; then mkdir -p "$HOME/games" fi # Create symbolic link if it doesn't already exist if [ ! -L "$HOME/Games" ] && [ ! -d "$HOME/Games" ]; then ln -s "$HOME/games" "$HOME/Games" fi export XDG_GAMES_DIR="$HOME/games" fi fi unset _dirs _lowercase_count _total_count _percent fi # Enter directory and list contents function cd-clear-ls() { if [ -n "$1" ]; then builtin cd "$@" 2>/dev/null || { echo "cd: no such file or directory: $1"; return 1; } else builtin cd ~ || return 1 fi echo -e "\033[H\033[J" # Clear screen but keep scroll buffer if [ "$PWD" != "$HOME" ] && git rev-parse --is-inside-work-tree &>/dev/null; then ls -a else ls fi } # cd using "up n" as a command up as many directories, example "up 3" up() { # default parameter to 1 if non provided declare -i d=${@:-1} # ensure given parameter is non-negative. Print error and return if it is (( $d < 0 )) && (>&2 echo "up: Error: negative value provided") && return 1; # remove last d directories from pwd, append "/" in case result is empty cd "$(pwd | sed -E 's;(/[^/]*){0,'$d'}$;;')/"; } # cd into $XDG_CONFIG_HOME/$1 directory c() { local root=${XDG_CONFIG_HOME:-~/.config} local dname="$root/$1" if [[ ! -d "$dname" ]]; then return fi cd "$dname" } # Make and cd into directory and any parent directories mkcd () { if [[ -z "$1" ]]; then echo "Usage: mkcd " 1>&2 return 1 fi mkdir -p "$1" cd "$1" } bak() { if [[ -e "$1" ]]; then echo "Found: $1" mv "${1%.*}"{,.bak} elif [[ -e "$1.bak" ]]; then echo "Found: $1.bak" mv "$1"{.bak,} fi } back() { for file in "$@"; do cp -r "$file" "$file".bak done } # tre is a shorthand for tree tre() { tree -aC -I \ '.git|.hg|.svn|.tmux|.backup|.vim-backup|.swap|.vim-swap|.undo|.vim-undo|*.bak|tags' \ --dirsfirst "$@" \ | less } # switch from/to project/package dir pkg() { if [ "$#" -eq 2 ]; then ln -s "$(readlink -f $1)" "$(readlink -f $2)"/._pkg ln -s "$(readlink -f $2)" "$(readlink -f $1)"/._pkg else cd "$(readlink -f ./._pkg)" fi } # Prepare C/C++ project for Language Server Protoco lsp-prep() { (cd build && cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) \ && ln -sf build/compile_commands.json } reposize() { url=`echo $1 \ | perl -pe 's#(?:https?://github.com/)([\w\d.-]+\/[\w\d.-]+).*#\1#g' \ | perl -pe 's#git\@github.com:([\w\d.-]+\/[\w\d.-]+)\.git#\1#g' ` printf "https://github.com/$url => " curl -s https://api.github.com/repos/$url \ | jq '.size' \ | numfmt --to=iec --from-unit=1024 } # Launch a program in a terminal without getting any output, # and detach the process from terminal # (can then close the terminal without terminating process) -echo() { "$@" &> /dev/null & disown } # Reload shell function reload() { local compdump_files="$ZDOTDIR/.zcompdump*" if ls $compdump_files &> /dev/null; then rm -f $compdump_files fi exec $SHELL -l } #pom() { # local -r HOURS=${1:?} # local -r MINUTES=${2:-0} # local -r POMODORO_DURATION=${3:-25} # # bc <<< "(($HOURS * 60) + $MINUTES) / $POMODORO_DURATION" #} #mnt() { # local FILE="/mnt/external" # if [ ! -z $2 ]; then # FILE=$2 # fi # # if [ ! -z $1 ]; then # sudo mount "$1" "$FILE" -o rw # echo "Device in read/write mounted in $FILE" # fi # # if [ $# = 0 ]; then # echo "You need to provide the device (/dev/sd*) - use lsblk" # fi #} # #umnt() { # local DIRECTORY="/mnt" # if [ ! -z $1 ]; then # DIRECTORY=$1 # fi # MOUNTED=$(grep $DIRECTORY /proc/mounts | cut -f2 -d" " | sort -r) # cd "/mnt" # sudo umount $MOUNTED # echo "$MOUNTED unmounted" #} mntmtp() { local DIRECTORY="$HOME/mnt" if [ ! -z $2 ]; then local DIRECTORY=$2 fi if [ ! -d $DIRECTORY ]; then mkdir $DIRECTORY fi if [ ! -z $1 ]; then simple-mtpfs --device "$1" "$DIRECTORY" echo "MTPFS device in read/write mounted in $DIRECTORY" fi if [ $# = 0 ]; then echo "You need to provide the device number - use simple-mtpfs -l" fi } umntmtp() { local DIRECTORY="$HOME/mnt" if ; then DIRECTORY=$1 fi cd $HOME umount $DIRECTORY echo "$DIRECTORY with mtp filesystem unmounted" } duckduckgo() { lynx -vikeys -accept_all_cookies "https://lite.duckduckgo.com/lite/?q=$@" } wikipedia() { lynx -vikeys -accept_all_cookies "https://en.wikipedia.org/wiki?search=$@" } #function filesize() { # # Check if 'du' supports the -b option, which provides sizes in bytes. # if du -b /dev/null > /dev/null 2>&1; then # local arg=-sbh; # If supported, use -sbh options for 'du'. # else # local arg=-sh; # If not supported, use -sh options for 'du'. # fi # # # Check if no arguments are provided. # if [ "$#" -eq 0 ]; then # # Calculate and display sizes for all files and directories in cwd. # du $arg ./* # else # # Calculate and display sizes for the specified files and directories. # du $arg -- "$@" # fi #} # fgl() { git log --graph --color=always \ --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" | fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \ --bind "ctrl-m:execute: (grep -o '[a-f0-9]\{7\}' | head -1 | xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF' {} FZF-EOF" } fgb() { local branches branch branches=$(git --no-pager branch -vv) && branch=$(echo "$branches" | fzf +m) && git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //") } # +--------+ # | Pacman | # +--------+ # TODO can improve that with a bind to switch to what was installed fpac() { pacman -Slq | fzf --multi --reverse --preview 'pacman -Si {1}' | xargs -ro sudo pacman -S } fyay() { yay -Slq | fzf --multi --reverse --preview 'yay -Si {1}' | xargs -ro yay -S } # +------+ # | tmux | # +------+ fmux() { prj=$(find $XDG_CONFIG_HOME/tmuxp/ -execdir bash -c 'basename "${0%.*}"' {} ';' | sort | uniq | nl | fzf | cut -f 2) echo $prj [ -n "$prj" ] && tmuxp load $prj } # ftmuxp - propose every possible tmuxp session ftmuxp() { if [[ -n $TMUX ]]; then return fi # get the IDs ID="$(ls $XDG_CONFIG_HOME/tmuxp | sed -e 's/\.yml$//')" if [[ -z "$ID" ]]; then tmux new-session fi create_new_session="Create New Session" ID="${create_new_session}\n$ID" ID="$(echo $ID | fzf | cut -d: -f1)" if [[ "$ID" = "${create_new_session}" ]]; then tmux new-session elif [[ -n "$ID" ]]; then # Change name of urxvt tab to session name printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID" tmuxp load "$ID" fi } # ftmux - help you choose tmux sessions ftmux() { if [[ ! -n $TMUX ]]; then # get the IDs ID="`tmux list-sessions`" if [[ -z "$ID" ]]; then tmux new-session fi create_new_session="Create New Session" ID="$ID\n${create_new_session}:" ID="`echo $ID | fzf | cut -d: -f1`" if [[ "$ID" = "${create_new_session}" ]]; then tmux new-session elif [[ -n "$ID" ]]; then printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID" tmux attach-session -t "$ID" else : # Start terminal normally fi fi } # +-------+ # | Other | # +-------+ # List install files for dotfiles fdot() { file=$(find "$DOTFILES/install" -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2) [ -n "$file" ] && "$EDITOR" "$DOTFILES/install/$file" } # List projects fwork() { result=$(find ~/workspace/* -type d -prune -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2) [ -n "$result" ] && cd ~/workspace/$result } # Open pdf with Zathura fpdf() { result=$(find -type f -name '*.pdf' | fzf --bind "ctrl-r:reload(find -type f -name '*.pdf')" --preview "pdftotext {} - | less") [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown } # Open epubs with Zathura fepub() { result=$(find -type f -name '*.epub' | fzf --bind "ctrl-r:reload(find -type f -name '*.epub')") [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown } # Search and find directories in the dir stack fpop() { # Only work with alias d defined as: # alias d='dirs -v' # for index ({1..9}) alias "$index"="cd +${index}"; unset index d | fzf --height="20%" | cut -f 1 | source /dev/stdin } #ip() { # emulate -LR zsh # # if [[ $1 == 'get' ]]; then # res=$(curl -s ipinfo.io/ip) # echo -n $res | xsel --clipboard # echo "copied $res to clipboard" # # only run ip if it exists # elif (( $+commands[ip] )); then # command ip $* # fi #} ssh-create() { if [ ! -z "$1" ]; then ssh-keygen -f $HOME/.ssh/$1 -t rsa -N '' -C "$1" chmod 700 $HOME/.ssh/$1* fi } guest() { local guest="$1" shift local port if [[ "$#" -ge 2 && "${@: -1}" =~ ^[0-9]+$ ]]; then port="${@: -1}" set -- "${@:1:$(($#-1))}" fi if [[ -z "$guest" || "$#" -lt 1 ]]; then echo "Send file(s) or directories to remote machine" echo "Usage: guest ... [port]" return 1 fi # Auto-detect port if [[ -z "$port" ]]; then if nc -z localhost 22220 2>/dev/null; then port=22220 elif nc -z localhost 22 2>/dev/null; then port=22 else echo "No known SSH port (22220 or 22) is open. Specify a port manually." return 1 fi fi for src in "$@"; do src="${src/#\~/$HOME}" if [[ ! -e "$src" ]]; then echo "Error: '$src' does not exist." continue fi local abs_path dest_dir rel_dir rsync_src rsync_dest abs_path=$(realpath "$src") rel_dir="${abs_path#$HOME/}" dest_dir=$(dirname "$rel_dir") # Ensure target dir exists remotely ssh -p "$port" "$guest" "mkdir -p ~/$dest_dir" if [[ -d "$src" ]]; then # Add trailing slash to copy contents instead of nesting the dir rsync_src="${src%/}/" rsync_dest="~/$rel_dir/" else rsync_src="$src" rsync_dest="~/$dest_dir/" fi echo "Sending '$src' to '$guest:$rsync_dest'..." rsync -avz -e "ssh -p $port" "$rsync_src" "$guest:$rsync_dest" done } historystat() { history 0 | awk '{print $2}' | sort | uniq -c | sort -n -r | head } promptspeed() { for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done } matrix() { local lines=$(tput lines) cols=$(tput cols) # Check if tmux is available if command -v tmux > /dev/null; then # Save the current status setting local status_setting=$(tmux show -g -w -v status) # Turn off tmux status tmux set -g status off else echo "tmux is not available. Exiting." return 1 fi # Function to restore terminal state restore_terminal() { # Clear the screen clear # Bring back tmux status to its original setting if command -v tmux > /dev/null; then tmux set -g status "$status_setting" fi } trap 'restore_terminal' INT awkscript=' { letters="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*()" lines=$1 random_col=$3 c=$4 letter=substr(letters,c,1) cols[random_col]=0; for (col in cols) { line=cols[col]; cols[col]=cols[col]+1; printf "\033[%s;%sH\033[2;32m%s", line, col, letter; printf "\033[%s;%sH\033[1;37m%s\033[0;0H", cols[col], col, letter; if (cols[col] >= lines) { cols[col]=0; } } } ' echo -e "\e[1;40m" clear while :; do echo $lines $cols $(( $RANDOM % $cols)) $(( $RANDOM % 72 )) sleep 0.05 done | awk "$awkscript" # Restore terminal state restore_terminal } ## Reload shell function reload() { local compdump_files="$ZDOTDIR/.zcompdump*" if ls $compdump_files &> /dev/null; then rm -f $compdump_files fi exec $SHELL -l } ## Generate a secure password function passgen() { LC_ALL=C tr -dc ${1:-"[:graph:]"} < /dev/urandom | head -c ${2:-20} } ## Encode/Decode string using base64 function b64e() { echo "$@" | base64 } function b64d() { echo "$@" | base64 -D } # Search through all man pages function fman() { man -k . | fzf -q "$1" --prompt='man> ' --preview $'echo {} | tr -d \'()\' | awk \'{printf "%s ", $2} {print $1}\' | xargs -r man' | tr -d '()' | awk '{printf "%s ", $2} {print $1}' | xargs -r man } # Back up a file. Usage "backupthis " backupthis() { cp -riv $1 ${1}-$(date +%Y%m%d%H%M).backup; } # Spawn a clone of current terminal putstate () { declare +x >~/environment.tmp declare -x >>~/environment.tmp echo cd "$PWD" >>~/environment.tmp } getstate () { . ~/environment.tmp } # Tmux layout openSession () { tmux split-window -h -t tmux split-window -v -t tmux resize-pane -U 5 } # archive compress compress() { if [[ -n "$1" ]]; then local file=$1 shift case "$file" in *.tar ) tar cf "$file" "$*" ;; *.tar.bz2 ) tar cjf "$file" "$*" ;; *.tar.gz ) tar czf "$file" "$*" ;; *.tgz ) tar czf "$file" "$*" ;; *.zip ) zip "$file" "$*" ;; *.rar ) rar "$file" "$*" ;; * ) tar zcvf "$file.tar.gz" "$*" ;; esac else echo 'usage: compress ./foo ./bar' fi } extract() { if [[ -f "$1" ]] ; then local filename filename=$(basename "$1") local foldername="${filename%%.*}" local fullpath fullpath=$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$1") local didfolderexist=false if [[ -d "$foldername" ]]; then didfolderexist=true read -p "$foldername already exists, do you want to overwrite it? (y/n) " -n 1 echo if [[ "$REPLY" =~ ^[Nn]$ ]]; then return fi fi mkdir -p "$foldername" && cd "$foldername" || return case "$1" in *.tar.bz2) tar xjf "$fullpath" ;; *.tar.gz) tar xzf "$fullpath" ;; *.tar.xz) tar Jxf "$fullpath" ;; *.tar.Z) tar xzf "$fullpath" ;; *.tar) tar xf "$fullpath" ;; *.taz) tar xzf "$fullpath" ;; *.tb2) tar xjf "$fullpath" ;; *.tbz) tar xjf "$fullpath" ;; *.tbz2) tar xjf "$fullpath" ;; *.tgz) tar xzf "$fullpath" ;; *.txz) tar Jxf "$fullpath" ;; *.rar) unrar x -o+ "$fullpath" >/dev/null ;; *.zip) unzip -o "$fullpath" ;; *) echo "'$1' cannot be extracted via extract()" \ && cd .. \ && ! "$didfolderexist" \ && rm -r "$foldername" ;; esac else echo "'$1' is not a valid file" fi } ports() { local result result=$(sudo netstat -tulpn | grep LISTEN) echo "$result" | fzf } trash() { case "$1" in --list) ls -A1 ~/.local/share/Trash/files/ ;; --empty) ls -A1 ~/.local/share/Trash/files/ && \rm -rfv ~/.local/share/Trash/files/* ;; --restore) gio trash --restore "$(gio trash --list | fzf | cut -f 1)" ;; --delete) trash_files=$(ls -A ~/.local/share/Trash/files/ | fzf --multi); echo $trash_files | xargs -I {} rm -rf ~/.local/share/Trash/files/{} ;; *) gio trash "$@" ;; esac } what() { type "$1" echo "$PATH" } shutdown() { if [ "$#" -eq 0 ]; then sudo /sbin/shutdown -h now else sudo /sbin/shutdown -h "$@" fi } windowManagerName () { local window=$( xprop -root -notype ) local identifier=$( echo "${window}" | awk '$1=="_NET_SUPPORTING_WM_CHECK:"{print $5}' ) local attributes=$( xprop -id "${identifier}" -notype -f _NET_WM_NAME 8t ) local name=$( echo "${attributes}" | grep "_NET_WM_NAME = " | cut --delimiter=' ' --fields=3 | cut --delimiter='"' --fields=2 ) echo "${name}" } logout() { local wm wm="$(windowManagerName)" if [[ -n "$wm" ]]; then echo "Logging out by killing window manager: $wm" pkill "$wm" else echo "No window manager detected!" >&2 fi } # Gentoo emg() { if [[ -z "$1" ]]; then echo "Usage: emg [USE_FLAGS] package [package...]" return 1 fi if [[ "$1" =~ ^[^-].* ]]; then local use_flags="$1" shift sudo USE="$use_flags" emerge -av "$@" else sudo emerge -av "$@" fi } # Remove command from history forget () { # Accepts one history line number as argument or search term if [[ -z "$1" ]]; then echo "Usage: hist | hist -s " return 1 fi if [[ "$1" == "-s" ]]; then if [[ -z "$2" ]]; then echo "Usage: hist -s " return 1 fi local search_term="$2" if [[ "$OSTYPE" == "linux-gnu"* ]]; then LC_ALL=C sed -i "/${search_term}/d" "$HISTFILE" # GNU sed else LC_ALL=C sed -i '' "/${search_term}/d" "$HISTFILE" # BSD/macOS sed fi fc -R "$HISTFILE" echo "Deleted all history entries matching '$search_term'." else local num=$1 local cmd=$(fc -ln $num $num 2>/dev/null) if [[ -z "$cmd" ]]; then echo "No history entry found for index $num" return 1 fi history -d $num local escaped_cmd=$(echo "$cmd" | sed 's/[\/&]/\\&/g') if [[ "$OSTYPE" == "linux-gnu"* ]]; then LC_ALL=C sed -i "/${escaped_cmd}/d" "$HISTFILE" else LC_ALL=C sed -i '' "/${escaped_cmd}/d" "$HISTFILE" fi fc -R "$HISTFILE" echo "Deleted '$cmd' from history." fi } # Remove hist command itself remove_hist_command() { [[ $1 != 'hist '* ]] } remove_hist_command search() { # Search for a pattern in the specified directory (non-recursive). dir="${1:-.}" ls -1 "$dir" | grep -i "$2" } deepsearch() { # Perform a recursive search for a pattern in the specified directory. dir="${1:-.}" find "$dir" -iname "$2" } notes() { local base_dir="$HOME/documents/main/" if [[ -z "$1" ]]; then # No argument → cd to notes directory cd "$base_dir" || return return fi local target="$1" # The argument itself # Use find to check if the file exists anywhere in the base directory local found_files=($(find "$base_dir" -type f -name "$target")) if [[ ${#found_files[@]} -eq 1 ]]; then # Only one match found, open it directly $EDITOR "${found_files[0]}" elif [[ ${#found_files[@]} -gt 1 ]]; then # Multiple files found, prompt the user to select one echo "Multiple files found for '$target'. Please choose one:" PS3="Please enter a number to select a file (1-${#found_files[@]}): " select selected_file in "${found_files[@]}"; do if [[ -n "$selected_file" ]]; then $EDITOR "$selected_file" break else echo "Invalid selection, try again." fi done else # If no match found, search for a directory local found_dir=$(find "$base_dir" -type d -name "$target" -print -quit) if [[ -n "$found_dir" ]]; then # Directory found, cd into it cd "$found_dir" || return else # If no match found, create the file and open it local full_target="$base_dir/$target" mkdir -p "$(dirname "$full_target")" $EDITOR "$full_target" fi fi } # Enable tab completion for files and directories _notes_complete() { local base_dir="$HOME/documents/main" compadd -o nospace -- $(find "$base_dir" -type f -o -type d -printf '%P\n') } compdef _notes_complete notes ship() { local binary_dir="$HOME/.local/share" local bin_symlink_dir="$HOME/.local/bin" local project_dirs=( "$HOME/projects/" "$HOME/src/" "$HOME/src/site/" ) mkdir -p "$binary_dir" "$bin_symlink_dir" local project_dir="" if [[ -n "$1" ]]; then # Project name specified for dir in "${project_dirs[@]}"; do if [[ -d "$dir/$1" ]]; then project_dir="$dir/$1" break fi done if [[ -z "$project_dir" ]]; then echo "Project '$1' not found." return 1 fi else # No argument: pick latest edited local bin_file bin_file=$(find "${project_dirs[@]}" -type f -name "Cargo.toml" -exec stat --format="%Y %n" {} \; 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-) if [[ -z "$bin_file" ]]; then echo "No Cargo.toml found." return 1 fi project_dir=$(dirname "$bin_file") fi cd "$project_dir" || return echo "Building project in $project_dir..." # Build it cargo build --release || { echo "Build failed"; return 1; } # Assume binary has same name as project dir local binary_name binary_name=$(basename "$project_dir") local built_binary="target/release/$binary_name" if [[ -x "$built_binary" ]]; then echo "Copying $built_binary to $binary_dir/$binary_name" cp "$built_binary" "$binary_dir/$binary_name" # Create/Update symlink local symlink_path="$bin_symlink_dir/$binary_name" ln -sf "$binary_dir/$binary_name" "$symlink_path" echo "Binary is now at: $binary_dir/$binary_name" echo "Symlink created at: $symlink_path" else echo "Built binary not found: $built_binary" echo "You may need to manually specify the output binary." fi } forge() { local install=no local usage="Usage: forge [--install]" # Handle --install flag if [[ "$1" == "--install" ]]; then install=yes shift elif [[ "$1" == "-h" || "$1" == "--help" ]]; then echo "$usage" return 0 fi if [[ -f "CMakeLists.txt" ]]; then echo "📦 CMake project detected" [[ ! -d build ]] && mkdir build cmake -B build -DCMAKE_BUILD_TYPE=Release || return 1 cmake --build build || return 1 [[ "$install" == "yes" ]] && sudo cmake --install build elif [[ -f "meson.build" ]]; then echo "📦 Meson project detected" if [[ ! -d build ]]; then meson setup build || return 1 fi ninja -C build || return 1 [[ "$install" == "yes" ]] && sudo ninja -C build install elif [[ -f "Makefile" ]]; then echo "📦 Makefile project detected" # Try `make all`, fallback to `make` if `all` fails if make -q all 2>/dev/null; then make all || return 1 else make || return 1 fi [[ "$install" == "yes" ]] && sudo make install else echo "❌ No supported build system found." return 1 fi } # Windows Path: windows_home() { for dir in /mnt/windows/Users/*(N); do base=${dir:t} # `:t` is zsh's "tail" = basename if [[ -d $dir && ! $base =~ ^(All Users|Default|Default User|Public|nx|desktop.ini)$ ]]; then echo "$dir" return 0 fi done return 1 } if winhome_path=$(windows_home); then hash -d winhome="$winhome_path" fi # Allow nnn filemanager to cd on quit nnn() { declare -x +g NNN_TMPFILE=$(mktemp --tmpdir $0.XXXX) trap "command rm -f $NNN_TMPFILE" EXIT =nnn $@ [ -s $NNN_TMPFILE ] && source $NNN_TMPFILE }