diff --git a/Functions/Zle/transpose-segments b/Functions/Zle/transpose-segments new file mode 100644 index 0000000..796ac18 --- /dev/null +++ b/Functions/Zle/transpose-segments @@ -0,0 +1,134 @@ +# Transpose segments, i.e. parts of lines obtained by (z) flag, i.e. +# as if zsh parsed the line. +# +# Code to activate the functionality with binding to Alt-t: +# autoload transpose-segments +# zle -N transpose-segments +# bindkey "^[t" transpose-segments + +local curcontext=":zle:$WIDGET" +local MATCH MBEGIN MEND i + +# Will remember white spaces before each segment +typeset -a spaces +spaces=() + +# Working variable for $BUFFER +local buf="$BUFFER" + +# Split the buffer +typeset -a bufarr +bufarr=( "${(z)buf}" ) + +# Output array, needed to skip (z)-generated semicolons +typeset -a outarr +outarr=() + +integer size="$#bufarr" +integer char_count=0 +integer selected_segment=0 +local newbuf newspaces + +# (z) handles spaces nicely, but we need them for the user +for (( i=1; i<=size; i++ )); do + local segment="$bufarr[i]" + + # In general, $buf can start with white spaces + # We will not search for them, but instead segment's + # leading character, negated. This is an ambition + # to completely avoid character classes + + newbuf="${buf##(#m)[^$segment[1]]#}" + newspaces="$MATCH" + + # Is this a last segment? + if [ "$i" -lt "$size" ]; then + # If there was (z)-added ";" then $newspaces will contain char $nextsegment[1] + # because the "${buf##..}" above will move onward in $buf + # Also, correct detection of spaces implies that $newbuf[1] = $segment[1] + local nextsegment="$bufarr[i+1]" + if [[ ("${newspaces/$nextsegment[1]/}" = "$newspaces") + && "$newbuf[1]" = "$segment[1]" ]]; then + # Normal situation, white space before $segment + spaces[i]="$newspaces" + outarr[i]="$segment" + buf="$newbuf" + MATCH="" + buf="${buf#(#m)$segment}" + + # If segment not found, return from the function doing nothing + # This shoudln't happen + [ -z "$MATCH" ] && return 0; + + char_count=char_count+"$#newspaces"+"$#segment" + else + # Special situation: we searched for [^;], but ";" was added + # by (z) flag, and we ended matching not only white spaces + # but also something from the next segment + # Or, if (z)-added ";" was one of last ones, then $segment[1] + # != $newbuf[1] + # + # This not-actual segment should be ignored + spaces[i]="" + outarr[i]="" + fi + else + # Last segment + if [[ "$newbuf[1]" = "$segment[1]" ]]; then + spaces[i]="$newspaces" + outarr[i]="$segment" + + buf="$newbuf" + MATCH="" + buf="${buf#(#m)$segment}" + + # If segment not found, return from the function doing nothing + # This shoudln't happen + [ -z "$MATCH" ] && return 0 + + char_count=char_count+"$#segment"+"$#newspaces" + else + # This handles multiple (z)-added ";" at the end (i.e. newlines) + outarr[i]="" + spaces[i]="$newspaces" + fi + fi + + # Detect which segment is active + [[ "$selected_segment" -eq 0 && "$char_count" -ge "$CURSOR" ]] && selected_segment=i +done + +# What's left in $buf can be only white spaces +spaces[i]="$buf" +char_count=char_count+"$#buf" + +# Detect which segment is active - this takes into account possible trailing white spaces +[[ "$selected_segment" -eq 0 && "$char_count" -ge "$CURSOR" ]] && selected_segment=i-1 + +# No active segment found (shouldn't happen)? Return +[ "$selected_segment" -eq "0" ] && return 0 + +# First find actual previous segment +integer prev_segment=0 +for (( i=selected_segment-1; i>0; i-- )) [ -n "$outarr[i]" ] && { prev_segment=i; break } +# No actual previous segment? Return doing nothing +[ "$prev_segment" = "0" ] && return 0 + +# Swap segments +local tmp="$outarr[selected_segment]" +outarr[selected_segment]="$outarr[prev_segment]" +outarr[prev_segment]="$tmp" + +# Build BUFFER +integer newcursor +BUFFER="" +for (( i=1; i<=size; i++ )); do + BUFFER+="$spaces[i]$outarr[i]" + [ "$i" = "$selected_segment" ] && newcursor="$#BUFFER" +done +CURSOR="$newcursor" + +# Append final white spaces +BUFFER+="$spaces[i]" + +return 0