zsh-workers
 help / color / mirror / code / Atom feed
* bug with camel case and delete-whole-word-match function
       [not found] <588168129.3340906.1467709726474.JavaMail.yahoo.ref@mail.yahoo.com>
@ 2016-07-05  9:08 ` Oliver Kiddle
  2016-07-05 10:19   ` Peter Stephenson
  2016-07-26 15:51   ` Peter Stephenson
  0 siblings, 2 replies; 12+ messages in thread
From: Oliver Kiddle @ 2016-07-05  9:08 UTC (permalink / raw)
  To: zsh-workers

I took a look at the word style widgets in the contributed functions with a view to perhaps adding a vim style text object based on them.

delete-whole-word-match appears to be the most useful example because it operates on a whole word rather than just forward of backward. Unfortunately, it doesn't seem to work too well for the subword (camel case) word style.

The presence of white space before the cursor position is used as an indication that the cursor is on the first character of the word but
with camel case, there isn't necessarily any whitespace.

Perhaps I'm missing something in the configuration but I have:
  autoload -U delete-whole-word-match
  zle -N delete-whole-word-match
  zstyle ':zle:*' word-style normal-subword
  bindkey '^K delete-whole-word-match

On the first character of a camel case word, both the current and previous word are deleted. On the last letter, it deletes up to the next real whitespace.

Oliver


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-05  9:08 ` bug with camel case and delete-whole-word-match function Oliver Kiddle
@ 2016-07-05 10:19   ` Peter Stephenson
  2016-07-05 16:12     ` Bart Schaefer
  2016-07-23 23:06     ` Oliver Kiddle
  2016-07-26 15:51   ` Peter Stephenson
  1 sibling, 2 replies; 12+ messages in thread
From: Peter Stephenson @ 2016-07-05 10:19 UTC (permalink / raw)
  To: zsh-workers

On Tue, 05 Jul 2016 09:08:46 +0000 (UTC)
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> I took a look at the word style widgets in the contributed functions
> with a view to perhaps adding a vim style text object based on them.
> 
> delete-whole-word-match appears to be the most useful example because
> it operates on a whole word rather than just forward of
> backward. Unfortunately, it doesn't seem to work too well for the
> subword (camel case) word style.
> 
> The presence of white space before the cursor position is used as an
> indication that the cursor is on the first character of the word but
> with camel case, there isn't necessarily any whitespace.
>
> On the first character of a camel case word, both the current and
> previous word are deleted.
>
> On the last letter, it deletes up to the next real whitespace.

These are related, but the first one's much harder to fix.

If you have "ThisIsSomeWords" and you're on the "e" then the relevant
bits of the split are "Som" (word before cursor) and "e" (word after
cursor), with whitespace before and after cursor empty.  The function
didn't recognise that "me" should be considered a word segment because
you had started the word at "So"; the reason it only happened on the
last character was because it previously assumed that must be the start
of a new word and this didn't match because "Words" followed
immediately.  That's easy enough to fix or at least improve --- detect
what we've been told is the start of a word is a character that normally
wouldn't be in subword mode when there's no white space around.

However, if you're on the "S", you get "Is" before and "Some" after.
Again there's no white space, so there's nothing to indicate to the
calling function that these are two separate words rather than bits of
the same word.  So I think we'd need to add some extra signalling from
match-words-by-style to indicate "I'm at a word start" whether or not
there's white space, which needs some thinking about.

diff --git a/Functions/Zle/match-words-by-style b/Functions/Zle/match-words-by-style
index 54e019d..1a3e78c 100644
--- a/Functions/Zle/match-words-by-style
+++ b/Functions/Zle/match-words-by-style
@@ -202,7 +202,7 @@ if [[ $wordstyle = *subword* ]]; then
   # followed by a lower case letter, or an upper case letter at
   # the start of a group of upper case letters.  To make
   # it easier to be consistent, we just use anything that
-  # isn't an upper case characer instead of a lower case
+  # isn't an upper case character instead of a lower case
   # character.
   # Here the initial "*" will match greedily, so we get the
   # last such match, as we want.
@@ -237,6 +237,12 @@ if [[ $wordstyle = *subword* ]]; then
 	  -n $match[2] ]]; then
     # Yes, so the last one is new word boundary.
     (( epos = ${#match[1]} - 1 ))
+    # Otherwise, are we in the middle of a word?
+    # In other, er, words, we've got something on the left with no
+    # white space following and something that doesn't start a word here.
+  elif [[ -n $word1 && -z $ws1 && -z $ws2 && \
+    $word2 = (#b)([^${~subwordrange}]##)* ]]; then
+    (( epos = ${#match[1]} ))
     # Otherwise, do we have upper followed by non-upper not
     # at the start?  Ignore the initial character, we already
     # know it's a word boundary so it can be an upper case character


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-05 10:19   ` Peter Stephenson
@ 2016-07-05 16:12     ` Bart Schaefer
  2016-07-05 16:28       ` Peter Stephenson
  2016-07-23 23:06     ` Oliver Kiddle
  1 sibling, 1 reply; 12+ messages in thread
From: Bart Schaefer @ 2016-07-05 16:12 UTC (permalink / raw)
  To: zsh-workers

On Jul 5, 11:19am, Peter Stephenson wrote:
} Subject: Re: bug with camel case and delete-whole-word-match function
}
} However, if you're on the "S", you get "Is" before and "Some" after.
} Again there's no white space, so there's nothing to indicate to the
} calling function that these are two separate words rather than bits of
} the same word.

Maybe I'm missing something, but shouldn't every capital letter be
treated as the start of a word in this situation, even if it's under
the cursor?


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-05 16:12     ` Bart Schaefer
@ 2016-07-05 16:28       ` Peter Stephenson
  0 siblings, 0 replies; 12+ messages in thread
From: Peter Stephenson @ 2016-07-05 16:28 UTC (permalink / raw)
  To: zsh-workers

On Tue, 05 Jul 2016 09:12:01 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:

> On Jul 5, 11:19am, Peter Stephenson wrote:
> } Subject: Re: bug with camel case and delete-whole-word-match function
> }
> } However, if you're on the "S", you get "Is" before and "Some" after.
> } Again there's no white space, so there's nothing to indicate to the
> } calling function that these are two separate words rather than bits of
> } the same word.
> 
> Maybe I'm missing something, but shouldn't every capital letter be
> treated as the start of a word in this situation, even if it's under
> the cursor?

