Skip to content

Commit

Permalink
zsh: fix quoted
Browse files Browse the repository at this point in the history
  • Loading branch information
rsteube committed Jan 21, 2025
1 parent 55a9678 commit 6acfacc
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@


────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 "embeddedP2\ with\ space"
> example action embeddedP1 "embeddedP2 with space"






────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 "embeddedP2\ with\ space"
> example action embeddedP1 "embeddedP2 with space"



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@


────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 "embeddedP2\ with\ space"
> example action embeddedP1 "embeddedP2 with space"






────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 "embeddedP2\ with\ space"
> example action embeddedP1 "embeddedP2 with space"



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@


────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 'embeddedP2\ with\ space'
> example action embeddedP1 'embeddedP2 with space'






────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 'embeddedP2\ with\ space'
> example action embeddedP1 'embeddedP2 with space'



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@


────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 'embeddedP2\ with\ space'
> example action embeddedP1 'embeddedP2 with space'






────────────────────────────────────────────────────────────────────────────────
> example action embeddedP1 'embeddedP2\ with\ space'
> example action embeddedP1 'embeddedP2 with space'



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,27 @@


────────────────────────────────────────────────────────────────────────────────
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
`\`"





────────────────────────────────────────────────────────────────────────────────
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
expected: "p1 & < > ' \" { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``"
actual : "p1\\ \\&\\ \\<\\ \\>\\ \\'\\ \"\\ \\{\\ \\}\\ $\\ \\#\\ \\|\\ \\?
\\ \\(\\ \\)\\ \\;\\ \\ \\[\\ \\]\\ \\*\\ \\\\ $\\(\\)\\ $\\{\\}\\ `\\ ``"
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
`\`"
ok
>



────────────────────────────────────────────────────────────────────────────────
> example special "p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`"
expected: "p1 & < > ' \" { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``"
actual : "p1\\ \\&\\ \\<\\ \\>\\ \\'\\ \"\\ \\{\\ \\}\\ $\\ \\#\\ \\|\\ \\?
\\ \\(\\ \\)\\ \\;\\ \\ \\[\\ \\]\\ \\*\\ \\\\ $\\(\\)\\ $\\{\\}\\ `\\ ``"
> example special "p1 & < > ' \" { } \$ # | ? ( ) ; [ ] * \\ \$() \${} \` \
`\`"
ok
>



────────────────────────────────────────────────────────────────────────────────
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@


────────────────────────────────────────────────────────────────────────────────
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'






────────────────────────────────────────────────────────────────────────────────
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
quote>
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'

ok
>



────────────────────────────────────────────────────────────────────────────────
> example special 'p1\ \&\ \<\ \>\ \'\ \"\ \{\ \}\ \$\ \#\ \|\ \?\ \(\ \)\ \
;\ \ \[\ \]\ \*\ \\\ \$\(\)\ \$\{\}\ \`\ \`\`'
quote>
> example special 'p1 & < > '\'' " { } $ # | ? ( ) ; [ ] * \ $() ${} ` ``'

ok
>



Expand Down
74 changes: 55 additions & 19 deletions internal/shell/zsh/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@ var sanitizer = strings.NewReplacer(
"\t", ``,
)

// TODO verify these are correct/complete (copied from bash)
var quoter = strings.NewReplacer(
var quotingReplacer = strings.NewReplacer(
`'`, `'\''`,
)

var quotingEscapingReplacer = strings.NewReplacer(
`\`, `\\`,
`"`, `\"`,
`$`, `\$`,
"`", "\\`",
)

var defaultReplacer = strings.NewReplacer(
`\`, `\\`,
`&`, `\&`,
`<`, `\<`,
Expand All @@ -41,11 +51,17 @@ var quoter = strings.NewReplacer(
`~`, `\~`,
)

// additional replacement for use with `_describe` in shell script
var describeReplacer = strings.NewReplacer(
`\`, `\\`,
`:`, `\:`,
)

func quoteValue(s string) string {
if strings.HasPrefix(s, "~/") || NamedDirectories.Matches(s) {
return "~" + quoter.Replace(strings.TrimPrefix(s, "~")) // assume file path expansion
return "~" + defaultReplacer.Replace(strings.TrimPrefix(s, "~")) // assume file path expansion
}
return quoter.Replace(s)
return defaultReplacer.Replace(s)
}

type state int
Expand All @@ -60,12 +76,18 @@ const (
// Values need to end with `'` as well.
// Weirdly regardless whether there are additional quotes within the word.
QUOTING_STATE
// Word starts and ends with quotes.
// Word starts and ends with `"`.
// Space suffix somehow ends up within the quotes.
// `"action"<TAB>`
// `"action "<CURSOR>`
// Workaround for now is to force nospace.
FULLY_QUOTED_STATE
FULL_QUOTING_ESCAPING_STATE
// Word starts and ends with `'`.
// Space suffix somehow ends up within the quotes.
// `'action'<TAB>`
// `'action '<CURSOR>`
// Workaround for now is to force nospace.
FULL_QUOTING_STATE
)

// ActionRawValues formats values for zsh
Expand All @@ -74,13 +96,16 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
state := DEFAULT_STATE
if err == nil {
rawValue := splitted.CurrentToken().RawValue
// TODO use token state to determine actual state (might have mixture).
switch {
case regexp.MustCompile(`^'$|^'.*[^']$`).MatchString(rawValue):
state = QUOTING_STATE
case regexp.MustCompile(`^"$|^".*[^"]$`).MatchString(rawValue):
state = QUOTING_ESCAPING_STATE
case regexp.MustCompile(`^".*"$|^'.*'$`).MatchString(rawValue):
state = FULLY_QUOTED_STATE
case regexp.MustCompile(`^".*"$`).MatchString(rawValue):
state = FULL_QUOTING_ESCAPING_STATE
case regexp.MustCompile(`^'.*'$`).MatchString(rawValue):
state = FULL_QUOTING_STATE
}
}

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

switch state {
// TODO depending on state value needs to be formatted differently
// TODO backspace strings are currently an issue
case QUOTING_STATE:
value = value + `'`
case QUOTING_ESCAPING_STATE:
value = quotingEscapingReplacer.Replace(value)
value = describeReplacer.Replace(value)
value = value + `"`
case QUOTING_STATE:
value = quotingReplacer.Replace(value)
value = describeReplacer.Replace(value)
value = value + `'`
case FULL_QUOTING_ESCAPING_STATE:
value = quotingEscapingReplacer.Replace(value)
value = describeReplacer.Replace(value)
case FULL_QUOTING_STATE:
value = quotingReplacer.Replace(value)
value = describeReplacer.Replace(value)
default:
value = quoteValue(value)
value = describeReplacer.Replace(value)
}

if !meta.Nospace.Matches(val.Value) && state != FULLY_QUOTED_STATE {
value += " "
if !meta.Nospace.Matches(val.Value) {
switch state {
case FULL_QUOTING_ESCAPING_STATE, FULL_QUOTING_STATE: // nospace workaround
default:
value += " "
}
}

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

vals[index] = value
Expand Down
8 changes: 8 additions & 0 deletions internal/shell/zsh/special.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#compdef special
function _special_completion {
local -a subcmds
subcmds=('p1 & < > '"'"' " { } $ # | ? ( ) ; [ ] * \\ $() ${} ` ``:description for c command' 'd:description for d command')
_describe 'command' subcmds
}
compquote '' 2>/dev/null && _special_completion
compdef _special_completion special

0 comments on commit 6acfacc

Please sign in to comment.