Skip to content

Commit 3b08eff

Browse files
authored
Merge pull request #1103 from carapace-sh/zsh-fix-quoted
zsh: fix quoted
2 parents 55a9678 + 6acfacc commit 3b08eff

File tree

8 files changed

+91
-47
lines changed

8 files changed

+91
-47
lines changed

docs/src/development/vhs/doubleQuoteOpen/out/doubleQuoteOpen.zsh.ascii

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example action embeddedP1 "embeddedP2\ with\ space"
25+
> example action embeddedP1 "embeddedP2 with space"
2626

2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example action embeddedP1 "embeddedP2\ with\ space"
33+
> example action embeddedP1 "embeddedP2 with space"
3434

3535

3636

docs/src/development/vhs/doubleQuotePartial/out/doubleQuotePartial.zsh.ascii

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example action embeddedP1 "embeddedP2\ with\ space"
25+
> example action embeddedP1 "embeddedP2 with space"
2626

2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example action embeddedP1 "embeddedP2\ with\ space"
33+
> example action embeddedP1 "embeddedP2 with space"
3434

3535

3636

docs/src/development/vhs/singleQuoteOpen/out/singleQuoteOpen.zsh.ascii

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example action embeddedP1 'embeddedP2\ with\ space'
25+
> example action embeddedP1 'embeddedP2 with space'
2626

2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example action embeddedP1 'embeddedP2\ with\ space'
33+
> example action embeddedP1 'embeddedP2 with space'
3434

3535

3636

docs/src/development/vhs/singleQuotePartial/out/singleQuotePartial.zsh.ascii

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example action embeddedP1 'embeddedP2\ with\ space'
25+
> example action embeddedP1 'embeddedP2 with space'
2626

2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example action embeddedP1 'embeddedP2\ with\ space'
33+
> example action embeddedP1 'embeddedP2 with space'
3434

3535

3636

docs/src/development/vhs/specialDoubleQuote/out/specialDoubleQuote.zsh.ascii

+12-12
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,27 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
26-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
25+
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
26+
`\`"
2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
34-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
35-
expected: "p1 & < > ' \" { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``"
36-
actual : "p1\\ \\&\\ \\<\\ \\>\\ \\'\\ \"\\ \\{\\ \\}\\ $\\ \\#\\ \\|\\ \\?
37-
\\ \\(\\ \\)\\ \\;\\ \\ \\[\\ \\]\\ \\*\\ \\\\ $\\(\\)\\ $\\{\\}\\ `\\ ``"
33+
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
34+
`\`"
35+
ok
3836
>
3937

38+
39+
4040
────────────────────────────────────────────────────────────────────────────────
41-
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
42-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
43-
expected: "p1 & < > ' \" { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``"
44-
actual : "p1\\ \\&\\ \\<\\ \\>\\ \\'\\ \"\\ \\{\\ \\}\\ $\\ \\#\\ \\|\\ \\?
45-
\\ \\(\\ \\)\\ \\;\\ \\ \\[\\ \\]\\ \\*\\ \\\\ $\\(\\)\\ $\\{\\}\\ `\\ ``"
41+
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
42+
`\`"
43+
ok
4644
>
4745

46+
47+
4848
────────────────────────────────────────────────────────────────────────────────

docs/src/development/vhs/specialSingleQuote/out/specialSingleQuote.zsh.ascii

+8-8
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@
2222

2323

2424
────────────────────────────────────────────────────────────────────────────────
25-
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
26-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
25+
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'
26+
2727

2828

2929

3030

3131

3232
────────────────────────────────────────────────────────────────────────────────
33-
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
34-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
35-
quote>
33+
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'
3634

35+
ok
36+
>
3737

3838

3939

4040
────────────────────────────────────────────────────────────────────────────────
41-
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
42-
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
43-
quote>
41+
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'
4442

43+
ok
44+
>
4545

4646

4747

internal/shell/zsh/action.go

+55-19
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,18 @@ var sanitizer = strings.NewReplacer(
1616
"\t", ``,
1717
)
1818

19-
// TODO verify these are correct/complete (copied from bash)
20-
var quoter = strings.NewReplacer(
19+
var quotingReplacer = strings.NewReplacer(
20+
`'`, `'\''`,
21+
)
22+
23+
var quotingEscapingReplacer = strings.NewReplacer(
24+
`\`, `\\`,
25+
`"`, `\"`,
26+
`$`, `\$`,
27+
"`", "\\`",
28+
)
29+
30+
var defaultReplacer = strings.NewReplacer(
2131
`\`, `\\`,
2232
`&`, `\&`,
2333
`<`, `\<`,
@@ -41,11 +51,17 @@ var quoter = strings.NewReplacer(
4151
`~`, `\~`,
4252
)
4353

54+
// additional replacement for use with `_describe` in shell script
55+
var describeReplacer = strings.NewReplacer(
56+
`\`, `\\`,
57+
`:`, `\:`,
58+
)
59+
4460
func quoteValue(s string) string {
4561
if strings.HasPrefix(s, "~/") || NamedDirectories.Matches(s) {
46-
return "~" + quoter.Replace(strings.TrimPrefix(s, "~")) // assume file path expansion
62+
return "~" + defaultReplacer.Replace(strings.TrimPrefix(s, "~")) // assume file path expansion
4763
}
48-
return quoter.Replace(s)
64+
return defaultReplacer.Replace(s)
4965
}
5066

5167
type state int
@@ -60,12 +76,18 @@ const (
6076
// Values need to end with `'` as well.
6177
// Weirdly regardless whether there are additional quotes within the word.
6278
QUOTING_STATE
63-
// Word starts and ends with quotes.
79+
// Word starts and ends with `"`.
6480
// Space suffix somehow ends up within the quotes.
6581
// `"action"<TAB>`
6682
// `"action "<CURSOR>`
6783
// Workaround for now is to force nospace.
68-
FULLY_QUOTED_STATE
84+
FULL_QUOTING_ESCAPING_STATE
85+
// Word starts and ends with `'`.
86+
// Space suffix somehow ends up within the quotes.
87+
// `'action'<TAB>`
88+
// `'action '<CURSOR>`
89+
// Workaround for now is to force nospace.
90+
FULL_QUOTING_STATE
6991
)
7092

