Skip to content

Latest commit

 

History

History
1507 lines (1386 loc) · 49.9 KB

shells.org

File metadata and controls

1507 lines (1386 loc) · 49.9 KB

Shell Config

  • dash as the system shell (because it’s faster)
  • fish as my interactive shell
  • bash for some scripts because it’s more convenient than dash and more POSIX compliant than fish

Using org-tangle, I output the configs to the files they need to be in:

  • ~/.profile is sourced by login, making everything in it available to all shells.
  • ~/.bashrc is sourced by bash and fish (using the bass extension).

Fish

Note: I keep fish aliases in Aliases.

set fish_greeting ''
function __haris_init_key_bindings --on-event fish_prompt
    # If I don't postpone it, something overrides it in certain setups
    [ -z "$EMACS_VTERM_PATH" ] && fish_vi_key_bindings || fish_default_key_bindings
    functions --erase __haris_init_key_bindings
end

# Disable path shortening
set fish_prompt_pwd_dir_length 0

if type --quiet bass
    # I'm setting HOME to prevent errors when running inside tuterm
    HOME="/home/$USER" bass source ~/.bashrc # Load bash's config
    if status is-login
        bass source ~/.profile
    end
end

set -gx MANPATH  ":$__fish_data_dir/man"      # Add fish manpages to MANPATH
set -gx MANWIDTH 80
set -gx EDITOR 'editor'
set -gx VISUAL 'visual-editor'
set -gx PAGER 'less'

Behavior overrides

fish_prompt

function fish_prompt --description 'Write out the prompt'
    set -l last_pipestatus $pipestatus

    set -l normal (set_color normal)

    # Color the prompt differently when we're root
    set -l color_cwd $fish_color_cwd
    set -l prefix
    set -l suffix '$'
    if contains -- $USER root toor
        if set -q fish_color_cwd_root
            set color_cwd $fish_color_cwd_root
        end
        set suffix '#'
    end

    # If we're running via SSH, change the host color.
    set -l color_host $fish_color_host
    if set -q SSH_TTY
        set color_host $fish_color_host_remote
    end

    set -l prompt_status (__fish_print_pipestatus " [" "]" "|" \
        (set_color brred) (set_color --bold brred) $last_pipestatus)

    # Using a timeout here prevents hang on remotely mounted filesystems
    set -l vcs_prompt (timeout 0.2 fish --no-config -c fish_vcs_prompt 2>/dev/null)
    set -l vcs_prompt (
        echo "$vcs_prompt" |
        head -c 40 |
        string replace -r '([^\)])$' '$1)'
    )

    set -l host
    if [ -n "$SSH_CONNECTION" ]
        set host "$normal"(set_color white)"[$hostname]"
    end

    set -l sudo_privilege
    if sudo -n true 2>/dev/null
        set sudo_privilege " (#)"
    end

    set -l private
    if [ -n "$fish_private_mode" ]
        set private "(private) "
    end


    echo -n -s  (set_color --bold brblue)   "$USER$host "                \
                (set_color bryellow)        "$private"          $normal  \
                (set_color --bold magenta)  (prompt_pwd)        $normal  \
                (set_color brmagenta)       "$vcs_prompt"                \
                (set_color brmagenta)       $prompt_status               \
                (set_color white)           $sudo_privilege              \
                (set_color yellow)          \n" $suffix "

    # These are needed because some apps (docker-compose) don't clean up properly
    begin
        # Exit AppMode
        echo -ne "\e[?1l"
        # Show the cursor again
        echo -e "\e[?25h"
    end
end

fish_mode_prompt

function fish_mode_prompt
    if [ -z "$EMACS_VTERM_PATH" ]
        switch $fish_bind_mode
            case default
                set_color --bold brred
                echo '[N] '
            case insert
                # Show nothing
            case replace_one
                set_color --bold green
                echo '[r] '
            case replace
                set_color --bold blue
                echo '[R] '
            case visual
                set_color --bold magenta
                echo '[V] '
            case '*'
                set_color --bold red
                echo '?'
        end
    end
    set_color normal
end

fish_right_prompt

function fish_right_prompt
    set_color yellow
    set -l any_content
    if [ -n "$WORKTREE" ]
        echo -n "WT: $WORKTREE"
        set any_content true
    end
    if [ "$COMPOSE_PROJECT_NAME" ]
        if [ -n "$any_content" ]
            echo -n " | "
        end
        echo -n "COMPOSE: $COMPOSE_PROJECT_NAME"
        set any_content true
    end
    if [ -n "$EXPOSED_IP" ]
        if [ -n "$any_content" ]
            echo -n " | "
        end
        echo -n "IP: $EXPOSED_IP"
        set any_content true
    end
    set_color normal
end

Horizontal command output separator

__haris_draw_output_separator

This function prints a horizontal line spanning the terminal screen, that serves as a visual separator of command outputs. It helps me to inspect the scrollback buffer more easily.

It is bound to the fish_postexec event, so it is printed any time a command finishes; unless the command was clear in which case we don’t print it in order to prevent the line to be visible after clearing.

function __haris_draw_output_separator --on-event fish_postexec
    set -l _pipestatus "$fish_pipestatus"
    # When clearing the screen using 'clear' (without -x), the output separator
    # should not be drawn. Otherwise the terminal would contain a line on the top.
    if echo "$argv" | grep -v -- '-x' | grep -qE '^clear($|\s)'
        return
    end

    # Clear any ^C text (due to pressing Ctrl+C to quit the previous process)
    if string match -rq 'SIGINT$' (__fish_print_pipestatus '' '' ' ' '' '' $pipestatus)
        echo -ne '\b\b'
    end

    bash -c 'echo -ne "\e[38;5;238m"; printf "%*s\n" "${COLUMNS:-$(tput cols)}" " " | sed "s: :—:g"'