Yes, that's what happens.  But the caller doesn't know the difference
between this and getting "Som" and "e" where there's *no* start-of-word
under the cursor, just parts of the word before and on/after.  With
standard word matching it can tell by looking at white space, here it
could only tell by checking again if it's *really* a start of word.
That additional check is the issue.

Because, in normal cases, (word-bit-before '' '' word-bit-after) always
indicates two parts of the same word, the caller will naturally assume
that here unless it has the extra test.  Hence it's the "Is" "Some" that
behaves incorrectly, rather than the "Som" "e".

pws


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-05 10:19   ` Peter Stephenson
  2016-07-05 16:12     ` Bart Schaefer
@ 2016-07-23 23:06     ` Oliver Kiddle
  2016-07-24 19:44       ` Peter Stephenson
  2016-07-26 13:52       ` Peter Stephenson
  1 sibling, 2 replies; 12+ messages in thread
From: Oliver Kiddle @ 2016-07-23 23:06 UTC (permalink / raw)
  To: zsh-workers

On 5 Jul, Peter wrote:
> If you have "ThisIsSomeWords"

> However, if you're on the "S", you get "Is" before and "Some" after.
> Again there's no white space, so there's nothing to indicate to the
> calling function that these are two separate words rather than bits of
> the same word.  So I think we'd need to add some extra signalling from
> match-words-by-style to indicate "I'm at a word start" whether or not
> there's white space, which needs some thinking about.

Do we need to keep the existing seven elements of matched_words
unchanged for backwards compatibility? Not that I can think of a
particularly obvious way to augment it for this case. May be just
a 1/0 indicator for start of word is the simplest. It also seems
to lack having a <whitespace before the word> field.

I've attached a patch for a select-word-match function which uses the
mechanism for a vim style text object. In the process, I've found a
couple of other issues with match-words-by-style:

One is that if the cursor is in the middle of a block of whitespace
at the end of the line, the 4th element (whitespace after cursor)
is empty while element 7 contains the whitespace.
A similar issue occurs at the start of the line - element 1 contains
whitespace while element 3 doesn't.

The other issue is that with the shell word style, it'll put whitespace
at the end of element 5 instead of in element 6.

Oliver

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 1d2b7ca..c3dec34 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -1940,6 +1940,8 @@ tindex(transpose-words-match)
 tindex(capitalize-word-match)
 tindex(up-case-word-match)
 tindex(down-case-word-match)
+tindex(delete-whole-word-match)
+tindex(select-word-match)
 tindex(select-word-style)
 tindex(match-word-context)
 tindex(match-words-by-style)
@@ -1947,12 +1949,14 @@ xitem(tt(forward-word-match), tt(backward-word-match))
 xitem(tt(kill-word-match), tt(backward-kill-word-match))
 xitem(tt(transpose-words-match), tt(capitalize-word-match))
 xitem(tt(up-case-word-match), tt(down-case-word-match))
+xitem(tt(delete-whole-word-match), tt(select-word-match))
 item(tt(select-word-style), tt(match-word-context), tt(match-words-by-style))(
-The eight `tt(-match)' functions are drop-in replacements for the
+The first eight `tt(-match)' functions are drop-in replacements for the
 builtin widgets without the suffix.  By default they behave in a similar
 way.  However, by the use of styles and the function tt(select-word-style),
-the way words are matched can be altered.  For comparison, the widgets
-described in ifzman(zmanref(zshzle) under Text Objects)\
+the way words are matched can be altered. tt(select-word-match) is intended
+to be used as a text object in vi mode but with custom word styles. For
+comparison, the widgets described in ifzman(zmanref(zshzle) under Text Objects)\
 ifnzman(noderef(Text Objects)) use fixed definitions of words, compatible
 with the tt(vim) editor.
 
@@ -1960,7 +1964,7 @@ The simplest way of configuring the functions is to use
 tt(select-word-style), which can either be called as a normal function with
 the appropriate argument, or invoked as a user-defined widget that will
 prompt for the first character of the word style to be used.  The first
-time it is invoked, the eight tt(-match) functions will automatically
+time it is invoked, the first eight tt(-match) functions will automatically
 replace the builtin versions, so they do not need to be loaded explicitly.
 
 The word styles available are as follows.  Only the first character
diff --git a/Functions/Zle/select-word-match b/Functions/Zle/select-word-match
new file mode 100644
index 0000000..24620c9
--- /dev/null
+++ b/Functions/Zle/select-word-match
@@ -0,0 +1,121 @@
+# Select the entire word around the cursor. Intended for use as
+# a vim-style text object in vi mode but with customisable
+# word boundaries.
+#
+# For example:
+#   autoload -U select-word-match
+#   zle -N select-in-camel select-word-match
+#   bindkey -M viopp ic select-in-camel
+#   zstyle ':zle:*-camel' word-style normal-subword
+
+emulate -L zsh
+setopt extendedglob
+
+local curcontext=:zle:$WIDGET
+local -a matched_words
+# Start and end of range of characters
+integer pos1 pos2 num=${NUMERIC:-1}
+local style word
+
+# choose between inner word or a word style of widget
+for style in $1 ${${WIDGET#*-}[1]} $KEYS[1] "i"; do
+  [[ $style = [ai] ]] && break
+done
+
+autoload -Uz match-words-by-style
+
+while (( num-- )); do
+  if (( MARK > CURSOR )); then
+    # if cursor is at the start of the selection, just move back a word
+    match-words-by-style
+    if [[ $style = i && -n $matched_words[3] ]]; then
+      word=$matched_words[3]
+    else
+      word=$matched_words[2]$matched_words[3]
+    fi
+    if [[ -n $word ]]; then
+      (( CURSOR -= ${#word} ))
+    else
+      return 1
+    fi
+  elif (( MARK >= 0 && MARK < CURSOR )); then
+    # cursor at the end, move forward a word
+    (( CURSOR+1 == $#BUFFER )) && return 1
+    (( CURSOR++ ))
+    match-words-by-style
+    if [[ -n $matched_words[4] ]]; then
+      if [[ $style = i ]]; then
+	# just skip the whitespace
+	word=$matched_words[4]
+      else
+	# skip the whitespace plus word
+	word=$matched_words[4]$matched_words[5]
+      fi
+    else
+      if [[ $style = i ]]; then
+	# skip the word
+	word=$matched_words[5]
+      else
+	# skip word and following whitespace
+	word=$matched_words[5]$matched_words[6]
+      fi
+    fi
+    (( CURSOR += ${#word} - 1 ))
+  else
+    match-words-by-style
+
+    if [[ -n "${matched_words[3]}" ]]; then
+      # There's whitespace before the cursor, so the word we are selecting
+      # starts at the cursor position.
+      pos1=$CURSOR
+    else
+      # No whitespace before us, so select any wordcharacters there.
+      pos1="${#matched_words[1]}"
+    fi
+
+    if [[ -n "${matched_words[4]}" ]]; then
+      if [[ -n "${matched_words[3]}" ]] || (( CURSOR == 0 )); then
+        # whitespace either side, select it
+	(( pos1 = CURSOR - ${#matched_words[3]} ))
+	(( pos2 = CURSOR + ${#matched_words[4]} ))
+      else
+	# There's whitespace at the cursor position, so only select
+	# up to the cursor position.
+	(( pos2 = CURSOR + 1 ))
+      fi
+    else
+      # No whitespace at the cursor position, so select the
+      # current character and any following wordcharacters.
+      (( pos2 = CURSOR + ${#matched_words[5]} ))
+    fi
+
+    if [[ $style = a ]]; then
+      if [[ -n "${matched_words[4]}"  && ( -n "${matched_words[3]}" || CURSOR -eq 0 ) ]]; then
+	# in the middle of whitespace so grab a word
+	if [[ -n "${matched_words[5]}" ]]; then
+	  (( pos2 += ${#matched_words[5]} )) # preferably the one after
+	else
+	  (( pos1 -= ${#matched_words[2]} )) # otherwise the one before
+	fi
+      elif [[ -n "${matched_words[6]}" ]]; then
+	(( pos2 += ${#matched_words[6]} ))
+      elif [[ -n "${matched_words[3]}" ]]; then
+	# couldn't grab whitespace forwards so try backwards
+	(( pos1 -= ${#matched_words[3]} ))
+      elif (( pos1 > 0 )); then
+	# There might have been whitespace before the word
+	(( CURSOR = pos1 ))
+	match-words-by-style
+	if [[ -n "${matched_words[3]}" ]]; then
+	  (( pos1 -= ${#matched_words[3]} ))
+	fi
+      fi
+    fi
+
+    (( MARK = pos1, CURSOR = pos2-1 ))
+  fi
+done
+
+if [[ $KEYMAP == vicmd ]] && (( !REGION_ACTIVE )); then
+  (( CURSOR++ )) # Need to include cursor position for operators
+fi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-23 23:06     ` Oliver Kiddle
@ 2016-07-24 19:44       ` Peter Stephenson
  2016-07-26 13:52       ` Peter Stephenson
  1 sibling, 0 replies; 12+ messages in thread
