From a7560ca7d7d89238cf4880620a27169ea30c21ae Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski Date: Wed, 6 Jul 2022 14:44:37 +0200 Subject: [PATCH 1/4] =?UTF-8?q?Initial=20version=20of=20an=20idea=20?= =?UTF-8?q?=E2=80=93=20an=20on=20demand=20plugin-id=20completor=20and?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verifier. One will be able to obtain, e.g.: zinit fzf-a » zinit junegunn/fzf zinit cd rip-a » zinit cd BurntSushi/ripgrep zinit cd tmux/tmux-v » "tmux/tmux" highlighted eighter in blue » or red, depending on existence of » such plugin and so on. The -a and -v shortcuts work with ALL commands, regardless if it's zinit command or not. Two autoloaded functions added, ziactioncomplete (the proper imple- mentation of the main features) and ziprocessbuffer (a very useful library function that takes care of $BUFFFER/$CURSOR processing). --- ziactioncomplete | 64 ++++++++++++++++++++++++++++++++ zinit.zsh | 7 ++++ ziprocessbuffer | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 ziactioncomplete create mode 100644 ziprocessbuffer diff --git a/ziactioncomplete b/ziactioncomplete new file mode 100644 index 000000000..21971de18 --- /dev/null +++ b/ziactioncomplete @@ -0,0 +1,64 @@ +# -*- mode: sh; sh-indentation: 4; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# Copyright (c) 2016-2020 Sebastian Gniazdowski and contributors. + +ziactioncomplete() +{ + # 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 + + # Invoke the helper/worker function that does all needed $BUFFER processing + ziprocessbuffer + integer i j pos=$CURSOR size=${#ZINIT_PB_WORDS} newcursor + local w=$ZINIT_PB_WORDS[ZINIT_PB_SELECTED_WORD] buf + local -a matches=( $ZINIT[PLUGINS_DIR]/*${w//\//---}*(-onND[1,8]) ) + REPLY=${${matches[1]:t}//---//} + 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 + # Check if we're at the word itself, + # or at some distance after it + if [[ $WIDGET == ziactioncompleteinsert ]] && (( 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 )) + 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/zinit.zsh b/zinit.zsh index bddbd8f0b..a1bab9265 100644 --- a/zinit.zsh +++ b/zinit.zsh @@ -3281,4 +3281,11 @@ 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'ziactioncomplete;ziprocessbuffer' for %$ZINIT[BIN_DIR] +zle -N ziactioncomplete +zle -N ziactioncompleteinsert ziactioncomplete +bindkey '\ea' ziactioncomplete +bindkey '\eA' ziactioncompleteinsert + # vim:ft=zsh:sw=4:sts=4:et:foldmarker=[[[,]]]:foldmethod=marker diff --git a/ziprocessbuffer b/ziprocessbuffer new file mode 100644 index 000000000..a03362728 --- /dev/null +++ b/ziprocessbuffer @@ -0,0 +1,96 @@ +# 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]}" + +# vim:ft=zsh From dbf6f4de0f9372e64886a75975e92e2363f898c9 Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski Date: Wed, 6 Jul 2022 20:32:13 +0200 Subject: [PATCH 2/4] Cycling and ice-completion (-A). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cycling will allow to choose a particular match, say for, e.g.: zinit at » zinit atclone » zinit atinit » » zinit atload » zinit atpull » » zinit run-atpull » zinit atclone" It works for plugin-id completion in the same way, i.e.: cycles through the matches. For ices, the value of the ice is preserved, e.g.: zinit at'./configure' » zinit atclone'./configure' » » zinit atinit'./configure' » » etc. --- ziactioncomplete => zi-action-complete | 79 +++++++++++++++++++++++--- ziprocessbuffer => zi-process-buffer | 20 ++++--- zinit.zsh | 13 +++-- 3 files changed, 90 insertions(+), 22 deletions(-) rename ziactioncomplete => zi-action-complete (51%) rename ziprocessbuffer => zi-process-buffer (82%) diff --git a/ziactioncomplete b/zi-action-complete similarity index 51% rename from ziactioncomplete rename to zi-action-complete index 21971de18..f19c5f88f 100644 --- a/ziactioncomplete +++ b/zi-action-complete @@ -1,25 +1,87 @@ # -*- mode: sh; sh-indentation: 4; indent-tabs-mode: nil; sh-basic-offset: 4; -*- # Copyright (c) 2016-2020 Sebastian Gniazdowski and contributors. -ziactioncomplete() +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 - ziprocessbuffer + # + + zi-process-buffer || return 1 + integer i j pos=$CURSOR size=${#ZINIT_PB_WORDS} newcursor - local w=$ZINIT_PB_WORDS[ZINIT_PB_SELECTED_WORD] buf - local -a matches=( $ZINIT[PLUGINS_DIR]/*${w//\//---}*(-onND[1,8]) ) - REPLY=${${matches[1]:t}//---//} + 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 - # Check if we're at the word itself, - # or at some distance after it - if [[ $WIDGET == ziactioncompleteinsert ]] && (( pos > (ZINIT_PB_WORDS_BEGINNINGS[i] + ${#ZINIT_PB_WORDS[i]} - 1) )); 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 @@ -45,6 +107,7 @@ ziactioncomplete() # 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 diff --git a/ziprocessbuffer b/zi-process-buffer similarity index 82% rename from ziprocessbuffer rename to zi-process-buffer index a03362728..b63e65c05 100644 --- a/ziprocessbuffer +++ b/zi-process-buffer @@ -3,7 +3,7 @@ # $2 - optional parameter containing cursor (default is $CURSOR) # # Output: -# ZINIT_PB_WORDS - split of "$1" into shell words; array +# 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 @@ -22,10 +22,10 @@ local cursor="${2:-$CURSOR}" ZINIT_PB_WORDS=( "${(Z+n+)buf}" ) ZINIT_PB_SPACES=( ) ZINIT_PB_WORDS_BEGINNINGS=( ) -ZINIT_PB_SELECTED_WORD="-1" +ZINIT_PB_SELECTED_WORD=-1 # (Z+n+) will return 1 element for buf that is empty or only whitespace -if [[ "$buf" = ( |$'\t')# ]]; then +if [[ $buf = ( |$'\t')# ]]; then ZINIT_PB_WORDS=( ) integer nwords=0 else @@ -63,29 +63,29 @@ for (( i=1; i<=nwords; i++ )); do # This is the beginning of current word ZINIT_PB_WORDS_BEGINNINGS[i]=$(( char_count + 1 )) # Remember the spaces - ZINIT_PB_SPACES[i]="$MATCH" + ZINIT_PB_SPACES[i]=$MATCH # Remove the word wordlen="${#word}" - [[ "${buf[1,wordlen]}" != "$word" ]] && return 1 # should not happen unless bug in (z) + [[ "${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 )) + [[ $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" + [[ $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" +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 )) +[[ $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 )) @@ -93,4 +93,6 @@ 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 diff --git a/zinit.zsh b/zinit.zsh index a1bab9265..862f593eb 100644 --- a/zinit.zsh +++ b/zinit.zsh @@ -3282,10 +3282,13 @@ 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'ziactioncomplete;ziprocessbuffer' for %$ZINIT[BIN_DIR] -zle -N ziactioncomplete -zle -N ziactioncompleteinsert ziactioncomplete -bindkey '\ea' ziactioncomplete -bindkey '\eA' ziactioncompleteinsert +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 From ff6b3ee2d96a97ecb10968899b929399e19c703b Mon Sep 17 00:00:00 2001 From: vladislav doster <10052309+vladdoster@users.noreply.github.com> Date: Fri, 30 Sep 2022 13:32:05 -0500 Subject: [PATCH 3/4] Update zi-action-complete --- zi-action-complete | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zi-action-complete b/zi-action-complete index f19c5f88f..4aaff3954 100644 --- a/zi-action-complete +++ b/zi-action-complete @@ -1,4 +1,8 @@ -# -*- mode: sh; sh-indentation: 4; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +#!/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() From fd56c9b45b5ddd6cb72efe0ed825d668358d18e3 Mon Sep 17 00:00:00 2001 From: vladislav doster <10052309+vladdoster@users.noreply.github.com> Date: Fri, 30 Sep 2022 13:32:19 -0500 Subject: [PATCH 4/4] Update zi-process-buffer --- zi-process-buffer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zi-process-buffer b/zi-process-buffer index b63e65c05..f62c612f9 100644 --- a/zi-process-buffer +++ b/zi-process-buffer @@ -95,4 +95,4 @@ ZINIT_PB_RIGHT="${word[diff+1,-1]}" [[ $ZINIT_PB_SELECTED_WORD -gt 0 ]] -# vim:ft=zsh +# vim:ft=zsh:sw=2:sts=2:et:foldmarker=[[[,]]]:foldmethod=marker