end

Notification when background job exits

Sends a graphical notification when a command running in a non-visible context finishes. This applies to commands that are:

  • running in a terminal emulator that is minimized or in a different workspace
  • running without a terminal that can be accessed, e.g. launched via dmenu

This applies only to graphical sessions, the linux TTY is unaffected.

__haris_notify_bg_task_done

function __haris_notify_bg_task_done --on-event fish_postexec
    set -l _status "$status"

    [ -n "$HARIS_BACKGROUND_TASKS_SILENT" ] && return
    [ -n "$DISPLAY" ] || return

    if ! type --quiet xdotool
        return 0
    end

    # Check if terminal window is hidden
    if xdotool search --all --onlyvisible "" 2>/dev/null | ! grep -q "$WINDOWID"
        set -l exit_message
        if [ "$_status" = 0 ]
            set exit_message "exited successfully"
        else
            set exit_message "exited with $_status"
        end
        # Timeout is so the command doesn't hang when daemon is not running
        timeout 0.8 notify-send (echo "Command "(history | tac | tail -n +"$__haris_history_count" 2>/dev/null)"" "$exit_message")
        if [ "$status" = "124" ]
            echo ERR: NOTIFICATION TIMED OUT
        end
    end

    set -g __haris_history_count (history | wc -l)
end

edit_command_buffer_custom

set -l actual_editor
if which emacs >/dev/null
    set actual_editor emacs
else
    set actual_editor nvim
end

functions edit_command_buffer |
    sed -E -e 's/^(function edit_command_buffer)/\1_custom/' \
           -e "s/(case.* $actual_editor\b)/\1 visual-editor /" \
    | source

Bindings

function bind_all_modes
    for mode in (bind --list-modes)
        bind -M "$mode" $argv
    end
end
function fish_user_key_bindings               # Start bindings

Quasi-vim-like key bindings

bind_all_modes \el forward-char
bind_all_modes \eh backward-char
bind_all_modes \cp up-or-search
bind_all_modes \cn down-or-search

Fish behavior control bindings

bind_all_modes \er src_fish

# Some terminals like linux TTY and emacs vterm send Ctrl+P as \cP, so I enable
# it only for alacritty which has been proven to work. I can add other terminals
# here as needed.

if [ "$TERM" = "alacritty" ]
    function __haris_toggle_private_mode
        set -g fish_private_mode ([ -z "$fish_private_mode" ] && echo true)
        commandline -f repaint
    end

    bind_all_modes \cP __haris_toggle_private_mode
end

bind_all_modes \eL 'clear; commandline -f repaint'

Utility bindings

# Copy the current contents of the command line
bind_all_modes \ec 'fish_clipboard_copy'

# Run the current command in bash
bind_all_modes \eb __haris_run_in_bash

# Prepend o in front of current command
bind_all_modes \eo '__haris_cmdline_prepend o'

# Prepend man in front of current command
bind_all_modes \em '__haris_show_man'

# Append --help to the end of the command line and submit
bind_all_modes \eH '__haris_cmdline_append_or_toggle --help'

# Append --version to the end of the command line and submit
bind_all_modes \eV '__haris_cmdline_append_or_toggle --version'

bind_all_modes \et term

bind_all_modes \eg 'dragon (command ls | fzf --multi) >/dev/null 2>&1'

bind_all_modes \ee 'edit_command_buffer_custom'

bind_all_modes \eE 'myemacs-float .'

bind_all_modes \ea 'commandline -i "(adhoc)"'

bind_all_modes \eA 'commandline -i "(adhoc - | string collect)"'

bind_all_modes \eG 'magit'

bind_all_modes \e\cd 'disown'

bind_all_modes \eC 'edocker-compose'

bind_all_modes \eT '__haris_test_eval'

bind_all_modes \e\? '__haris_cmdline_prepend whatis'

bind_all_modes \ey '__haris_copy_cmdline_and_output'

# Ctrl+Alt+L
# TODO: Add sol command installation instructions
bind_all_modes \e\f 'commandline --replace (commandline | sol -b -s -a -jqobj)'
end                                          # End fish_user_key_bindings

Helper functions

__haris_run_in_bash
function __haris_run_in_bash
    set -l cmd (commandline -b)
    echo
    eval bash -c "'source ~/.bashrc; $cmd'"
    commandline -f repaint
    commandline -r ''
end
__haris_cmdline_prepend
function __haris_cmdline_prepend
    commandline --cursor 0
    commandline --insert "$argv "
    commandline --function end-of-line
end
__haris_show_man
function __haris_show_man
    eman (commandline --current-process --tokenize | grep -v '^sudo$' | head -1)
end
__haris_cmdline_append_or_toggle
function __haris_cmdline_append_or_toggle -a text
    if [ (commandline -o | tail -1) = "$text" ]
        commandline -r (commandline | string replace -r -- "\s+$text\s*\$" '')
        return
    end
    commandline --append " $text"
    commandline -f execute
end
__haris_test_eval
function __haris_test_eval
    eval "$(adhoc - (mktemp -t XXXXXXXX.fish) 2>/dev/null)"
end
__haris_copy_cmdline_and_output
function __haris_copy_cmdline_and_output
    set -l commandline "$(commandline)"

    if string match -qr __haris_copy_cmdline_and_output "$commandline"
        echo >&2
        echo "Error: __haris_copy_cmdline_and_output is not allowed to be used recursively" >&2
        commandline -f repaint
        return 1
    end

    echo

    begin
        echo "\$ $commandline"
        eval "$commandline" 2>&1 | tee /dev/tty
    end | fish_clipboard_copy
end

Custom colors