7193
// ActionRawValues formats values for zsh
@@ -74,13 +96,16 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
7496
state := DEFAULT_STATE
7597
if err == nil {
7698
rawValue := splitted.CurrentToken().RawValue
99+
// TODO use token state to determine actual state (might have mixture).
77100
switch {
78101
case regexp.MustCompile(`^'$|^'.*[^']$`).MatchString(rawValue):
79102
state = QUOTING_STATE
80103
case regexp.MustCompile(`^"$|^".*[^"]$`).MatchString(rawValue):
81104
state = QUOTING_ESCAPING_STATE
82-
case regexp.MustCompile(`^".*"$|^'.*'$`).MatchString(rawValue):
83-
state = FULLY_QUOTED_STATE
105+
case regexp.MustCompile(`^".*"$`).MatchString(rawValue):
106+
state = FULL_QUOTING_ESCAPING_STATE
107+
case regexp.MustCompile(`^'.*'$`).MatchString(rawValue):
108+
state = FULL_QUOTING_STATE
84109
}
85110
}
86111

@@ -97,26 +122,37 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
97122
displays := make([]string, len(values))
98123
for index, val := range values {
99124
value := sanitizer.Replace(val.Value)
100-
value = quoteValue(value)
101-
value = strings.ReplaceAll(value, `\`, `\\`) // TODO find out why `_describe` needs another backslash
102-
value = strings.ReplaceAll(value, `:`, `\:`) // TODO find out why `_describe` needs another backslash
103125

104126
switch state {
105-
// TODO depending on state value needs to be formatted differently
106-
// TODO backspace strings are currently an issue
107-
case QUOTING_STATE:
108-
value = value + `'`
109127
case QUOTING_ESCAPING_STATE:
128+
value = quotingEscapingReplacer.Replace(value)
129+
value = describeReplacer.Replace(value)
110130
value = value + `"`
131+
case QUOTING_STATE:
132+
value = quotingReplacer.Replace(value)
133+
value = describeReplacer.Replace(value)
134+
value = value + `'`
135+
case FULL_QUOTING_ESCAPING_STATE:
136+
value = quotingEscapingReplacer.Replace(value)
137+
value = describeReplacer.Replace(value)
138+
case FULL_QUOTING_STATE:
139+
value = quotingReplacer.Replace(value)
140+
value = describeReplacer.Replace(value)
141+
default:
142+
value = quoteValue(value)
143+
value = describeReplacer.Replace(value)
111144
}
112145

113-
if !meta.Nospace.Matches(val.Value) && state != FULLY_QUOTED_STATE {
114-
value += " "
146+
if !meta.Nospace.Matches(val.Value) {
147+
switch state {
148+
case FULL_QUOTING_ESCAPING_STATE, FULL_QUOTING_STATE: // nospace workaround
149+
default:
150+
value += " "
151+
}
115152
}
116153

117154
display := sanitizer.Replace(val.Display)
118-
display = strings.ReplaceAll(display, `\`, `\\`) // TODO find out why `_describe` needs another backslash
119-
display = strings.ReplaceAll(display, `:`, `\:`) // TODO find out why `_describe` needs another backslash
155+
display = describeReplacer.Replace(display) // TODO check if this needs to be applied to description as well
120156
description := sanitizer.Replace(val.Description)
121157

122158
vals[index] = value

internal/shell/zsh/special.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#compdef special
2+
function _special_completion {
3+
local -a subcmds
4+
subcmds=('p1 & < > '"'"' " { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``:description for c command' 'd:description for d command')
5+
_describe 'command' subcmds
6+
}
7+
compquote '' 2>/dev/null && _special_completion
8+
compdef _special_completion special

0 commit comments

Comments
 (0)