Subject: Re: New widget "transpose-segments"
From: Sebastian Gniazdowski <[email-masked]>
To: Zsh hackers list <[email-masked]>
Date: Sat, 9 Jan 2016 16:57:12 +0100
List-Id: Zsh Workers List <zsh-workers.zsh.org>

The forgotten patch and file

Best regards,
Sebastian Gniazdowski
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