Problem depends on having a case insensitive matcher enabled for replicating with zsh -f, as far as I understand it is commonly used. At least I had on it for a long time.
The diff looks big, but it's mostly adding one extra check and nesting everything under it.
Have this in `.zshrc`.
`zstyle ':completion:*' matcher-list ':{a-zA-Z}={A-Za-z}'`
Consider this scenario:
```
% touch "Strategy TB" ; touch "Strategy Scenario"
```
Then user types `"St<TAB>"`. In the ideal world this produces common
prefix: `"Strategy "` then subsequent `<TAB>`s switch between HBT and
Scenario. But instead it completes `"Strategy t"`, note lower case `'t'`.
Subsequent press produces `"Strategy TB"` leaving `"Strategy Scenaio"`
inaccessible.
In `compmatch.c:join_strs()`, when two strings differ at a character
position, zsh tries matchers to find a common representation. The
bld_line() function builds a string that matches one of the inputs, but
the result is added to the output without verifying it also matches the
other input.
With the matcher `m:{a-zA-Z}={A-Za-z}`, the pattern `{a-zA-Z}`
matches any letter. So both `S` and `T` individually match it.
`bld_line()` succeeds for one string (e.g. lowercases `T` -> `t`), and that
character is unconditionally appended to the completion, producing
`"Sample t"` instead of stopping at the correct LCP `"Sample "`.
The fix adds a verification: after `bld_line()` succeeds, call it again
with reversed arguments to confirm the built line matches both strings
before adding it to the output.
diff --git a/Src/Zle/compmatch.c b/Src/Zle/compmatch.c
index bc82ff4d0..9b39dc243 100644
--- a/Src/Zle/compmatch.c
+++ b/Src/Zle/compmatch.c
@@ -2037,32 +2037,45 @@ join_strs(int la, char *sa, int lb, char *sb)
bp = &sa;
blp = &la;
}
- /* Now try to build a string that matches the other
- * string. */
+ /* Now try to build a string that matches the other string. */
if ((bl = bld_line(mp, line, *ap, *bp, *blp, 0))) {
- /* Found one, put it into the return string. */
- char *convstr =
- zlelineasstring(line, mp->llen, 0, &convlen,
- NULL, 0);
- if (rr <= convlen) {
- ptrdiff_t diff = rp - rs;
- int alloclen = (convlen > 20) ? convlen : 20;
-
- rs = realloc(rs, (rl += alloclen));
- rr += alloclen;
- rp = rs + diff;
- }
- memcpy(rp, convstr, convlen);
- rp += convlen;
- rr -= convlen;
- /* HERE: multibyte chars */
- *ap += mp->wlen;
- *alp -= mp->wlen;
-
- *bp += bl;
- *blp -= bl;
- t = 1;
- free(convstr);
+ /*
+ * Verify the built line also matches *ap (the
+ * matched string), not just *bp. This prevents
+ * adding characters that only match one string
+ * when the matcher pattern is too generic
+ * (e.g. {a-zA-Z} matching any letter).
+ */
+ VARARR(ZLE_CHAR_T, line2, mp->llen);
+ int bl2;
+
+ if ((bl2 = bld_line(mp, line2, *bp, *ap, *alp, 0))
+ && bl2 == bl) {
+ /* Found one, put it into the return string. */
+ char *convstr =
+ zlelineasstring(line, mp->llen, 0, &convlen,
+ NULL, 0);
+ if (rr <= convlen) {
+ ptrdiff_t diff = rp - rs;
+ int alloclen = (convlen > 20) ? convlen : 20;
+
+ rs = realloc(rs, (rl += alloclen));
+ rr += alloclen;
+ rp = rs + diff;
+ }
+ memcpy(rp, convstr, convlen);
+ rp += convlen;
+ rr -= convlen;
+ /* HERE: multibyte chars */
+ *ap += mp->wlen;
+ *alp -= mp->wlen;
+
+ *bp += bl;
+ *blp -= bl;
+ t = 1;
+ free(convstr);
+ } else
+ t = 0;
} else
t = 0;
}