From: Peter Stephenson @ 2016-07-24 19:44 UTC (permalink / raw)
  To: zsh-workers

On Sun, 24 Jul 2016 01:06:02 +0200
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> On 5 Jul, Peter wrote:
> > If you have "ThisIsSomeWords"
> 
> > However, if you're on the "S", you get "Is" before and "Some" after.
> > Again there's no white space, so there's nothing to indicate to the
> > calling function that these are two separate words rather than bits of
> > the same word.  So I think we'd need to add some extra signalling from
> > match-words-by-style to indicate "I'm at a word start" whether or not
> > there's white space, which needs some thinking about.
> 
> Do we need to keep the existing seven elements of matched_words
> unchanged for backwards compatibility? Not that I can think of a
> particularly obvious way to augment it for this case. May be just
> a 1/0 indicator for start of word is the simplest. It also seems
> to lack having a <whitespace before the word> field.

Yes, something like that.  I was wondering if it was time to keep the
current way for backward compatibility but switch to a keyword-based
(associative array?) system for future enhancements.

> One is that if the cursor is in the middle of a block of whitespace
> at the end of the line, the 4th element (whitespace after cursor)
> is empty while element 7 contains the whitespace.
> A similar issue occurs at the start of the line - element 1 contains
> whitespace while element 3 doesn't.

