Files
bookhoard/plans/2026-04-20-fish-to-zsh-conversion-1.0.md
T
john-okeefe d4e65a79f9 docs: add fish-to-zsh conversion plan
Add a structured plan for translating Fish shell config.fish into
equivalent Zsh .zshrc syntax, preserving the existing Forge-managed
block in the target file.
2026-04-20 20:42:44 -04:00

11 KiB

Convert Fish config.fish to Zsh .zshrc

Objective

Translate all functional content from ~/.config/fish/config.fish into equivalent Zsh syntax, append it below the existing Forge-managed block in ~/.config/zsh/.zshrc, and leave the existing content untouched.

Source Analysis

Fish config breakdown (~/.config/fish/config.fish, 178 lines)

Lines Category Notes
1 Comment (collapsed) Multi-line comment got collapsed into one line; contains set fish_greeting, set VIRTUAL_ENV_DISABLE_PROMPT, set -x SHELL /usr/bin/fish
2-3 Man pager (bat) set -xU universal env vars
5-6 Paru pager set -x exported env var
8-10 Done plugin settings set -U universal vars for done notification plugin
12-16 Source ~/.fish_profile Fish-specific profile file
18-23 PATH: ~/.local/bin Already in .zshrc (line 2)
25-30 PATH: depot_tools Conditional PATH prepend
32-37 Starship + zoxide + atuin Interactive-only init
39-40 find-the-command hook Fish-specific (ftc.fish)
42-76 Bang-bang + history functions !! and !$ support + history formatting
78-80 backup function Simple cp backup
83-92 copy function Smart cp with directory detection
94-102 cleanup function Remove orphaned pacman packages
104-163 Aliases & abbreviations Many aliases, some with Fish-specific syntax
165-168 Fastfetch on interactive Run fastfetch if available
170-172 PATH: opencode + local bin PATH additions (local bin already covered)
174-178 Qt/KDE theming Conditional QT_STYLE_OVERRIDE

Existing .zshrc breakdown (~/.config/zsh/.zshrc, 48 lines)

Lines 1-47 are Forge-managed and must remain untouched. The new content should be appended after line 48 (end of file).

Implementation Plan

  • Step 1. Append all converted content below the existing Forge block (after line 48). Do not modify any existing lines.

Converted Zsh Code (to append after line 48 of ~/.config/zsh/.zshrc)

# ============================================================
# Converted from ~/.config/fish/config.fish
# ============================================================

# --- Environment Variables ---

# Hide welcome message (Zsh equivalent: set empty PS1 greeting or just skip)
VIRTUAL_ENV_DISABLE_PROMPT="1"
export VIRTUAL_ENV_DISABLE_PROMPT

# Use bat for man pages
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
export MANROFFOPT="-c"

# Hint to exit PKGBUILD review in Paru
export PARU_PAGER="less -P \"Press 'q' to exit the PKGBUILD review.\""

# --- PATH Additions ---

# Add ~/.local/bin to PATH (already present via Forge, but kept for completeness)
if [[ -d ~/.local/bin ]]; then
    case ":${PATH}:" in
        *:"$HOME/.local/bin":*) ;;
        *) export PATH="$HOME/.local/bin:$PATH" ;;
    esac
fi

# Add depot_tools to PATH
if [[ -d ~/Applications/depot_tools ]]; then
    case ":${PATH}:" in
        *:"$HOME/Applications/depot_tools":*) ;;
        *) export PATH="$HOME/Applications/depot_tools:$PATH" ;;
    esac
fi

# Add opencode to PATH
if [[ -d ~/.opencode/bin ]]; then
    case ":${PATH}:" in
        *:"$HOME/.opencode/bin":*) ;;
        *) export PATH="$HOME/.opencode/bin:$PATH" ;;
    esac
fi

# --- Interactive Shell Setup ---

if [[ -o interactive ]]; then
    # Zoxide (smart cd)
    eval "$(zoxide init zsh)"

    # Atuin (shell history)
    eval "$(atuin init zsh)"

    # Starship prompt
    eval "$(starship init zsh)"