set -U fish_color_command           brblue
set -U fish_color_quote             brgreen
set -U fish_color_param             brcyan
set -U fish_color_autosuggestion    brblack
set -U fish_color_cancel         -r red
set -U fish_color_error             red
set -U fish_color_comment           green
set -U fish_color_operator          normal
set -U fish_color_redirection       brmagenta
set -U fish_pager_color_progress    brgreen
set -U fish_pager_color_description green
set -U fish_color_end               yellow

Plugins

jorgebucaran/fisher
edc/bass
oh-my-fish/plugin-pj
PatrickF1/fzf.fish
jorgebucaran/nvm.fish
evanlucas/fish-kubectl-completions
# Fixes some vterm issues
pymander/vfish
brgmnn/fish-docker-compose
oh-my-fish/plugin-pyenv@df70a41

Initialization

# pj plugin
set -g PROJECT_PATHS ~/proj ~/proj/drytoe

# z.lua
set _ZL_CMD z
if type --quiet lua && [ -f /usr/share/z.lua/z.lua ]
    lua /usr/share/z.lua/z.lua --init fish | source
end
set -gx _ZL_CD cd

# tem
if [ "$(type --type tem)" = "function" ]
    tem fish-init
end

# fzf bindings
if functions | grep --quiet fzf_configure_bindings
    fzf_configure_bindings
else if [ -e ~/.config/fish/conf.d/fzf.fish ]
    function __haris_load_fzf --on-event fish_prompt
        source ~/.config/fish/conf.d/fzf.fish
        functions --erase __haris_load_fzf
    end
end

Dependencies

System packages:

fisher
fish
# Dependencies for fzf.fish
fd bat

This code block installs all system dependencies and all plugins in fish based on the plugin list above.

touch ~/.config/fish/{private,tmp}.fish
fish -c "fisher update"

Functions

whatis

function whatis --wraps type --description "Print what something is, regardless of type" --argument-names what
    type "$what"
    if [ "$(type --type "$what")" = 'file' ]
        catcmd "$what"
    end
end

ndir

# Create a new dir and cd
function ndir;  mkdir -p "$argv"; cd "$argv"; end

1

# Print first argument
function 1; echo $argv[1]; end

a

# Run z through fzf
function a
    z -l $argv | read -z choices
    set -l count (echo "$choices" | sed '/^$/d' | wc -l)
    set dest (echo "$choices" | sed '/^$/d' | tac | fzf --select-1)

    cd (echo "$dest" | sed -E -e '/^$/d' -e 's/^\S+\s+//')
end

open

# Open a GUI app and disown
function open; for file in $argv; o xdg-open "$file"; end; end

imount

# Wrapper around imount script so I can cd to the mount directory
function imount
    command imount $argv
    cd (cat /tmp/imount_directory)
end

vh

# Vim help
function vh; vim -c ":h $argv | only"; end

copypath

# Save the path of the argument to the clipboard
function copypath; realpath $argv | xsel -b; end

ls

# When you ls, save the argument so you can quickly cd to that folder.
# It's not fool-proof, but it works in most situations and it's safe.
function ls
    if [ -z "$EMACS_VTERM_PATH" ] && type --quiet lsd
        # In emacs vterm, lsd outputs additional whitespace which is annoying
        lsd --color=auto $argv
    else
        command ls --color=auto $argv
    end
    set -g __last_ls_arg "$argv"
end

cdls

# cd the last directory you have ls-ed
function cdls
    [ -n "$__last_ls_arg" ] && cd "$__last_ls_arg"
end

vils

# vim the last file you have ls-ed
function vils
    [ -n "$__last_ls_arg" ] && vim "$__last_ls_arg"
end

chbg

function chbg
    set -l path (
        ls 2>/dev/null \
            ~/.local/share/backgrounds/"$argv[1]" \
            /usr/local/share/backgrounds/"$argv[1]" \
            /usr/share/backgrounds/"$argv[1]" |
            head -1
    )
    feh --bg-fill "$path"
    rm ~/.wallpaper
    ln -s "$path" ~/.wallpaper
end

Completions

complete --command chbg --no-files --arguments="(
    begin
        ls -1 2>/dev/null ~/.local/share/backgrounds
        ls -1 2>/dev/null /usr/local/share/backgrounds
        ls -1 2>/dev/null /usr/share/backgrounds
    end |
        sort |
        uniq
)"

vicmd

function vicmd
    set -l file (fcmd "$argv");
    if [ -f "$file" ]
        vim "$file"
    else
        read -n 1 -P "Create new script? [y/N]: " choice
        if [ "$choice" = 'y' ]
            myemacs-float ~/.haris/scripts.org
        else
            echo 'Aborting...'
            return 1
        end
    end
end

o

# Run a command and disown. Put it into a tmux session. Notify the user when done.
# Persist a shell for a small time so the user can follow up on the command. If
# the user doesn't follow up within 20 minutes, the background shell will exit
# automatically.
function o
    set -l __o_args $argv
    <<o/argparse>>
    set -l _status "$status"
    if [ "$_status" != 0 ]
        return "$_status"
    end

    if [ -n "$_flag_h" ]
        echo "Run a command in the background in a tmux session so its output can be inspected on-demand."
        echo
        echo "A GUI notification is sent when the command completes."
        echo "After the command completes, plus a certain linger period, the tmux session is killed."
        echo "The linger period can be adjusted with --linger-period or disabled with --persist."
        echo
        echo "Usage: o [OPTIONS] COMMAND..."
        echo
        echo "  -h, --help"        \t\t"Print this help message."
        echo "  -p, --persist"       \t"Persist the tmux session until manually killed."
        echo "  -l, --linger-period" \t"How long the tmux session should linger after the command completes."
        echo                     \t\t\t"Same format as for the sleep command. (default: 20m)"
        echo "  -s, --silent"      \t\t"Do not send a GUI notification when the command completes."
        echo "  -a, --attach"      \t\t"Immediately attach to the created tmux session"
        return
    end

    # The remaining args contain only the command to be run (argparse options have been extracted)
    set -l __cmdline (string escape -- $argv)
    # The arguments are forwarded to the helper script, so it can parse them
    set -l __o_args (string escape -- $__o_args)

    set -l tempfile (mktemp)

    echo >"$tempfile" "
    rm -f '$tempfile'
    set HARIS_BACKGROUND_TASKS_SILENT $HARIS_BACKGROUND_TASKS_SILENT;
    set __o_args $__o_args;
    set __cmdline $__cmdline;"'
    <<o/background-task>>
    '

    set -l tmux_session "$(echo "$__cmdline" | head -c20) [$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 5)]"

    tmux new -d \
        -s "$tmux_session" \
        fish --private -C "$(env | grep -v '\s' | tr '\n' ' ') source $tempfile"

    if [ -n "$_flag_attach" ]
        tmux attach -t "$tmux_session"
    end