Might simply not be using an inclusive enough type of white space?

pws


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-23 23:06     ` Oliver Kiddle
  2016-07-24 19:44       ` Peter Stephenson
@ 2016-07-26 13:52       ` Peter Stephenson
  2016-07-26 18:22         ` Oliver Kiddle
  1 sibling, 1 reply; 12+ messages in thread
From: Peter Stephenson @ 2016-07-26 13:52 UTC (permalink / raw)
  To: zsh-workers

On Sun, 24 Jul 2016 01:06:02 +0200
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> One is that if the cursor is in the middle of a block of whitespace
> at the end of the line, the 4th element (whitespace after cursor)
> is empty while element 7 contains the whitespace.
> A similar issue occurs at the start of the line - element 1 contains
> whitespace while element 3 doesn't.

I'm not sure what you're testing.  I've put a test function below and
ran it with

mwbs-test -w normal-subword $'one two ThreeFour ' $' \nFiveSix seven'

and I get

      start: 'one two Three'
wd-before-c: 'Four'
ws-before-c: ' '
 ws-after-c: ' 
'
 wd-after-c: 'Five'
 ws-after-w: ''
        end: 'Six seven'

which is what I expect.  Similarly at the start of the next line.  Do
you get something different, or isn't it testing for the problem at all?

> The other issue is that with the shell word style, it'll put whitespace
> at the end of element 5 instead of in element 6.

Aagain, I get:

mwbs-test -w shell $'one two ThreeFour \n ' $' FiveSix seven'

      start: 'one two '
wd-before-c: 'ThreeFour'
ws-before-c: ' 
 '
 ws-after-c: ' '
 wd-after-c: 'FiveSix'
 ws-after-w: ' '
        end: 'seven'

pws


# mwbs-test
autoload -Uz match-words-by-style

local wordstyle=normal-subword
local opt
while getopts "w:" opt; do
  case $opt in
    (w)
    wordstyle=$OPTARG
    ;;
    (*)
    return 1
    ;;
  esac
done
shift $(( OPTIND - 1 ))