fi

# --- Bang-Bang Support (!!) ---

# Zsh has built-in bang-bang via `setopt bang_hist` (default on).
# Enable history expansion:
setopt bang_hist
setopt hist_expand

# --- History Settings ---

HISTFILE=~/.config/zsh/.zsh_history
HISTSIZE=10000
SAVEHIST=10000
setopt extended_history       # Record timestamps (equivalent to --show-time='%F %T ')
setopt share_history          # Share history across sessions
setopt hist_ignore_all_dups   # Remove older duplicate entries
setopt hist_ignore_space      # Ignore commands starting with space
setopt hist_save_no_dups      # Don't save duplicates

# --- Functions ---

# Create a backup of a file
backup() {
    cp "$1" "${1}.bak"
}

# Smart copy: if source is a directory and exactly 2 args, copy recursively
copy() {
    if [[ $# -eq 2 && -d "$1" ]]; then
        command cp -r "${1%/}" "$2"
    else
        command cp "$@"
    fi
}

# Cleanup orphaned packages
cleanup() {
    local orphans
    orphans=$(pacman -Qdtq 2>/dev/null)
    while [[ -n "$orphans" ]]; do
        sudo pacman -R $orphans || break
        orphans=$(pacman -Qdtq 2>/dev/null)
    done
}

# --- Aliases ---

# Replace ls with eza
alias ls='eza -al --color=always --group-directories-first --icons'
alias lsz='eza -al --color=always --total-size --group-directories-first --icons'
alias la='eza -a --color=always --group-directories-first --icons'
alias ll='eza -l --color=always --group-directories-first --icons'
alias lt='eza -aT --color=always --group-directories-first --icons'
alias l.='eza -ald --color=always --group-directories-first --icons .*'

# Replace cat with bat
alias cat='bat --style header,snip,changes'

# Use paru as yay if yay is not installed
if ! command -v yay &>/dev/null && command -v paru &>/dev/null; then
    alias yay='paru'
fi

# Directory navigation
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
alias .....='cd ../../../..'
alias ......='cd ../../../../..'

# Package management
alias big='expac -H M "%m\t%n" | sort -h | nl'
alias fixpacman='sudo rm /var/lib/pacman/db.lck'
alias gitpkg='pacman -Q | grep -i "\-git" | wc -l'
alias rmpkg='sudo pacman -Rdd'
alias upd='/usr/bin/garuda-update'

# Grep replacements (ugrep)
alias grep='ugrep --color=auto'
alias egrep='ugrep -E --color=auto'
alias fgrep='ugrep -F --color=auto'

# Tar
alias tarnow='tar -acf '
alias untar='tar -zxvf '

# Misc
alias dir='dir --color=auto'
alias grubup='sudo update-grub'
alias hw='hwinfo --short'
alias ip='ip -color'
alias psmem='ps auxf | sort -nr -k 4'
alias psmem10='ps auxf | sort -nr -k 4 | head -10'
alias vdir='vdir --color=auto'
alias wget='wget -c '

# Mirror management (reflector)
alias mirror='sudo reflector -f 30 -l 30 --number 10 --verbose --save /etc/pacman.d/mirrorlist'
alias mirrora='sudo reflector --latest 50 --number 20 --sort age --save /etc/pacman.d/mirrorlist'
alias mirrord='sudo reflector --latest 50 --number 20 --sort delay --save /etc/pacman.d/mirrorlist'
alias mirrors='sudo reflector --latest 50 --number 20 --sort score --save /etc/pacman.d/mirrorlist'

# Help for newcomers to Arch
alias apt='man pacman'
alias apt-get='man pacman'
alias please='sudo'
alias tb='nc termbin.com 9999'
alias helpme='echo "To print basic information about a command use tldr <command>"'
alias pacdiff='sudo -H DIFFPROG=meld pacdiff'

# Journalctl errors
alias jctl='journalctl -p 3 -xb'

# Recent installed packages
alias rip='expac --timefmt="%Y-%m-%d %T" "%l\t%n %v" | sort | tail -200 | nl'

# --- Run fastfetch if session is interactive ---

if [[ -o interactive ]] && command -v fastfetch &>/dev/null; then
    fastfetch --config dr460nized.jsonc
fi

# --- Qt/KDE Theming ---

# Set for non-Plasma sessions (Hyprland, etc.)
if [[ -z "$XDG_CURRENT_DESKTOP" ]] || [[ "$XDG_CURRENT_DESKTOP" != "KDE" ]]; then
    export QT_STYLE_OVERRIDE=kvantum
fi

Items Intentionally Excluded

Fish Line(s) Item Reason for Exclusion
1 set -x SHELL /usr/bin/fish Fish-specific; should be SHELL=/usr/bin/zsh if anything, but zsh sets this automatically
1 set fish_greeting Fish-specific; Zsh has no equivalent greeting mechanism
8-10 __done_* settings These are Fish done plugin variables; Zsh equivalent would need a different notification plugin
12-16 Source ~/.fish_profile Fish-specific profile file; if needed, convert to ~/.zprofile or source a zsh-compatible profile
39-40 source /usr/share/doc/find-the-command/ftc.fish Fish-specific command-not-found hook; Zsh has command-not-found-handler or uses pkgfile
42-76 __history_previous_command / __history_previous_command_arguments + bind Fish-specific bang-bang plugin; Zsh has built-in !! and !$ via setopt bang_hist hist_expand

Verification Criteria

  • All aliases work: open a new zsh shell and run alias to confirm they are loaded
  • eza aliases produce colored output with icons
  • bat alias shows headers/snippets
  • backup testfile.txt creates testfile.txt.bak
  • copy dir1 dir2 copies recursively; copy file1 file2 copies normally
  • cleanup removes orphaned pacman packages without error
  • fastfetch runs on interactive shell launch
  • QT_STYLE_OVERRIDE=kvantum is set when not in KDE
  • PATH includes ~/.local/bin, ~/Applications/depot_tools, and ~/.opencode/bin (when dirs exist)
  • zoxide, atuin, and starship initialize in interactive sessions
  • Existing Forge-managed block (lines 1-47) is completely untouched
  • History expansion works: try !! to repeat last command

Potential Risks and Mitigations

  1. Alias name conflicts with existing commands Mitigation: All aliases use the same names as the Fish config, which was already working. The cat alias overriding cat with bat is intentional but could break scripts that call cat and expect raw output. Use \cat or command cat when needed.

  2. Forge-managed block interference Mitigation: The Forge block already sets up compinit, zsh-autosuggestions, zsh-syntax-highlighting, and a prompt theme. The converted Starship init will override the Forge prompt theme. If you want to keep the Forge prompt, remove the eval "$(starship init zsh)" line.

  3. cleanup function subshell variable scope Mitigation: The Fish version used a while loop with $status. The Zsh version captures orphan list in a variable and uses the -n test instead, which is more reliable.

  4. garuda-update alias may not exist Mitigation: The alias is a direct path reference. If the binary doesn't exist, the alias simply won't work when invoked — same behavior as Fish.

  5. Fastfetch config path Mitigation: fastfetch --config dr460nized.jsonc uses a relative config name. Ensure the config file is discoverable by fastfetch (typically in ~/.config/fastfetch/).

Alternative Approaches

  1. Use a framework (oh-my-zsh / zinit): Instead of manual conversion, use a Zsh framework that provides bang-bang, aliases, and plugin management out of the box. Trade-off: adds framework dependency and complexity.

  2. Use babelfish or fish2zsh tools: Automated Fish-to-Zsh converters exist but may not handle all Fish-specific constructs correctly. Trade-off: faster but less reliable for edge cases.

  3. Source a separate file: Instead of appending to .zshrc, place converted content in ~/.config/zsh/.zshrc.fish-converted and source it from .zshrc. Trade-off: cleaner separation but adds an extra file to manage.