end

Testing

Test the function by executing this code block:

<<o>>
o --linger-period=8s fish -C '
    echo Simulating long command...;
    sleep 3s; echo Done.;
    echo The terminal window should close in a few seconds;
    echo "The exit code (\$status) should be 1!";
    false'
alacritty-float -e tmux attach

Argparse block

argparse \
  --stop-nonopt \
  --exclusive linger-period,persist \
  h/help \
  p/persist \
  l/linger-period= \
  s/silent \
  a/attach \
  -- $__o_args

Background task and follow-up shell

Note: This code block must not contain any apostrophes!

# Convert o options to local variables
<<o/argparse>>
set linger_period 20m
if [ -n "$_flag_linger_period" ]
    set linger_period "$_flag_linger_period"
end
set persist "$_flag_persist"
set silent "$HARIS_BACKGROUND_TASKS_SILENT"
if [ -n "$_flag_silent" ]
    set silent "$_flag_silent"
end

functions -e fish_greeting
# Run the command
$__cmdline

set __status "$status"
set -l tmux_session (tmux display-message -p "#S")

if [ -z "$silent" ] && [ -z "$(tmux list-clients -t "$tmux_session")" ]
    # Send a notification and wait for it to close. The reason we wait for it is
    # because if the user is AFK, then the follow-up shell would exit prematurely.
    # This way, if the user has set up notiication persistence while AFK, we
    # leverage that feature.
    # If a tmux session is already attached, the notification is not displayed.
    set -l notif_options
    if [ "$__status" != 0 ]; set -a notif_options "--urgency=critical"; end
    set -l notif_id_file (mktemp)
    # If a client attaches, the notification is no longer necessary and is immediately closed
    set -l hook (string escape run-shell "sh -c \"[ -n \"$notif_id_file\" ] && dunstify --close=\$(cat $notif_id_file)\" || true")
    tmux set-hook client-attached "$hook"

    unbuffer \
        notify-send "Background task done" \
        "$__cmdline exited with code $__status" \
        --wait --print-id $notif_options \
        2>/dev/null >"$notif_id_file"

    rm -f "$notif_id_file"
end

if [ -z "$persist" ]
    # Start a timeout for the shell to close if the user does not perform any
    # follow-up commands
    sh -c "sleep $linger_period; kill $fish_pid" &
    jobs --last --pid | read _timeout_pid
    disown

    function __haris_on_stop_interaction_timeout --on-event fish_preexec
        kill "$_timeout_pid"
        functions --erase __haris_on_stop_interaction_timeout
    end
end

function __haris_reproduce_status_code
    functions --erase __haris_reproduce_status_code
    return $__status
end

__haris_reproduce_status_code

Completions

complete -c o --wraps command

enved

function enved --argument envvar --description "Edit an environment variable by name"
    set tmp (mktemp /tmp/envvar-XXXXXXXXX)
    bass echo \$"$envvar" > "$tmp"
    "$EDITOR" "$tmp"
    bass export "$envvar"=(cat "$tmp")
    /usr/bin/rm -f "$tmp"
end

funcat

function funcat
    type $argv[1] | sed '1,3d;$d' | fish_indent
end

^

function '^' -d "cd to the first directory in the hierarchy by specified name" -a name
    pushd "$PWD"
    while [ "$PWD" != "/" ]
        if [ (basename "$PWD") = "$name" ]
            set -l dir "$PWD"
            popd
            cd "$dir"
            return
        end
        cd ..
    end
    popd
    return 1
end

Completions

complete -c '^' -a '(pwd | tr "/" "\n")' -f

forever

function forever
    while :
        $argv
        sleep 2s
    end
end

Completions

complete -c forever --wraps command --wraps builtin

Worktree utilities

wt

function wt --argument-names wt_index subpath
    if [ (count $argv) = 1 ] && ! string match -qr '^[0-9]+$' "$wt_index"
        set subpath $wt_index
        set wt_index
    end

    set -l worktree_root

    if [ -n "$wt_index" ]
        set worktree_root (git worktree list | cut -f1 -d' ' | grep "wt$wt_index\$")
    else
        set worktree_root (git worktree list | fzf | cut -f1 -d' ')
    end

    if [ -z "$worktree_root" ]
        return 1
    end

    cd "$worktree_root/$subpath"
end
Completions
function __haris_complete_wt
    set -l args (commandline -op)

    set -l git_root (git root)
    set -l current_subpath (realpath (pwd) --relative-to "$git_root")/
    set -l new_git_root "$git_root"

    # The worktree index was provided
    if [ (count $args) -gt 1 ]
        set -l wt_index {$args[2]}
        set new_git_root (git worktree list | cut -f1 -d' ' | grep "wt$wt_index\$")
        if [ -z "$new_git_root" ]
            return
        end
    end

    if [ -d "$new_git_root/$current_subpath" ]
        echo "$current_subpath"
    end

    set -l arg (commandline -ot)

    pushd "$new_git_root"
    complete -C"cd $arg"
    popd
