Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use zsh-auto-notify plugin #55

Merged
merged 3 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/lint_pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
env:
SHELLCHECK_OPTS: -x
with:
ignore_names: .zshrc .zprofile
ignore_names: .zshrc .zprofile *.zsh
shfmt:
name: Run shfmt
runs-on: ubuntu-latest
Expand All @@ -28,6 +28,7 @@ jobs:
SHFMT_OPTS: -s
with:
sh_checker_checkbashisms_enable: true
sh_checker_exclude: .zshrc .zprofile .zsh
yamllint:
name: Run yamllint
runs-on: ubuntu-latest
Expand Down
207 changes: 207 additions & 0 deletions linkme/.oh-my-zsh/custom/plugins/auto-notify/auto-notify.plugin.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# modified from https://github.com/MichaelAquilina/zsh-auto-notify

# Time it takes for a notification to expire
[[ -z "$AUTO_NOTIFY_EXPIRE_TIME" ]] &&
export AUTO_NOTIFY_EXPIRE_TIME=8000
# Threshold in seconds for when to automatically show a notification
[[ -z "$AUTO_NOTIFY_THRESHOLD" ]] &&
export AUTO_NOTIFY_THRESHOLD=30
# Threshold in seconds for when to automatically show a notification
[[ -z "$AUTO_NOTIFY_TRUNCATE_COMMAND" ]] &&
export AUTO_NOTIFY_TRUNCATE_COMMAND=40
# Sound to play when a notification is shown
[[ -z "$AUTO_NOTIFY_SOUND" ]] &&
export AUTO_NOTIFY_SOUND='Frog'

# List of commands/programs to ignore sending notifications for
[[ -z "$AUTO_NOTIFY_IGNORE" ]] &&
export AUTO_NOTIFY_IGNORE=(
'vim'
'nvim'
'less'
'more'
'man'
'tig'
'watch'
'git commit'
'top'
'htop'
'ssh'
'nano'
'ipython'
'python'
)

