From 7363ebbeb2c5f05ed9880c2ce24e54de773fa540 Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski Date: Wed, 6 Jul 2022 14:44:37 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20version=20of=20an=20idea=20=E2=80=93?= =?UTF-8?q?=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 d3b6b5e19..1a25e496f 100644 --- a/zinit.zsh +++ b/zinit.zsh @@ -3232,4 +3232,11 @@ if [[ -e ${${ZINIT[BIN_DIR]}}/zmodules/Src/zdharma/zplugin.so ]] { # Create so that for sure no warncreateglobal warning is issued typeset -g REPLY + +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