end
complete -c wt -fk -a '(__haris_complete_wt)'

wt_setup_compose

function wt_setup_compose --argument-names project worktree_index
    begin [ -z "$project" ] || [ -z "$worktree_index" ]; end && echo "Usage: wt_setup_compose PROJECT WORKTREE_INDEX" && return 1

    set -gx WORKTREE "$worktree_index"
    set -gx COMPOSE_PROJECT_NAME "$project$worktree_index"
    set -gx EXPOSED_IP (dig +short "$COMPOSE_PROJECT_NAME" | grep -v '^;;')

    if [ -z "$EXPOSED_IP" ]
        set_color yellow
        echo "EXPOSED_IP set to localhost. Consider aliasing $COMPOSE_PROJECT_NAME to a unique IP under the 127.x.x.x subnet in /etc/hosts"
        set_color normal
        echo
        set -gx EXPOSED_IP (dig +short localhost | grep -v '^;;')
    end

    echo "Variables:"
    env | grep -P '^(WORKTREE|COMPOSE_PROJECT_NAME|EXPOSED_IP)=' | cat
end

Miscellaneous others

function cdcf;   set -l file (cf "$argv");   test -f "$file" && cd (dirname        "$file"); end
function catcf;  set -l file (cf "$argv");   test -f "$file" && cat                "$file" ; end
function vicf;   set -l file (cf "$argv");   test -e "$file" && vim                "$file" ; end
function ecf;    set -l file (cf "$argv");   test -e "$file" && myemacs-float "$file" ; end

function cdcmd;  set -l file (fcmd "$argv"); test -f "$file" && cd (dirname "$file"); end
function catcmd; set -l file (fcmd "$argv"); test -f "$file" && cat         "$file" ; end
function ecmd;   set -l file (fcmd "$argv"); test -f "$file" && myemacs     "$file" ; end
function rmcmd;  set -l file (fcmd "$argv"); rm "$file";                              end

eman

function eman
    if [ -n "$DISPLAY" ] && emacsclient -s emacs --eval nil >/dev/null 2>&1
        command eman $argv
    else
        man $argv
    end
end

tmp_func

if type --query uuidgen
    function tmp_func
        set -g __haris_latest_tmp_func_name "__tmp_func_$(uuidgen | tr '-' '_')"
        echo "$__haris_latest_tmp_func_name"
    end
end
tmp_func_consume_content
if type --query tmp_func
    function tmp_func_consume_content
        funcat "$__haris_latest_tmp_func_name"
        functions --erase "$__haris_latest_tmp_func_name"
    end
end

Completions

Function(s) that will be used by many completions. Completions for the functions defined in Functions.

# Return success if the command line contains no positional arguments
function no_positional_args
    set -l -- args    (commandline -po)         # cmdline broken up into list
    set -l -- cmdline (commandline -p)          # single string
    set -l -- n       (count $args)             # number of cmdline tokens
    for i in (seq 2 $n)
        set -l arg $args[$i]
        [ -z "$arg" ] && continue               # can be caused by '--' argument

        # If the the last token is a positional argument and there is no
        # trailing space, we ignore it
        [ "$i" = "$n" ] && [ (string sub -s -1 "$cmdline") != ' ' ] && break

        if string match -rvq '^-' -- "$arg"     # doesn't start with -
            return 1
        end
    end
    # contains a '--' argument
    string match -r -- '\s--\s' "$cmdline" && return 1
    return 0
end

snip

complete -c snip -f -a \
    "(pushd ~/.vim/snips; command ls | sed 's_\(.*\)\.snippets_\1_g'; popd)"

adhoc

complete -c adhoc -f \
    -a "(complete -C'adsfadadflasdjflasdflnasdflasdu /tmp/adhoc-files/' | string replace /tmp/adhoc-files/ \"\")"

pass

source /usr/share/fish/vendor_completions.d/pass.fish
complete -c pass -a 'add' -n "no_positional_args"
complete -c pass -a '(complete -C "pass show ")' -f -n '__fish_seen_subcommand_from add'

otp

complete -c otp -a '(fd "otp-secret.gpg" ~/.password-store -x echo {//} | sed "s:^.*/\.password-store/\?::")' -f

Self-bootstrapping

Some commands provide a completion subcommand (maybe under a slightly different name) which generates the completion script for a specific shell. If you source this script from that shell, you will get completions for that command. In this section I take advantage of that for some programs.

Each self-bootstrapping completion script should set the source variable to the desired final content of the completion script.

[ "$status" != 0 ] && return 1
echo "$source" | tee "$(status filename)" | source

bufls

set -l source "$(bufls completion fish 2>/dev/null)"
«completions/self-bootstrapping/common»

gh

set -l source "$(gh completion -s fish 2>/dev/null)"
«completions/self-bootstrapping/common»

Miscellaneous others

# *cf and *cmd style commands
for cmd in {,cd,vi,cat,e}cf
    complete --command $cmd --no-files -a '(lscf)'
end
for cmd in {f,cd,vi,cat,e, rm}cmd
    complete -c $cmd -f \
        -a '(command ls -1 $PATH 2>/dev/null | grep -v "/")'
end

Bash

Note: ~/.bashrc is sourced by fish as well.

PS1='\[\e[1;36m\]\u\[\e[1;31m\]@\[\e[1;34m\]\h \[\e[1;32m\]\W \[\e[1;31m\]\$ \[\e[0;32m\]\[\e[0m\]'

# Shell options
shopt -s extglob
shopt -s autocd
shopt -s globstar