function _auto_notify_format() {
local MESSAGE="$1"
local command="$2"
local elapsed="$3"
local exit_code="$4"
if [[ ${#command} -gt $AUTO_NOTIFY_TRUNCATE_COMMAND ]]; then
command="${command:0:$AUTO_NOTIFY_TRUNCATE_COMMAND - 1}"
fi
MESSAGE="${MESSAGE//\%command/$command}"
MESSAGE="${MESSAGE//\%elapsed/$elapsed}"
MESSAGE="${MESSAGE//\%exit_code/$exit_code}"
printf "%s" "$MESSAGE"
}

function _auto_notify_message() {
local command="$1"
local elapsed="$2"
local exit_code="$3"
local platform="$(uname)"
martimlobao marked this conversation as resolved.
Show resolved Hide resolved
# Run using echo -e in order to make sure notify-send picks up new line
local DEFAULT_TITLE="Terminal Command Completed"
local DEFAULT_BODY="'%command' finished after %elapseds with exit code %exit_code"

local title="${AUTO_NOTIFY_TITLE:-$DEFAULT_TITLE}"
local text="${AUTO_NOTIFY_BODY:-$DEFAULT_BODY}"

title="$(_auto_notify_format "$title" "$command" "$elapsed" "$exit_code")"
body="$(_auto_notify_format "$text" "$command" "$elapsed" "$exit_code")"

if [[ "$platform" == "Linux" ]]; then
local urgency="normal"
local transient="--hint=int:transient:1"
local icon=${AUTO_NOTIFY_ICON_SUCCESS:-""}
# Exit code 130 is returned when a process is terminated with SIGINT.
# Since the user is already interacting with the program, there is no
# need to make the notification persistent.
if [[ "$exit_code" != "0" ]] && [[ "$exit_code" != "130" ]]; then
urgency="critical"
transient=""
icon=${AUTO_NOTIFY_ICON_FAILURE:-""}
fi

local arguments=("$title" "$body" "--app-name=zsh" "$transient" "--urgency=$urgency" "--expire-time=$AUTO_NOTIFY_EXPIRE_TIME")

if [[ -n "$icon" ]]; then
arguments+=("--icon=$icon")
fi
notify-send ${arguments[@]}
if [[ -n "$AUTO_NOTIFY_SOUND" ]]; then
paplay "$AUTO_NOTIFY_SOUND"
fi

elif [[ "$platform" == "Darwin" ]]; then
notification_command="display notification (item 1 of argv) with title (item 2 of argv)"
notification_body=($body $title)
if [[ ! -z "$AUTO_NOTIFY_SOUND" ]]; then
notification_command+=" sound name (item 3 of argv)"
notification_body+=("$AUTO_NOTIFY_SOUND")
fi
osascript \
-e 'on run argv' \
-e ${notification_command[@]} \
-e 'end run' \
${notification_body[@]}
else
printf "Unknown platform for sending notifications: $platform\n"
fi
}

function _is_auto_notify_ignored() {
martimlobao marked this conversation as resolved.
Show resolved Hide resolved
local command="$1"
# split the command if its been piped one or more times
local command_list=("${(@s/|/)command}")
local target_command="${command_list[-1]}"
# Remove leading whitespace
target_command="$(echo "$target_command" | sed -e 's/^ *//')"

# If the command is being run over SSH, then ignore it
if [[ -n ${SSH_CLIENT-} || -n ${SSH_TTY-} || -n ${SSH_CONNECTION-} ]]; then
print "yes"
return
fi

# Remove sudo prefix from command if detected
if [[ "$target_command" == "sudo "* ]]; then
target_command="${target_command/sudo /}"
fi

# If AUTO_NOTIFY_WHITELIST is defined, then auto-notify will ignore
# any item not defined in the white list
# Otherwise - the alternative (default) approach is used where the
# AUTO_NOTIFY_IGNORE blacklist is used to ignore commands

if [[ -n "$AUTO_NOTIFY_WHITELIST" ]]; then
for allowed in $AUTO_NOTIFY_WHITELIST; do
if [[ "$target_command" == "$allowed"(| *) ]]; then
print "no"
return
fi
done
print "yes"
else
for ignore in $AUTO_NOTIFY_IGNORE; do
if [[ "$target_command" == "$ignore"(| *) ]]; then
print "yes"
return
fi
done
print "no"
fi
}

function _auto_notify_send() {
# Immediately store the exit code before it goes away
local exit_code="$?"

if [[ -z "$AUTO_COMMAND" && -z "$AUTO_COMMAND_START" ]]; then
return
fi

if [[ "$(_is_auto_notify_ignored "$AUTO_COMMAND_FULL")" == "no" ]]; then
local current="$(date +"%s")"
let "elapsed = current - AUTO_COMMAND_START"

if [[ $elapsed -gt $AUTO_NOTIFY_THRESHOLD ]]; then
_auto_notify_message "$AUTO_COMMAND" "$elapsed" "$exit_code"
fi
fi

# Empty tracking so that notifications are not
# triggered for any commands not run (e.g ctrl+C when typing)
_auto_notify_reset_tracking
}

function _auto_notify_track() {
# $1 is the string the user typed, but only when history is enabled
# $2 is a single-line, size-limited version of the command that is always available
# To still do something useful when history is disabled, although with reduced functionality, fall back to $2 when $1 is empty
AUTO_COMMAND="${1:-$2}"
AUTO_COMMAND_FULL="$3"
AUTO_COMMAND_START="$(date +"%s")"
}

function _auto_notify_reset_tracking() {
# Command start time in seconds since epoch
unset AUTO_COMMAND_START
# Full command that the user has executed after alias expansion
unset AUTO_COMMAND_FULL
# Command that the user has executed
unset AUTO_COMMAND
}

function disable_auto_notify() {
add-zsh-hook -D preexec _auto_notify_track
add-zsh-hook -D precmd _auto_notify_send
}

function enable_auto_notify() {
autoload -Uz add-zsh-hook
add-zsh-hook preexec _auto_notify_track
add-zsh-hook precmd _auto_notify_send
}

_auto_notify_reset_tracking


platform="$(uname)"
if [[ "$platform" == "Linux" ]] && ! type notify-send > /dev/null; then
printf "'notify-send' must be installed for zsh-auto-notify to work\n"
printf "Please install it with your relevant package manager\n"
else
enable_auto_notify
fi
56 changes: 1 addition & 55 deletions linkme/.zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ source $(brew --prefix)/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zs
# Add wisely, as too many plugins slow down shell startup.
plugins=(
1password
auto-notify
autojump
git
thefuck
Expand All @@ -107,58 +108,3 @@ eval "$(starship init zsh)"
# Shell completion for uv and uvx
eval "$(uv generate-shell-completion zsh)"
eval "$(uvx --generate-shell-completion zsh)"

# Notify for long running commands
# https://dev.to/kniraj/macos-the-long-running-task-notifier-35o1
# https://github.com/MichaelAquilina/zsh-auto-notify
function command_start {
zsh_command_start_time=$SECONDS
zsh_last_command=$1
}

function command_end {
local duration=$((SECONDS - zsh_command_start_time))
if (( duration > 30 )); then
# Split the command if its been piped one or more times
local command_list=("${(@s/|/)${zsh_last_command}}")
local command="${command_list[-1]}"
# Remove leading whitespace
command="$(echo "$command" | sed -e 's/^ *//')"
# Remove sudo prefix from command if detected
if [[ "$command" == "sudo "* ]]; then
command="${command/sudo /}"
fi

# Don't notify for SSH commands
if [[ -n ${SSH_CLIENT-} || -n ${SSH_TTY-} || -n ${SSH_CONNECTION-} ]]; then
return
fi
# Don't notify for these commands
local notify_exclude=(
"git diff"
"htop"
"ipython"
"less"
"man"
"more"
"nano"
"python"
"ssh"
"top"
"vim"
"watch"
)
for exclude in "${notify_exclude[@]}"; do
if [[ "$command" == "$exclude"* ]]; then
return
fi
done
local message="'${zsh_last_command}' finished after ${duration}s"
local title="Terminal Command Completed"
osascript -e "display notification \"$message\" with title \"$title\""
fi
}

autoload -U add-zsh-hook
add-zsh-hook preexec command_start
add-zsh-hook precmd command_end