diff --git a/zi-action-complete b/zi-action-complete new file mode 100644 index 000000000..4aaff3954 --- /dev/null +++ b/zi-action-complete @@ -0,0 +1,131 @@ +#!/usr/bin/env zsh +# +# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# +# Copyright (c) 2022 zdharma-continuum and contributors +# Copyright (c) 2016-2020 Sebastian Gniazdowski and contributors. + +zi-action-complete() +{ + # Emulate zsh and useful options + emulate -L zsh -o extended_glob -o warn_create_global -o typeset_silent \ + -o no_short_loops -o rc_quotes -o no_auto_pushd + + # The index of the match that's inserted in cmd line + integer -g ziac_midx + typeset -ga ziac_matches ziac_wids + typeset -g ziac_prevw ziac_mt + local -a match mbegin mend + typeset -gA Times + + # Emulate real time if needed + if (( !$+EPOCHREALTIME )); then + (( !$+SECONDS )) && float -g SECONDS + float EPOCHREALTIME=$SECONDS + fi + + # + # Invoke the helper/worker function that does all needed $BUFFER processing + # + + zi-process-buffer || return 1 + + integer i j pos=$CURSOR size=${#ZINIT_PB_WORDS} newcursor + local w PWIDGET buf + (( Times[$WIDGET] = Times[$WIDGET] <= 0 ? + EPOCHREALTIME-3 : Times[$WIDGET] )) + # Detect series. + if (( EPOCHREALTIME - Times[$WIDGET] < 1.5 )); then + PWIDGET=$WIDGET + else + PWIDGET= ziac_prevw= + fi + Times[$WIDGET]=$EPOCHREALTIME + + # Cursor at empty space? Only if not use the word + [[ $BUFFER[CURSOR] == [[:space:]] ]] || \ + w=$ZINIT_PB_WORDS[ZINIT_PB_SELECTED_WORD] + + if [[ $WIDGET == zi-action-complete-ice && $WIDGET != $PWIDGET && \ + ( $ziac_prevw != $w || -z $w ) ]] + then + local -a ice_order=( + ${(Aons:|:)ZINIT[ice-list]} + ${(@)${(A@kons:|:)${ZINIT_EXTS[ice-mods]//\'\'/}}/(#s)<->-/} + ) + ziac_prevw=$w + match=() + w=${w//(#b)(([=:]|)[\'\"]?#([\'\"]|(#e)))/} + ziac_mt=$match[1] + + ziac_matches=( ${(onM)ice_order:#*$w*} ) + elif (( ziac_midx )) && [[ $WIDGET == *-ice && $WIDGET == $PWIDGET ]]; then + ziac_midx+=1 + fi + if [[ $WIDGET == zi-action-complete && $WIDGET != $PWIDGET && \ + ( $ziac_prevw != $w || -z $w ) ]]; then + ziac_matches=( $ZINIT[PLUGINS_DIR]/*${w//\//---}*(-onND[1,18]) ) + ziac_prevw=$w + ziac_mt= + elif (( ziac_midx )) && [[ $WIDGET == *-complete && $WIDGET == $PWIDGET ]]; then + ziac_midx+=1 + fi + if (( !ziac_midx || ziac_midx > $#ziac_matches )); then + ziac_midx=1 + fi + + if [[ -z $ziac_matches ]]; then + zle -M "No matches for $w found" + return 1 + fi + zle -M "${(j: :)${ziac_matches[@]:t}//---//}" + REPLY=${${ziac_matches[$ziac_midx]:t}//---//}$ziac_mt + for (( i=1; i<=size; i++ )); do + # Check if we're at (i.e. directly at or after, + # when after are just spaces) current word + if [[ $i = $ZINIT_PB_SELECTED_WORD ]]; then + # INSERT MODE? I.e.: addition of a new token at the pointed free space? + if (( pos > (ZINIT_PB_WORDS_BEGINNINGS[i] + ${#ZINIT_PB_WORDS[i]} - 1) )); then + # We need to introduce new word + # First move all words and spaces forward + for (( j=size; j>=i+1; j-- )); do + ZINIT_PB_WORDS[j+1]=${ZINIT_PB_WORDS[j]} + ZINIT_PB_SPACES[j+1]=${ZINIT_PB_SPACES[j]} + ZINIT_PB_WORDS_BEGINNINGS[j+1]=${ZINIT_PB_WORDS_BEGINNINGS[j]} + done + size+=1 + # New word is introduced at position i+1, after current word + # It doesn't have word beginnings and spaces assigned + # It has to take spaces from word next to it, i+2 + ZINIT_PB_WORDS[i+1]=$REPLY + ZINIT_PB_WORDS_BEGINNINGS[i+1]="$(( pos + 1 ))" + + # Now split spaces + # cursor_spaces: spaces from current word to the cursor + integer cursor_spaces=$(( pos - ZINIT_PB_WORDS_BEGINNINGS[i] - ${#ZINIT_PB_WORDS[i]} + 1 )) + # take that from spaces of word "next" in: current NEW next + integer after_spaces=$(( ZINIT_PB_SPACES[i+2] - cursor_spaces )) + local empty="" + ZINIT_PB_SPACES[i+1]="${(l:cursor_spaces:: :)empty}" + ZINIT_PB_SPACES[i+2]="${(l:after_spaces:: :)empty}" + + # Cursor will be at end of newly added word + newcursor=$(( ZINIT_PB_WORDS_BEGINNINGS[i+1] + ${#ZINIT_PB_WORDS[i+1]} - 1 )) + # OR REPLACE MODE – substitute the match for the input/needle token + else + ZINIT_PB_WORDS[i]=$REPLY + + # Cursor will be at end of newly substituted word + newcursor=$(( ZINIT_PB_WORDS_BEGINNINGS[i] + ${#ZINIT_PB_WORDS[i]} - 1 )) + + # Update word beginnings of following words – skipped + fi + fi + buf+=$ZINIT_PB_SPACES[i]$ZINIT_PB_WORDS[i] + done + + buf+=$ZINIT_PB_SPACES[i] + BUFFER=$buf + CURSOR=$newcursor +} + diff --git a/zi-process-buffer b/zi-process-buffer new file mode 100644 index 000000000..f62c612f9 --- /dev/null +++ b/zi-process-buffer @@ -0,0 +1,98 @@ +# Input: +# $1 - optional buffer to process (default is $BUFFER) +# $2 - optional parameter containing cursor (default is $CURSOR) +# +# Output: +# ZINIT_PB_WORDS - split of $1 into shell words; array +# ZINIT_PB_WORDS_BEGINNINGS - indexes of first letters of corresponding words in ZINIT_PB_WORDS +# ZINIT_PB_SPACES - white spaces before corresponding words in ZINIT_PB_WORDS +# ZINIT_PB_SELECTED_WORD - index in ZINIT_PB_WORDS pointing to word activated by cursor position +# ZINIT_PB_LEFT - left part of active word +# ZINIT_PB_RIGHT - right part of active word +# + +emulate -LR zsh +setopt typesetsilent extendedglob noshortloops + +local MBEGIN MEND MATCH mbegin mend match + +local buf="${1:-$BUFFER}" +local cursor="${2:-$CURSOR}" + +ZINIT_PB_WORDS=( "${(Z+n+)buf}" ) +ZINIT_PB_SPACES=( ) +ZINIT_PB_WORDS_BEGINNINGS=( ) +ZINIT_PB_SELECTED_WORD=-1 + +# (Z+n+) will return 1 element for buf that is empty or only whitespace +if [[ $buf = ( |$'\t')# ]]; then + ZINIT_PB_WORDS=( ) + integer nwords=0 +else + integer nwords="${#ZINIT_PB_WORDS}" +fi + +# Remove ZINIT_PB_WORDS one by one, counting characters, +# computing beginning of each word, to find +# place to break the word into 2 halves (for +# complete_in_word option) + +local i word wordlen +integer char_count=0 + +# (Z) handles spaces nicely, but we need them for the user +# Also compute words beginnings and the selected word +for (( i=1; i<=nwords; i++ )); do + # Remove spurious space generated by Z-flag when + # input is an unbound '$(' (happens with zsh < 5.1) + # and also real spaces gathered by an unbound '$(', + # to handle them in a way normal to this loop + ZINIT_PB_WORDS[i]="${ZINIT_PB_WORDS[i]%% ##}" + word="${ZINIT_PB_WORDS[i]}" + + # In general, $buf can start with white spaces + # We will not search for them, but instead for + # leading character of current shell word, + # negated. This is an ambition to completely + # avoid character classes + + # Remove white spaces + buf="${buf##(#m)[^$word[1]]#}" + # Count them + char_count=char_count+"$#MATCH" + # This is the beginning of current word + ZINIT_PB_WORDS_BEGINNINGS[i]=$(( char_count + 1 )) + # Remember the spaces + ZINIT_PB_SPACES[i]=$MATCH + + # Remove the word + wordlen="${#word}" + [[ "${buf[1,wordlen]}" != $word ]] && return 1 # should not happen unless bug in (z) + buf="${buf[wordlen+1,-1]}" + + # Spaces point to previous shell word + # Visual cursor right after spaces (-ge) -> not enough to select previous word (-gt required) + [[ $ZINIT_PB_SELECTED_WORD -eq -1 && $char_count -gt $cursor ]] && ZINIT_PB_SELECTED_WORD=$(( i-1 )) + + # Actual characters point to current shell word + # Visual cursor right after letters (-ge) -> enough to select current word + char_count=char_count+"$#word" + [[ $ZINIT_PB_SELECTED_WORD -eq -1 && $char_count -ge $cursor ]] && ZINIT_PB_SELECTED_WORD=$i +done + +# What's left in $buf can be only white spaces +char_count=char_count+"$#buf" +ZINIT_PB_SPACES[i]=$buf + +# Visual cursor right after spaces (-ge) -> enough to select last word +[[ $ZINIT_PB_SELECTED_WORD -eq -1 && $char_count -ge $cursor ]] && ZINIT_PB_SELECTED_WORD=$(( i-1 )) + +# Divide active word into two halves +integer diff=$(( cursor - ZINIT_PB_WORDS_BEGINNINGS[ZINIT_PB_SELECTED_WORD] + 1 )) +word="${ZINIT_PB_WORDS[ZINIT_PB_SELECTED_WORD]}" +ZINIT_PB_LEFT="${word[1,diff]}" +ZINIT_PB_RIGHT="${word[diff+1,-1]}" + +[[ $ZINIT_PB_SELECTED_WORD -gt 0 ]] + +# vim:ft=zsh:sw=2:sts=2:et:foldmarker=[[[,]]]:foldmethod=marker diff --git a/zinit.zsh b/zinit.zsh index bddbd8f0b..862f593eb 100644 --- a/zinit.zsh +++ b/zinit.zsh @@ -3281,4 +3281,14 @@ zle -N zi-browse-symbol-pbackwards zi-browse-symbol zle -N zi-browse-symbol-pforwards zi-browse-symbol bindkey "^K" zi-browse-symbol +# A custom completion of plugin ids (alt-a) and of ice names (alt-c) +zinit null light-mode autoload'zi-action-complete;zi-process-buffer' for %$ZINIT[BIN_DIR] +zle -N zi-action-complete +zle -N zi-action-complete-ice zi-action-complete +# Alt-A and Alt-C are default. +zstyle -s ":zinit:action-complete:plugin-id" key ZINIT_TMP || ZINIT_TMP='\eA' +[[ -n $ZINIT_TMP ]] && bindkey $ZINIT_TMP zi-action-complete +zstyle -s ":zinit:action-complete:ice" key ZINIT_TMP || ZINIT_TMP='\eC' +[[ -n $ZINIT_TMP ]] && bindkey $ZINIT_TMP zi-action-complete-ice + # vim:ft=zsh:sw=4:sts=4:et:foldmarker=[[[,]]]:foldmethod=marker