{
bind '"\C-p":previous-history'
bind '"\C-k":previous-history'
bind '"\C-n":next-history'
bind '"\C-j":next-history'
} 2>/dev/null

export SHELL='fish'
export MPD_HOST="localhost"
export MPD_PORT="6601"

if ! [[ "$PATH" =~ ~/\.local/bin ]]; then
    export PATH=~/.local/bin:"$PATH"
fi

Aliases

Posix Sh

if which startx >/dev/null; then
    alias x='startx'
fi
if which nvim >/dev/null; then
    alias vim='nvim'
fi

Fish

Aliases

alias src_fish  'source ~/.config/fish/config.fish'
alias term      'term & disown'
abbr stage      'ndir /tmp/stage-$USER'
if command --quiet docker
    alias alpine    'docker run -it --rm --name alpine alpine'
    alias debian    'docker run -it --name debian debian:bookworm-slim'
end
function vrg --wraps rg; vim (rg -l $argv); end
function vfd --wraps fd; vim (fd $argv); end

dragon

function dragon --wraps dragon-drop --wraps dragon-drag-and-drop
    if contains -- --help $argv
        command dragon --help
    else
        o "$(which dragon)" $argv
    end
end

Abbreviations

Helper functions

conditional_abbr

Create an abbreviation of a callable (command, function or builting), optionally with arguments, but only if the callable actually exists.

function conditional_abbr
    set -l command (echo $argv[-1] | cut -f1 -d' ')
    if type --quiet "$command"
        _abbr -g $argv
    end
end
_abbr

A wrapper around built-in abbr that adds the --set-cursor option, to reduce verbosity of abbreviation definitions.

function _abbr
    builtin abbr --set-cursor $argv
end

Safety precautions

_abbr -g rm 'rm -i'
_abbr -g mv 'mv -i'

Typo correction

conditional_abbr -g claer 'clear'
conditional_abbr -g pas   'pass'
conditional_abbr -g gs    'git status'

Pacman commands

I also sometimes use Ubuntu, but since I’m so used to pacman, I use the same abbreviations for equivalent apt operations.

Guard
function _if_pacman_else
    if command --quiet pacman
        echo "$argv[1]"
    else
        echo "$argv[2]"
    end
end
if command --quiet pacman || command --quiet apt
Abbreviations
_abbr -g p     (_if_pacman_else 'pacman'         'apt')
_abbr -g pq    (_if_pacman_else 'pacman -Q'      'apt list --installed')
_abbr -g pqq   (_if_pacman_else 'pacman -Qq'     'apt list --installed')
_abbr -g pqi   (_if_pacman_else 'pacman -Qi'     'dpkg --status')
_abbr -g pql   (_if_pacman_else 'pacman -Ql'     'dpkg --listfiles')
_abbr -g pqm   (_if_pacman_else 'pacman -Qm')
_abbr -g pqe   (_if_pacman_else 'pacman -Qe')
_abbr -g pqo   (_if_pacman_else 'pacman -Qo'     'apt-file search --regexp')
_abbr -g pqs   (_if_pacman_else 'pacman -Qs')
_abbr -g psi   (_if_pacman_else 'pacman -Si'     'apt-cache show')
_abbr -g pss   (_if_pacman_else 'pacman -Ss'     'apt search')
_abbr -g pqdtq (_if_pacman_else 'pacman -Qdtq')
_abbr -g sp    (_if_pacman_else 'sudo pacman'    'sudo apt')
_abbr -g sps   (_if_pacman_else 'sudo pacman -S' 'sudo apt install')
_abbr -g spr   (_if_pacman_else 'sudo pacman -R' 'sudo apt remove')
_abbr -g sprq  (_if_pacman_else 'sudo pacman -R (pacman -Qdtq)' 'sudo apt autoremove')
End guard
end
functions --erase _if_pacman_else

Systemd

Guard
if command --quiet systemctl
Abbreviations
_abbr -g ctl       'sudo systemctl'
_abbr -g start     'sudo systemctl start'
_abbr -g stop      'sudo systemctl stop'
_abbr -g en        'sudo systemctl enable'
_abbr -g dis       'sudo systemctl disable'
_abbr -g sts       'systemctl status'
_abbr -g drel      'sudo systemctl daemon-reload'
_abbr -g rel       'sudo systemctl reload'
_abbr -g res       'sudo systemctl restart'
_abbr -g dres      'sudo systemctl daemon-reload --user && systemctl restart  --user'
_abbr -g sus       'systemctl suspend'
_abbr -g j         'journalctl -f -u'
–user versions
_abbr -g ctlu      'systemctl --user'
_abbr -g startu    'systemctl start --user'
_abbr -g stopu     'systemctl stop --user'
_abbr -g enu       'systemctl enable --user'
_abbr -g disu      'systemctl disable --user'
_abbr -g stsu      'systemctl status --user'
_abbr -g drelu     'systemctl daemon-reload --user'
_abbr -g relu      'systemctl reload --user'
_abbr -g resu      'systemctl restart  --user'
_abbr -g dresu     'systemctl daemon-reload --user && systemctl restart  --user'
_abbr -g ju        'journalctl --user -f -u'
End guard
end

Docker

if command --quiet docker
    set -g __haris_docker docker
else if command --quiet podman
    set -g __haris_docker podman
end

if [ -n "$__haris_docker" ]
_abbr -g d      "$__haris_docker"
_abbr -g db     "$__haris_docker build"
_abbr -g dbn    "$__haris_docker build --no-cache"

_abbr -g dr     "$__haris_docker run --rm"
_abbr -g drit   "$__haris_docker run -it --rm"
_abbr -g drite  "$__haris_docker run -it --rm --entrypoint"

_abbr -g deit   "$__haris_docker exec -it"

