NIGHTHAWK_SOCKET="${NIGHTHAWK_SOCKET:-/tmp/nighthawk-$(id -u).sock}"
NIGHTHAWK_FUZZY_DISPLAY="${NIGHTHAWK_FUZZY_DISPLAY:-hint}"
typeset -g _nh_suggestion=""
typeset -g _nh_replace_start=""
typeset -g _nh_replace_end=""
typeset -g _nh_last_buffer=""
typeset -g _nh_has_highlight=0
typeset -g _nh_diff_ops=""
typeset -g _nh_original_buffer=""
typeset -g _nh_original_cursor=""
typeset -g _nh_backoff_until=0
if ! command -v socat &>/dev/null; then
echo "nighthawk: socat not found, install with: apt install socat" >&2
return 1
fi
if ! command -v jq &>/dev/null; then
echo "nighthawk: jq not found, install with: apt install jq" >&2
return 1
fi
_nh_render_ghost() {
local ghost="$1"
if [[ -n "$ghost" ]]; then
POSTDISPLAY="$ghost"
region_highlight+=("${#BUFFER} $((${#BUFFER} + ${#ghost})) fg=8")
_nh_has_highlight=1
fi
}
_nh_render_diff() {
local diff_ops="$1"
local token_start="$2"
local replace_end="$3"
_nh_original_buffer="$BUFFER"
_nh_original_cursor="$CURSOR"
local new_token=""
local -a diff_highlights=()
local pos=$token_start
local last_typed_pos=$token_start
local entry op ch
for entry in ${(s: :)diff_ops}; do
op="${entry[1]}"
ch="${entry[3,-1]}"
case "$op" in
k) new_token+="$ch"; pos=$((pos + 1))
last_typed_pos=$pos ;;
d) new_token+="$ch"
diff_highlights+=("$pos $((pos + 1)) fg=red,bold")
pos=$((pos + 1))
last_typed_pos=$pos ;;
i) new_token+="$ch"
diff_highlights+=("$pos $((pos + 1)) fg=8")
pos=$((pos + 1)) ;;
esac
done
local before="${_nh_original_buffer[1,$token_start]}"
local after="${_nh_original_buffer[$((replace_end + 1)),-1]}"
BUFFER="${before}${new_token}${after}"
CURSOR=$last_typed_pos
_nh_last_buffer="$BUFFER"
for hl in "${diff_highlights[@]}"; do
region_highlight+=("$hl")
done
_nh_has_highlight=${#diff_highlights[@]}
}
_nh_render_hint() {
local suggestion="$1"
if [[ -n "$suggestion" ]]; then
POSTDISPLAY=" → $suggestion"
region_highlight+=("${#BUFFER} $((${#BUFFER} + ${#POSTDISPLAY})) fg=8")
_nh_has_highlight=1
fi
}
_nh_restore_before_edit() {
if [[ -n "$_nh_original_buffer" ]]; then
BUFFER="$_nh_original_buffer"
CURSOR=${#BUFFER} _nh_original_buffer=""
_nh_original_cursor=""
region_highlight=()
_nh_has_highlight=0
_nh_diff_ops=""
_nh_suggestion=""
_nh_replace_start=""
_nh_replace_end=""
unset POSTDISPLAY
_nh_last_buffer="$BUFFER"
fi
}
_nh_self_insert() {
_nh_restore_before_edit
zle .self-insert
}
zle -N self-insert _nh_self_insert
_nh_backward_delete() {
_nh_restore_before_edit
zle .backward-delete-char
}
zle -N backward-delete-char _nh_backward_delete
_nh_clear_ghost() {
unset POSTDISPLAY
if (( _nh_has_highlight )); then
local i
for i in $(seq $_nh_has_highlight); do
region_highlight[-1]=()
done
_nh_has_highlight=0
fi
if [[ -n "$_nh_original_buffer" ]]; then
BUFFER="$_nh_original_buffer"
CURSOR="$_nh_original_cursor"
_nh_original_buffer=""
_nh_original_cursor=""
fi
_nh_suggestion=""
_nh_replace_start=""
_nh_replace_end=""
_nh_diff_ops=""
}
_nh_ensure_daemon() {
[[ -S "$NIGHTHAWK_SOCKET" ]] && return 0
if command -v nh &>/dev/null; then
nh start &>/dev/null
elif command -v nighthawk-daemon &>/dev/null; then
nighthawk-daemon &>/dev/null &
disown
sleep 0.2
fi
return 0
}
_nh_query() {
local now=$EPOCHSECONDS
if (( now < _nh_backoff_until )); then
return
fi
_nh_ensure_daemon
local buffer="$1"
local cursor="$2"
local escaped_buffer="${buffer//\\/\\\\}"
escaped_buffer="${escaped_buffer//\"/\\\"}"
local escaped_cwd="${PWD//\\/\\\\}"
escaped_cwd="${escaped_cwd//\"/\\\"}"
local json="{\"input\":\"${escaped_buffer}\",\"cursor\":${cursor},\"cwd\":\"${escaped_cwd}\",\"shell\":\"zsh\"}"
local response
response=$(echo "$json" | socat -t1 - UNIX-CONNECT:"$NIGHTHAWK_SOCKET" 2>/dev/null)
if [[ -z "$response" ]]; then
_nh_backoff_until=$(( EPOCHSECONDS + 5 ))
return
fi
local text replace_start replace_end diff_ops_str
eval $(echo "$response" | jq -r '
if (.suggestions | length) > 0 then
"text=" + (.suggestions[0].text | @sh) +
" replace_start=" + (.suggestions[0].replace_start | tostring) +
" replace_end=" + (.suggestions[0].replace_end | tostring) +
" diff_ops_str=" + ((.suggestions[0].diff_ops // null) | if . then [.[] | .op[0:1] + ":" + .ch] | join(" ") | @sh else ("" | @sh) end)
else
"text='"''"'"
end
' 2>/dev/null)
if [[ -n "$text" ]]; then
_nh_suggestion="$text"
_nh_replace_start="$replace_start"
_nh_replace_end="$replace_end"
if [[ -n "$diff_ops_str" ]]; then
if [[ "$NIGHTHAWK_FUZZY_DISPLAY" == "hint" ]]; then
_nh_render_hint "$text"
else
_nh_diff_ops="$diff_ops_str"
_nh_render_diff "$diff_ops_str" "$replace_start" "$replace_end"
fi
else
local already_typed_len=$(( cursor - replace_start ))
if (( already_typed_len >= 0 && already_typed_len < ${#text} )); then
local typed_part="${buffer:$replace_start:$already_typed_len}"
if [[ "${text:0:$already_typed_len}" == "$typed_part" ]]; then
local ghost="${text:$already_typed_len}"
_nh_render_ghost "$ghost"
else
_nh_render_hint "$text"
fi
fi
fi
fi
}
if zle -l zle-line-pre-redraw; then
zle -A zle-line-pre-redraw _nh_orig_pre_redraw
fi
_nh_pre_redraw() {
(( $+functions[_nh_orig_pre_redraw] )) && _nh_orig_pre_redraw
[[ "$BUFFER" == "$_nh_last_buffer" ]] && return
_nh_last_buffer="$BUFFER"
_nh_clear_ghost
(( CURSOR != ${#BUFFER} )) && return
(( ${#BUFFER} < 2 )) && return
_nh_query "$BUFFER" "$CURSOR"
}
zle -N zle-line-pre-redraw _nh_pre_redraw
_nh_accept() {
if [[ -n "$_nh_suggestion" && "$_nh_replace_start" != "" && "$_nh_replace_end" != "" ]]; then
local suggestion="$_nh_suggestion"
local rstart="$_nh_replace_start"
local rend="$_nh_replace_end"
if [[ -n "$_nh_original_buffer" ]]; then
BUFFER="$_nh_original_buffer"
_nh_original_buffer=""
_nh_original_cursor=""
fi
unset POSTDISPLAY
region_highlight=()
_nh_has_highlight=0
_nh_suggestion=""
_nh_diff_ops=""
local before="${BUFFER[1,$rstart]}"
local after="${BUFFER[$((rend + 1)),-1]}"
BUFFER="${before}${suggestion}${after}"
CURSOR=${#BUFFER}
_nh_replace_start=""
_nh_replace_end=""
_nh_last_buffer="$BUFFER"
zle redisplay
else
zle expand-or-complete
fi
}
zle -N _nh_accept
bindkey '^I' _nh_accept
_nh_line_finish() {
if [[ -n "$_nh_original_buffer" ]]; then
BUFFER="$_nh_original_buffer"
_nh_original_buffer=""
_nh_original_cursor=""
fi
unset POSTDISPLAY
region_highlight=()
_nh_has_highlight=0
_nh_suggestion=""
_nh_replace_start=""
_nh_replace_end=""
_nh_diff_ops=""
_nh_last_buffer="$BUFFER"
zle accept-line
}
zle -N _nh_line_finish
bindkey '^M' _nh_line_finish