if (( $# != 2 )); then
  print "Usage: mwbs-test LBUFFER RBUFFER" >&2
  return 1
fi

local -a matched_words

local LBUFFER=$1 RBUFFER=$2

match-words-by-style -w $wordstyle || return

print -r "\
      start: '$matched_words[1]'
wd-before-c: '$matched_words[2]'
ws-before-c: '$matched_words[3]'
 ws-after-c: '$matched_words[4]'
 wd-after-c: '$matched_words[5]'
 ws-after-w: '$matched_words[6]'
        end: '$matched_words[7]'
"


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-05  9:08 ` bug with camel case and delete-whole-word-match function Oliver Kiddle
  2016-07-05 10:19   ` Peter Stephenson
@ 2016-07-26 15:51   ` Peter Stephenson
  2016-07-26 16:00     ` Peter Stephenson
  1 sibling, 1 reply; 12+ messages in thread
From: Peter Stephenson @ 2016-07-26 15:51 UTC (permalink / raw)
  To: zsh-workers

On Tue, 05 Jul 2016 09:08:46 +0000 (UTC)
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> On the first character of a camel case word, both the current and
> previous word are deleted.

This should fix this in a way that makes it easy to add new features.

pws

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index c3dec34..5a7fc13 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2132,6 +2132,15 @@ non-word characters following that word (7) the remainder of the line.  Any
 of the elements may be an empty string; the calling function should test
 for this to decide whether it can perform its function.
 
+If the option -A is given to tt(match-words-by-style), then
+tt(matched_words) is an associative array and the seven values
+given above should be retrieved from it as elements named tt(start),
+tt(word-before-cursor), tt(ws-before-cursor), tt(ws-after-cursor),
+tt(word-after-cursor), tt(ws-after-word), and tt(end).  In addition
+the element tt(is-word-start) is 1 if the cursor is on the start
+of a word or subword, and 0 otherwise.  This form is recommended
+for future compatibility.
+
 It is possible to pass options with arguments to tt(match-words-by-style)
 to override the use of styles.  The options are:
 startsitem()
diff --git a/Functions/Zle/delete-whole-word-match b/Functions/Zle/delete-whole-word-match
index aece860..a07f236 100644
--- a/Functions/Zle/delete-whole-word-match
+++ b/Functions/Zle/delete-whole-word-match
@@ -12,30 +12,29 @@ emulate -L zsh
 setopt extendedglob
 
 local curcontext=:zle:$WIDGET
-local -a matched_words
+local -A matched_words
 # Start and end of range of characters to remove.
 integer pos1 pos2
 
 autoload -Uz match-words-by-style
-match-words-by-style
+match-words-by-style -A
 
-if [[ -n "${matched_words[3]}" ]]; then
-    # There's whitespace before the cursor, so the word we are deleting
-    # starts at the cursor position.
+if (( ${matched_words[is-word-start]} )); then
+    # The word we are deleting starts at the cursor position.
     pos1=$CURSOR
 else
-    # No whitespace before us, so delete any wordcharacters there.
-    pos1="${#matched_words[1]}"
+    # Not, so delete any wordcharacters before, too
+    pos1="${#matched_words[start]}"
 fi
 
-if [[ -n "${matched_words[4]}" ]]; then
+if [[ -n "${matched_words[ws-after-cursor]}" ]]; then
     # There's whitespace at the cursor position, so only delete
     # up to the cursor position.
     (( pos2 = CURSOR + 1 ))
 else
     # No whitespace at the cursor position, so delete the
     # current character and any following wordcharacters.
-    (( pos2 = CURSOR + ${#matched_words[5]} + 1 ))
+    (( pos2 = CURSOR + ${#matched_words[word-after-cursor]} + 1 ))
 fi
 
 # Move the cursor then delete the block in one go for the
diff --git a/Functions/Zle/match-words-by-style b/Functions/Zle/match-words-by-style
index 6cdec75..1110f76 100644
--- a/Functions/Zle/match-words-by-style
+++ b/Functions/Zle/match-words-by-style
@@ -5,8 +5,16 @@
 #    <whitespace-after-cursor> <word-after-cursor> <whitespace-after-word>
 #    <stuff-at-end>
 # where the cursor position is always after the third item and `after'
-# is to be interpreted as `after or on'.  Some
-# of the array elements will be empty; this depends on the style.
+# is to be interpreted as `after or on'.
+#
+# With the option -A, matched_words is an associative array; the
+# values above are now given by the elements named start, word-before-cursor,
+# ws-before-cursor, ws-after-cursor, word-after-cursor, ws-after-word,
+# end.  In addition, the element is-word-start is 1 if the cursor
+# is on the start of a word; this is non-trivial in the case of subword
+# (camel case) matching as there may be no white space to test.
+#
+# Some of the array elements will be empty; this depends on the style.
 # For example
 #    foo bar  rod stick
 #            ^
@@ -70,14 +78,19 @@ setopt extendedglob
 local wordstyle spacepat wordpat1 wordpat2 opt charskip wordchars wordclass
 local match mbegin mend pat1 pat2 word1 word2 ws1 ws2 ws3 skip
 local nwords MATCH MBEGIN MEND subwordrange
+integer use_assoc
 
 local curcontext=${curcontext:-:zle:match-words-by-style}
 
 autoload -Uz match-word-context
 match-word-context
 
-while getopts "w:s:c:C:r:" opt; do
+while getopts "Aw:s:c:C:r:" opt; do
   case $opt in
+    (A)
+    use_assoc=1
+    ;;
+
     (w)
     wordstyle=$OPTARG
     ;;
@@ -229,6 +242,8 @@ ws2=$match[1]
 word2=$match[2]
 ws3=$match[3]
 
+integer wordstart
+[[ -n $ws1 || -n $ws2 ]] && wordstart=1
 if [[ $wordstyle = *subword* ]]; then
   # Do we have a group of upper case characters at the start
   # of word2 (that don't form the entire word)?
@@ -249,6 +264,7 @@ if [[ $wordstyle = *subword* ]]; then
     # if it wants.
   elif [[ $word2 = (#b)(?[^${~subwordrange}]##)[${~subwordrange}]* ]]; then
     (( epos = ${#match[1]} ))
+    (( wordstart = 1 ))
   else
     (( epos = 0 ))
   fi
@@ -262,4 +278,21 @@ if [[ $wordstyle = *subword* ]]; then
   fi
 fi
 
-matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2")
+# matched_words should be local to caller.
+# Just fix type here.
+if (( use_assoc )); then
+  typeset -gA matched_words
+  matched_words=(
+    start              "$pat1"
+    word-before-cursor "$word1"
+    ws-before-cursor   "$ws1"
+    ws-after-cursor    "$ws2"
+    word-after-cursor  "$word2"
+    ws-after-word      "$ws3"
+    end                "$pat2"
+    is-word-start      $wordstart
+  )
+else
+  typeset -ga matched_words
+  matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2")
+fi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-26 15:51   ` Peter Stephenson
@ 2016-07-26 16:00     ` Peter Stephenson
  0 siblings, 0 replies; 12+ messages in thread
From: Peter Stephenson @ 2016-07-26 16:00 UTC (permalink / raw)
  To: zsh-workers

On Tue, 26 Jul 2016 16:51:14 +0100
Peter Stephenson <p.stephenson@samsung.com> wrote:
> ...In addition
> +the element tt(is-word-start) is 1 if the cursor is on the start
> +of a word or subword, and 0 otherwise.

Actually, if it's on a word start or after the end of the previous word,
i.e. there could be intervening white space.  This is OK if you want to
operate on the next word by default, but could at least be documented
better.  Or it could be 0 = in middle of word, 1 = right at start of
word, 2 = before start of word, or something.

pws


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-26 13:52       ` Peter Stephenson
@ 2016-07-26 18:22         ` Oliver Kiddle
  2016-07-27  8:54           ` Peter Stephenson
  0 siblings, 1 reply; 12+ messages in thread
From: Oliver Kiddle @ 2016-07-26 18:22 UTC (permalink / raw)
  To: zsh-workers

Peter wrote:
> > A similar issue occurs at the start of the line - element 1 contains
> > whitespace while element 3 doesn't.
>
> I'm not sure what you're testing.  I've put a test function below and
> ran it with
>
> mwbs-test -w normal-subword $'one two ThreeFour ' $' \nFiveSix seven'

I probably should have said start/end of the buffer rather than of the
line.
It is the output of the following two:
  mwbs-test '   ' '  word'
  mwbs-test 'word  ' '   '

In the latter case, this is:

      start: ''
wd-before-c: 'word'
ws-before-c: '  '
 ws-after-c: ''
 wd-after-c: ''
 ws-after-w: ''
        end: '   '

So the spaces go in end rather than ws-after-c.
Whenever the cursor is between actual words, ws-before-c and ws-after-c will
cover the full area of whitespace surrounding the cursor. I don't see
why it should be different when you've got the end/start of the buffer.
For comparison, try: mwbs-test 'word  ' '   x'

In vi word selection will grab a whole block of whitespace in these
cases.

> This should fix this in a way that makes it easy to add new features.

Thanks. Looks good to me.

> +If the option -A is given to tt(match-words-by-style), then

Given that it is the calling functions' responsibility to declare
matched_words, it could just use ${(t)matched_words} but I'm not
especially bothered.

Oliver


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-26 18:22         ` Oliver Kiddle
@ 2016-07-27  8:54           ` Peter Stephenson
  2016-07-27 23:05             ` Oliver Kiddle
  0 siblings, 1 reply; 12+ messages in thread
From: Peter Stephenson @ 2016-07-27  8:54 UTC (permalink / raw)
  To: zsh-workers

On Tue, 26 Jul 2016 20:22:05 +0200
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> It is the output of the following two:
>   mwbs-test '   ' '  word'
>   mwbs-test 'word  ' '   '
> 
> In the latter case, this is:
> 
>       start: ''
> wd-before-c: 'word'
> ws-before-c: '  '
>  ws-after-c: ''
>  wd-after-c: ''
>  ws-after-w: ''
>         end: '   '
> 
> So the spaces go in end rather than ws-after-c.

OK, we need to check that there actually is a word after the cursor, as
we should also do before setting is-word-start.

> > +If the option -A is given to tt(match-words-by-style), then
> 
> Given that it is the calling functions' responsibility to declare
> matched_words, it could just use ${(t)matched_words} but I'm not
> especially bothered.

Yes, that would probably be neater.

pws

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index c3dec34..8db7395 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2132,6 +2132,17 @@ non-word characters following that word (7) the remainder of the line.  Any
 of the elements may be an empty string; the calling function should test
 for this to decide whether it can perform its function.
 
+If the variable tt(matched_words) is defined by the caller to
+tt(match-words-by-style) as an associative array (tt(local -A
+matched_words)), then the seven values given above should be retrieved
+from it as elements named tt(start), tt(word-before-cursor),
+tt(ws-before-cursor), tt(ws-after-cursor), tt(word-after-cursor),
+tt(ws-after-word), and tt(end).  In addition the element
+tt(is-word-start) is 1 if the cursor is on the start of a word or
+subword, or on white space before it (the cases can be distinguished by
+testing the tt(ws-after-cursor) element) and 0 otherwise.  This form is
+recommended for future compatibility.
+
 It is possible to pass options with arguments to tt(match-words-by-style)
 to override the use of styles.  The options are:
 startsitem()
diff --git a/Functions/Zle/delete-whole-word-match b/Functions/Zle/delete-whole-word-match
index aece860..3d52dd3 100644
--- a/Functions/Zle/delete-whole-word-match
+++ b/Functions/Zle/delete-whole-word-match
@@ -12,30 +12,29 @@ emulate -L zsh
 setopt extendedglob
 
 local curcontext=:zle:$WIDGET
-local -a matched_words
+local -A matched_words
 # Start and end of range of characters to remove.
 integer pos1 pos2
 
 autoload -Uz match-words-by-style
 match-words-by-style
 
-if [[ -n "${matched_words[3]}" ]]; then
-    # There's whitespace before the cursor, so the word we are deleting
-    # starts at the cursor position.
+if (( ${matched_words[is-word-start]} )); then
+    # The word we are deleting starts at the cursor position.
     pos1=$CURSOR
 else
-    # No whitespace before us, so delete any wordcharacters there.
-    pos1="${#matched_words[1]}"
+    # Not, so delete any wordcharacters before, too
+    pos1="${#matched_words[start]}"
 fi
 
-if [[ -n "${matched_words[4]}" ]]; then
+if [[ -n "${matched_words[ws-after-cursor]}" ]]; then
     # There's whitespace at the cursor position, so only delete
     # up to the cursor position.
     (( pos2 = CURSOR + 1 ))
 else
     # No whitespace at the cursor position, so delete the
     # current character and any following wordcharacters.
-    (( pos2 = CURSOR + ${#matched_words[5]} + 1 ))
+    (( pos2 = CURSOR + ${#matched_words[word-after-cursor]} + 1 ))
 fi
 
 # Move the cursor then delete the block in one go for the
diff --git a/Functions/Zle/match-words-by-style b/Functions/Zle/match-words-by-style
index 6cdec75..fc59c27 100644
--- a/Functions/Zle/match-words-by-style
+++ b/Functions/Zle/match-words-by-style
@@ -5,8 +5,16 @@
 #    <whitespace-after-cursor> <word-after-cursor> <whitespace-after-word>
 #    <stuff-at-end>
 # where the cursor position is always after the third item and `after'
-# is to be interpreted as `after or on'.  Some
-# of the array elements will be empty; this depends on the style.
+# is to be interpreted as `after or on'.
+#
+# matched_words may be an associative array, in which case the
+# values above are now given by the elements named start, word-before-cursor,
+# ws-before-cursor, ws-after-cursor, word-after-cursor, ws-after-word,
+# end.  In addition, the element is-word-start is 1 if the cursor
+# is on the start of a word; this is non-trivial in the case of subword
+# (camel case) matching as there may be no white space to test.
+#
+# Some of the array elements will be empty; this depends on the style.
 # For example
 #    foo bar  rod stick
 #            ^
@@ -224,11 +232,18 @@ charskip=${(l:skip::?:)}
 
 eval pat2='${RBUFFER##(#b)('${charskip}${spacepat}')('\
 ${wordpat2}')('${spacepat}')}'
+if [[ -n $match[2] ]]; then
+  ws2=$match[1]
+  word2=$match[2]
+  ws3=$match[3]
+else
+  # No more words, so anything left is white space after cursor.
+  ws2=$RBUFFER
+  pat2=
+fi
 
-ws2=$match[1]
-word2=$match[2]
-ws3=$match[3]
-
+integer wordstart
+[[ ( -n $ws1 || -n $ws2 ) && -n $word2 ]] && wordstart=1
 if [[ $wordstyle = *subword* ]]; then
   # Do we have a group of upper case characters at the start
   # of word2 (that don't form the entire word)?
@@ -249,6 +264,7 @@ if [[ $wordstyle = *subword* ]]; then
     # if it wants.
   elif [[ $word2 = (#b)(?[^${~subwordrange}]##)[${~subwordrange}]* ]]; then
     (( epos = ${#match[1]} ))
+    (( wordstart = 1 ))
   else
     (( epos = 0 ))
   fi
@@ -262,4 +278,19 @@ if [[ $wordstyle = *subword* ]]; then
   fi
 fi
 
-matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2")
+# matched_words should be local to caller.
+# Just fix type here.
+if [[ ${(t)matched_words} = *association* ]]; then
+  matched_words=(
+    start              "$pat1"
+    word-before-cursor "$word1"
+    ws-before-cursor   "$ws1"
+    ws-after-cursor    "$ws2"
+    word-after-cursor  "$word2"
+    ws-after-word      "$ws3"
+    end                "$pat2"
+    is-word-start      $wordstart
+  )
+else
+  matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2")
+fi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: bug with camel case and delete-whole-word-match function
  2016-07-27  8:54           ` Peter Stephenson
@ 2016-07-27 23:05             ` Oliver Kiddle
  0 siblings, 0 replies; 12+ messages in thread
From: Oliver Kiddle @ 2016-07-27 23:05 UTC (permalink / raw)
  To: zsh-workers

Peter wrote:
> OK, we need to check that there actually is a word after the cursor, as
> we should also do before setting is-word-start.

Thanks. That now works nicely.

Attached patch adjusts select-word-match to take advantage of this.
I've also updated zstyle completion slightly for the word-style and
word-class styles. The documentation seems a bit confusing in the way it
mixes "normal" and "standard" and there might be an error there. I have
adjusted the word-context example for what looks like an error to me.

I've also now had a chance to experiment with select-word-match. I
had previously used select-bracketed for directory components and
lists using i/ a/ i, and a, etc. For things that are essentially
list separators, select-word-match works better because it can grab
the first and last components of a list and the a forms such as a/
will grab just one surrounding / rather than both. Unfortunately,
for the last component of a path, it'll grab the following whitespace
rather than the preceding slash but we can perhaps add a style for
patterns specifying preferred whitespace. That might have other
uses like prefering preceding whitespace over a following newline.

Oliver

diff --git a/Completion/Zsh/Command/_zstyle b/Completion/Zsh/Command/_zstyle
index 9a6d618..20ff47f 100644
--- a/Completion/Zsh/Command/_zstyle
+++ b/Completion/Zsh/Command/_zstyle
@@ -173,6 +173,7 @@ styles=(
   url-seps               e:
   whence                 e:
   word-chars             e:
+  word-class             e:
   word-style             e:word-style
   word-context           e:
 
@@ -241,11 +242,13 @@ while (( $#state )); do
   case "$state[1]" in
     (contexts)
       if [[ ! -prefix :*: ]]; then
-	_wanted contexts expl context compadd -P : -qS : completion vcs_info zftp
+	_wanted contexts expl context compadd -P : -qS : chpwd completion vcs_info zftp zle
       elif compset -P :completion:; then
         contexts=( functions _completers cmdorcont argument tag )
       elif compset -P :vcs_info:; then
         contexts=( vcs-string user-context repo-root-name )
+      elif compset -P :zle:; then
+	_wanted widgets expl widget _widgets -qS :
       fi
       if (( $#contexts )); then
         for ostate in $contexts; do
@@ -521,7 +524,7 @@ while (( $#state )); do
       ;;
 
     (word-style)
-      _wanted word-styles expl 'word style' compadd normal shell space
+      _wanted word-styles expl 'word style' compadd {normal,specified,unspecified,shell,whitespace}-subword
       ;;
 
     (vcs-string)
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 8db7395..00ed080 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2105,7 +2105,7 @@ Here are some examples of use of the tt(word-context) style to extend
 the context.
 
 example(zstyle ':zle:*' word-context \ 
-       "*/*" file "[[:space:]]" whitespace
+       "*/*" filename "[[:space:]]" whitespace
 zstyle ':zle:transpose-words:whitespace' word-style shell
 zstyle ':zle:transpose-words:filename' word-style normal
 zstyle ':zle:transpose-words:filename' word-chars '')
diff --git a/Functions/Zle/select-word-match b/Functions/Zle/select-word-match
index 24620c9..8440852 100644
--- a/Functions/Zle/select-word-match
+++ b/Functions/Zle/select-word-match
@@ -12,7 +12,7 @@ emulate -L zsh
 setopt extendedglob
 
 local curcontext=:zle:$WIDGET
-local -a matched_words
+local -A matched_words
 # Start and end of range of characters
 integer pos1 pos2 num=${NUMERIC:-1}
 local style word
@@ -28,10 +28,10 @@ while (( num-- )); do
   if (( MARK > CURSOR )); then
     # if cursor is at the start of the selection, just move back a word
     match-words-by-style
-    if [[ $style = i && -n $matched_words[3] ]]; then
-      word=$matched_words[3]
+    if [[ $style = i && -n $matched_words[ws-before-cursor] ]]; then
+      word=$matched_words[ws-before-cursor]
     else
-      word=$matched_words[2]$matched_words[3]
+      word=$matched_words[word-before-cursor]$matched_words[ws-before-cursor]
     fi
     if [[ -n $word ]]; then
       (( CURSOR -= ${#word} ))
@@ -43,41 +43,40 @@ while (( num-- )); do
     (( CURSOR+1 == $#BUFFER )) && return 1
     (( CURSOR++ ))
     match-words-by-style
-    if [[ -n $matched_words[4] ]]; then
+    if [[ -n $matched_words[ws-after-cursor] ]]; then
       if [[ $style = i ]]; then
 	# just skip the whitespace
-	word=$matched_words[4]
+	word=$matched_words[ws-after-cursor]
       else
 	# skip the whitespace plus word
-	word=$matched_words[4]$matched_words[5]
+	word=$matched_words[ws-after-cursor]$matched_words[word-after-cursor]
       fi
     else
       if [[ $style = i ]]; then
 	# skip the word
-	word=$matched_words[5]
+	word=$matched_words[word-after-cursor]
       else
 	# skip word and following whitespace
-	word=$matched_words[5]$matched_words[6]
+	word=$matched_words[word-after-cursor]$matched_words[ws-after-word]
       fi
     fi
     (( CURSOR += ${#word} - 1 ))
   else
     match-words-by-style
 
-    if [[ -n "${matched_words[3]}" ]]; then
-      # There's whitespace before the cursor, so the word we are selecting
-      # starts at the cursor position.
+    if (( ${matched_words[is-word-start]} )); then
+      # The word we are selecting starts at the cursor position.
       pos1=$CURSOR
     else
       # No whitespace before us, so select any wordcharacters there.
-      pos1="${#matched_words[1]}"
+      pos1="${#matched_words[start]}"
     fi
 
-    if [[ -n "${matched_words[4]}" ]]; then
-      if [[ -n "${matched_words[3]}" ]] || (( CURSOR == 0 )); then
+    if [[ -n "${matched_words[ws-after-cursor]}" ]]; then
+      if [[ -n "${matched_words[ws-before-cursor]}" ]] || (( CURSOR == 0 )); then
         # whitespace either side, select it
-	(( pos1 = CURSOR - ${#matched_words[3]} ))
-	(( pos2 = CURSOR + ${#matched_words[4]} ))
+	(( pos1 = CURSOR - ${#matched_words[ws-before-cursor]} ))
+	(( pos2 = CURSOR + ${#matched_words[ws-after-cursor]} ))
       else
 	# There's whitespace at the cursor position, so only select
 	# up to the cursor position.
@@ -86,28 +85,28 @@ while (( num-- )); do
     else
       # No whitespace at the cursor position, so select the
       # current character and any following wordcharacters.
-      (( pos2 = CURSOR + ${#matched_words[5]} ))
+      (( pos2 = CURSOR + ${#matched_words[word-after-cursor]} ))
     fi
 
     if [[ $style = a ]]; then
-      if [[ -n "${matched_words[4]}"  && ( -n "${matched_words[3]}" || CURSOR -eq 0 ) ]]; then
+      if [[ -n "${matched_words[ws-after-cursor]}"  && ( -n "${matched_words[ws-before-cursor]}" || CURSOR -eq 0 ) ]]; then
 	# in the middle of whitespace so grab a word
-	if [[ -n "${matched_words[5]}" ]]; then
-	  (( pos2 += ${#matched_words[5]} )) # preferably the one after
+	if [[ -n "${matched_words[word-after-cursor]}" ]]; then
+	  (( pos2 += ${#matched_words[word-after-cursor]} )) # preferably the one after
 	else
-	  (( pos1 -= ${#matched_words[2]} )) # otherwise the one before
+	  (( pos1 -= ${#matched_words[word-before-cursor]} )) # otherwise the one before
 	fi
-      elif [[ -n "${matched_words[6]}" ]]; then
-	(( pos2 += ${#matched_words[6]} ))
-      elif [[ -n "${matched_words[3]}" ]]; then
+      elif [[ -n "${matched_words[ws-after-word]}" ]]; then
+	(( pos2 += ${#matched_words[ws-after-word]} ))
+      elif [[ -n "${matched_words[ws-before-cursor]}" ]]; then
 	# couldn't grab whitespace forwards so try backwards
-	(( pos1 -= ${#matched_words[3]} ))
+	(( pos1 -= ${#matched_words[ws-before-cursor]} ))
       elif (( pos1 > 0 )); then
 	# There might have been whitespace before the word
 	(( CURSOR = pos1 ))
 	match-words-by-style
-	if [[ -n "${matched_words[3]}" ]]; then
-	  (( pos1 -= ${#matched_words[3]} ))
+	if [[ -n "${matched_words[ws-before-cursor]}" ]]; then
+	  (( pos1 -= ${#matched_words[ws-before-cursor]} ))
 	fi
       fi
     fi


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2016-07-27 23:12 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <588168129.3340906.1467709726474.JavaMail.yahoo.ref@mail.yahoo.com>
2016-07-05  9:08 ` bug with camel case and delete-whole-word-match function Oliver Kiddle
2016-07-05 10:19   ` Peter Stephenson
2016-07-05 16:12     ` Bart Schaefer
2016-07-05 16:28       ` Peter Stephenson
2016-07-23 23:06     ` Oliver Kiddle
2016-07-24 19:44       ` Peter Stephenson
2016-07-26 13:52       ` Peter Stephenson
2016-07-26 18:22         ` Oliver Kiddle
2016-07-27  8:54           ` Peter Stephenson
2016-07-27 23:05             ` Oliver Kiddle
2016-07-26 15:51   ` Peter Stephenson
2016-07-26 16:00     ` Peter Stephenson

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).