_abbr -g drm    "$__haris_docker rm -f"

_abbr -g di     "$__haris_docker image"
_abbr -g dil    "$__haris_docker image ls"
_abbr -g dip    "$__haris_docker image prune"
_abbr -g dii    "$__haris_docker image inspect"
_abbr -g dir    "$__haris_docker image rm"

_abbr -g dc     "$__haris_docker container"
_abbr -g dcl    "$__haris_docker container ls"
_abbr -g dcp    "$__haris_docker container prune"
_abbr -g dci    "$__haris_docker container inspect"

_abbr -g dv     "$__haris_docker volume"
_abbr -g dvl    "$__haris_docker volume ls"
_abbr -g dvc    "$__haris_docker volume create"
_abbr -g dvr    "$__haris_docker volume rm"
_abbr -g dvi    "$__haris_docker volume inspect"
_abbr -g dvp    "$__haris_docker volume prune"

_abbr -g dn     "$__haris_docker network"
_abbr -g dnl    "$__haris_docker network ls"
_abbr -g dni    "$__haris_docker network inspect"
_abbr -g dnp    "$__haris_docker network prune"
_abbr -g dnr    "$__haris_docker network rm"
_abbr -g dnc    "$__haris_docker network connect"
_abbr -g dnd    "$__haris_docker network disconnect"
_abbr -g dnC    "$__haris_docker network create"

_abbr -g ds     "$__haris_docker system"
_abbr -g dsp    "$__haris_docker system prune"
_abbr -g dsd    "$__haris_docker system df -v"

_abbr -g dm     "$__haris_docker manifest"
_abbr -g dmi    "$__haris_docker manifest inspect"

_abbr -g dhh    "$__haris_docker history"

_abbr -g dl     "$__haris_docker logs -f"

# Misc
_abbr -g ENT --position anywhere -- '--entrypoint env%'
end
Compose
if [ "$__haris_docker" = "docker" ] &&
    begin
        docker compose version ||
        docker-compose version
    end &>/dev/null
    _abbr -g D      'docker-compose'

    _abbr -g Db     'docker-compose build'

    _abbr -g Du     'docker-compose up'
    _abbr -g Dub    'docker-compose up --build'
    _abbr -g Dud    'docker-compose up -d'
    _abbr -g Dubd   'docker-compose up --build -d'
    _abbr -g Dubt   'docker-compose up --build --timestamps'

    _abbr -g Dr     'docker-compose run --rm'
    _abbr -g Drit   'docker-compose run -it --rm'
    _abbr -g Dritd  'docker-compose run -it -d --rm'
    _abbr -g Dritb  'docker-compose run -it --build --rm'
    _abbr -g Dritn  'docker-compose run -it --no-deps --rm'
    _abbr -g Dritbn 'docker-compose run -it --build --no-deps --rm'
    _abbr -g Drite  'docker-compose run -it --rm --entrypoint'

    _abbr -g Ds     'docker-compose start'
    _abbr -g DS     'docker-compose stop'
    _abbr -g DR     'docker-compose restart'

    _abbr -g Dd     'docker-compose down'
    _abbr -g Ddo    'docker-compose down --remove-orphans'
    _abbr -g Ddv    'docker-compose down --volumes'
    _abbr -g Ddov   'docker-compose down --remove-orphans --volumes'

    _abbr -g Deit   'docker-compose exec -it'
    _abbr -g Drm    'docker-compose rm -sf'
    _abbr -g Dl     'docker-compose logs -f'
    _abbr -g Dc     'docker-compose config'

    # Misc
    _abbr -g Dsh    'docker-compose exec -it % sh'
    _abbr -g Dbash  'docker-compose exec -it % bash'
    _abbr -g Denv   'docker-compose exec -i % env'
    _abbr -g Denvr  'docker-compose run --rm -i % env'
    _abbr -g Dp     'export COMPOSE_PROFILES=%'
    _abbr -g DP     'export COMPOSE_PROJECT_NAME=%'

    # Restart using rm + up
    if type --query tmp_func
        function (tmp_func)
            begin
                set -l container (docker-compose config --services | fzf)
                docker-compose rm -sf "$container"
                docker-compose up -d "$container"
            end
        end
        _abbr -g DRR    "$(tmp_func_consume_content)"
    end
end

Pass

if command --quiet pass
    conditional_abbr -g pn  'pass insert'
    conditional_abbr -g pg  'pass generate --clip'
    conditional_abbr -g pe  'pass edit'
    conditional_abbr -g pc  'pass show --clip'
    conditional_abbr -g pon 'pass otp insert'
end

Watch

_abbr -g w0 'watch -n0.1'
_abbr -g w1 'watch -n1'
_abbr -g w2 'watch -n2'

Ufw

_abbr ufw   'sudo ufw'
_abbr ufws  'sudo ufw status numbered'

MQTT

_abbr sub 'mosquitto_sub -t'
_abbr pub 'mosquitto_pub -t'

Miscellaneous

conditional_abbr -g g     'git'
if [ -n "$DISPLAY" ]
    conditional_abbr -g e 'myemacs -c'
else
    conditional_abbr -g e 'myemacs'
end
conditional_abbr -g E     'myemacs'
conditional_abbr -g s     'sudo'
conditional_abbr -g py    'python'
conditional_abbr -g ipy   'ipython'
conditional_abbr -g copy  'fish_clipboard_copy'
conditional_abbr -g Copy  'tee /dev/tty | fish_clipboard_copy'
conditional_abbr -g C     'tee /dev/tty | fish_clipboard_copy'
conditional_abbr -g CC    'clipctl tmp-disable | fish_clipboard_copy'
conditional_abbr -g paste 'xsel -b -o'
conditional_abbr -g oct   'octave'
conditional_abbr -g octb  'OCTAVE_BASIC=true command octave'
conditional_abbr -g va    'vagrant'
conditional_abbr -g M     'sudo mount -o uid=(id -u),gid=(id -g) /dev/%'
conditional_abbr -g u     'fusermount -u'
conditional_abbr -g um    'fusermount -u ~/mnt/%'
conditional_abbr -g U     'sudo umount'
conditional_abbr -g Um    'sudo umount ~/mnt/%'
conditional_abbr -g UM    'sudo umount ~/mnt/%'
conditional_abbr -g cmd   'command'
conditional_abbr -g w     'which'
conditional_abbr -g P     "pgrep -af"
conditional_abbr -g Pu     "pgrep -afu "(whoami)
conditional_abbr -g yt    'ytfzf -t -s'
conditional_abbr -g t     'tem'
conditional_abbr -g v     'vim'
conditional_abbr -g vf    'vim (fzf)'
conditional_abbr -g fm    'vifm'
conditional_abbr -g fb    'facebook-cli'
conditional_abbr -g fl    'flameshot'
conditional_abbr -g ff    'firefox'
conditional_abbr -g tb    'nc termbin.com 9999'
conditional_abbr -g asc   'asciinema'
conditional_abbr -g hk    'heroku'
conditional_abbr -g mhc   'man http-codes'
conditional_abbr -g rgh   'rg --hidden'
conditional_abbr -g px    'ping x'
conditional_abbr -g ii    'curl ipinfo.io'
conditional_abbr -g bt    'bluetoothctl'
conditional_abbr -g sshk  'ssh-copy-id -i ~/.ssh/%'
conditional_abbr -g sshkm 'ssh-copy-id -i ~/.ssh/main.pub'
_abbr            -g ek    'sudo etckeeper'

if [ "$TERM" != "linux" ]
    conditional_abbr -g man   'eman'
end
conditional_abbr -g R     'reset'
conditional_abbr -g pa    'printarg'
conditional_abbr -g hosts 'myemacs -c /etc/hosts'

_abbr            -g WT    'export WORKTREE=%'
_abbr            -g sni   'sudo npm install -g'
_abbr            -g c     'cut -d " " -f'
_abbr            -g ..    'cd ../%'
_abbr            -g ...   'cd ../../%'

Google cloud

conditional_abbr -g gce 'gcloud compute'

Tmux

# Tmux
conditional_abbr -g x   'tmux'

if type --query tmp_func
    function (tmp_func)
        tmux attach -t (tmux list-sessions -F "#{session_created} #{session_name}" | sort | tac | grep -Po "(?<= ).*" | fzf -s)
    end
    conditional_abbr -g X   "$(tmp_func_consume_content)"
end

VirtualBox

# VirtualBox
conditional_abbr -g vb   'vboxmanage'

Discard helper function

functions --erase conditional_abbr

Variables

Load local and temporary configs

if [ -f ~/.config/fish/local.fish ]
    source ~/.config/fish/local.fish
end
if [ -f ~/.config/fish/tmp.fish ]
    source ~/.config/fish/tmp.fish
end
if [ -w "$XDG_RUNTIME_DIR/ephemeral.config.fish" ]
    source "$XDG_RUNTIME_DIR/ephemeral.config.fish"
end

Environment

# Clipmenu
# (ref:CM_DIR)
CM_DIR=$HOME/.local/share/haris/clipmenu
RCLONE_PASSWORD_COMMAND="pass rclone/config"
VAGRANT_HOME=/usr/local/share/haris/vagrant

Make the environment variables available for shells as well:

eval "$(cat ~/.config/environment.d/90-haris.conf | grep -vP '^\s*#' | sed 's/^/export /')"

.profile

Local configuration

Executable paths

mkdir -p ~/.go
export GOPATH="$HOME"/.go

export PATH="$CLOUDSDK_ROOT_DIR"/bin:"$PATH"
export PATH=~/.go/bin:"$PATH"
gempath="$(gem environment gempath)"
if [ -n "$gempath" ]; then
    export PATH="$(echo "$gempath" | sed -E 's_(:|$)_/bin:_g')$PATH"
fi
export PATH=~/vm/.tem/path:"$PATH"
export PATH=~/.local/bin:"$PATH"

SSH agent

systemctl start --user ssh-agent.service
eval "$(ssh-agent)" >/dev/null

Miscellaneous

export MAKEFLAGS='-j6'
export GPG_TTY=$(tty)
export PYTHONSTARTUP=~/.startup.py
export RUSTC_WRAPPER=sccache
export MOZ_USE_XINPUT2=1
[ "$(get-os-type)" = "arch" ] && export QT_QPA_PLATFORMTHEME=gtk2

# If rootless docker is used, configure the DOCKER_HOST variable
if systemctl is-enabled --user --quiet docker.service; then
    export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
fi

# Better pager for psql command
if which pspg >/dev/null 2>&1; then
    # '-s 5' chooses the mutt theme
    export PSQL_PAGER='pspg -s 5'
fi

mkdir -p /tmp/stage-"$USER"

if [ -f ~/.local.profile ]; then . ~/.local.profile; fi

Local and temporary configs

These are optional and must be provided separately.

Local

Local configurations that I don’t want to keep under version control. Click to edit.

Temporary

Temporary configurations that I don’t want to keep under version control, but also don’t want to use for a long time. Mostly useful for debugging. Click to edit

Ephemeral

Configuration that lives until reboot. Click to edit

Appendix

Helpers

open-ephemeral.config.fish

Code block that is evaluated in order to edit the ephemeral configuration file.

(spacemacs/find-file-split
 (format "%s/ephemeral.config.fish"
         (getenv "XDG_RUNTIME_DIR" (selected-frame))))

Dependencies

dash fish

Local variables