From 2de2ef1e529c087e3c7780e8042aa38920a12af4 Mon Sep 17 00:00:00 2001 From: rsteube Date: Wed, 7 Apr 2021 13:27:48 +0200 Subject: [PATCH] implicit nospace (initial support) --- action.go | 77 +++++++++++++---------------------- example/cmd/_test/elvish.elv | 2 +- example/cmd/_test/zsh.sh | 8 +++- internal/bash/action.go | 10 ++++- internal/elvish/action.go | 16 +++++--- internal/elvish/snippet.go | 2 +- internal/fish/action.go | 2 +- internal/ion/action.go | 6 ++- internal/nushell/action.go | 6 ++- internal/oil/action.go | 2 +- internal/powershell/action.go | 6 ++- internal/xonsh/action.go | 7 +++- internal/zsh/action.go | 5 ++- internal/zsh/snippet.go | 8 +++- 14 files changed, 91 insertions(+), 66 deletions(-) diff --git a/action.go b/action.go index d7db37e23..1577c872e 100644 --- a/action.go +++ b/action.go @@ -28,17 +28,9 @@ import ( // Action indicates how to complete a flag or positional argument type Action struct { - rawValues []common.RawValue - bash func(callbackValue string) string - elvish func(callbackValue string) string - fish func(callbackValue string) string - ion func(callbackValue string) string - nushell func(callbackValue string) string - oil func(callbackValue string) string - powershell func(callbackValue string) string - xonsh func(callbackValue string) string - zsh func(callbackValue string) string - callback CompletionCallback + rawValues []common.RawValue + callback CompletionCallback + nospace bool } type ActionMap map[string]Action @@ -122,7 +114,12 @@ func (a InvokedAction) Merge(others ...InvokedAction) InvokedAction { for _, c := range uniqueRawValues { rawValues = append(rawValues, c) } - return InvokedAction(actionRawValues(rawValues...)) + return InvokedAction(actionRawValues(rawValues...).noSpace(a.nospace)) +} + +func (a Action) noSpace(state bool) Action { + a.nospace = a.nospace || state + return a } func (a InvokedAction) Filter(values []string) InvokedAction { @@ -136,7 +133,7 @@ func (a InvokedAction) Filter(values []string) InvokedAction { filtered = append(filtered, rawValue) } } - return InvokedAction(actionRawValues(filtered...)) + return InvokedAction(actionRawValues(filtered...).noSpace(a.nospace)) } func (a InvokedAction) Prefix(prefix string) InvokedAction { @@ -177,45 +174,40 @@ func (a InvokedAction) ToMultiPartsA(divider string) Action { vals = append(vals, val, description) } - return ActionValuesDescribed(vals...) + return ActionValuesDescribed(vals...).noSpace(true) }) } func (a Action) nestedAction(c Context, maxDepth int) Action { if a.rawValues == nil && a.callback != nil && maxDepth > 0 { - return a.callback(c).nestedAction(c, maxDepth-1) + return a.callback(c).nestedAction(c, maxDepth-1).noSpace(a.nospace) } else { return a } } func (a InvokedAction) value(shell string, callbackValue string) string { // TODO use context instead? - var f func(callbackValue string) string switch shell { case "bash": - f = a.bash + return bash.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "fish": - f = a.fish + return fish.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "elvish": - f = a.elvish + return elvish.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "ion": - f = a.ion + return ion.ActionRawValues(callbackValue, a.nospace, a.rawValues) case "nushell": - f = a.nushell + return nushell.ActionRawValues(callbackValue, a.nospace, a.rawValues) case "oil": - f = a.oil + return oil.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "powershell": - f = a.powershell + return powershell.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "xonsh": - f = a.xonsh + return xonsh.ActionRawValues(callbackValue, a.nospace, a.rawValues...) case "zsh": - f = a.zsh - } - - if f == nil { + return zsh.ActionRawValues(callbackValue, a.nospace, a.rawValues...) + default: return "" - } else { - return f(callbackValue) } } @@ -232,14 +224,14 @@ func ActionBool() Action { // ActionDirectories completes directories func ActionDirectories() Action { return ActionCallback(func(c Context) Action { - return actionPath([]string{""}, true).Invoke(c).ToMultiPartsA("/") + return actionPath([]string{""}, true).Invoke(c).ToMultiPartsA("/").noSpace(true) }) } // ActionFiles completes files with optional suffix filtering func ActionFiles(suffix ...string) Action { return ActionCallback(func(c Context) Action { - return actionPath(suffix, false).Invoke(c).ToMultiPartsA("/") + return actionPath(suffix, false).Invoke(c).ToMultiPartsA("/").noSpace(true) }) } @@ -324,16 +316,7 @@ func ActionValuesDescribed(values ...string) Action { func actionRawValues(rawValues ...common.RawValue) Action { return Action{ - rawValues: rawValues, - bash: func(callbackValue string) string { return bash.ActionRawValues(callbackValue, rawValues...) }, - elvish: func(callbackValue string) string { return elvish.ActionRawValues(callbackValue, rawValues...) }, - fish: func(callbackValue string) string { return fish.ActionRawValues(callbackValue, rawValues...) }, - ion: func(callbackValue string) string { return ion.ActionRawValues(callbackValue, rawValues) }, - nushell: func(callbackValue string) string { return nushell.ActionRawValues(callbackValue, rawValues) }, - oil: func(callbackValue string) string { return oil.ActionRawValues(callbackValue, rawValues...) }, - powershell: func(callbackValue string) string { return powershell.ActionRawValues(callbackValue, rawValues...) }, - xonsh: func(callbackValue string) string { return xonsh.ActionRawValues(callbackValue, rawValues...) }, - zsh: func(callbackValue string) string { return zsh.ActionRawValues(callbackValue, rawValues...) }, + rawValues: rawValues, } } @@ -343,10 +326,8 @@ var skipCache bool // ActionMessage displays a help messages in places where no completions can be generated func ActionMessage(msg string) Action { return ActionCallback(func(c Context) Action { - return ActionCallback(func(c Context) Action { - skipCache = true // TODO find a better solution - any call to ActionMessage i assumed to be an error for now - return ActionValuesDescribed("_", "", "ERR", msg).Invoke(c).Prefix(c.CallbackValue).ToA() // needs to be prefixed with current callback value to not be filtered out - }) + skipCache = true // TODO find a better solution - any call to ActionMessage i assumed to be an error for now + return ActionValuesDescribed("_", "", "ERR", msg).Invoke(c).Prefix(c.CallbackValue).ToA().noSpace(true) // needs to be prefixed with current callback value to not be filtered out }) } @@ -367,7 +348,7 @@ func ActionMultiParts(divider string, callback func(c Context) Action) Action { } c.Parts = parts - return callback(c).Invoke(c).Prefix(prefix).ToA() + return callback(c).Invoke(c).Prefix(prefix).ToA().noSpace(true) }) } @@ -418,7 +399,7 @@ func actionFlags(cmd *cobra.Command) Action { if isShorthandSeries { matches := re.FindStringSubmatch(c.CallbackValue) parts := strings.Split(matches[1], "") - return ActionValuesDescribed(vals...).Invoke(c).Filter(parts).Prefix(c.CallbackValue).ToA() + return ActionValuesDescribed(vals...).Invoke(c).Filter(parts).Prefix(c.CallbackValue).ToA().noSpace(true) } else { return ActionValuesDescribed(vals...) } diff --git a/example/cmd/_test/elvish.elv b/example/cmd/_test/elvish.elv index 3987d19f8..2ca903bae 100644 --- a/example/cmd/_test/elvish.elv +++ b/example/cmd/_test/elvish.elv @@ -1,4 +1,4 @@ edit:completion:arg-completer[example] = [@arg]{ - example _carapace elvish _ (all $arg) | from-json | all (one) | each [c]{ edit:complex-candidate $c[Value] &display=$c[Display] } + example _carapace elvish _ (all $arg) | from-json | all (one) | each [c]{ edit:complex-candidate $c[Value] &display=$c[Display] &code-suffix=$c[CodeSuffix] } } diff --git a/example/cmd/_test/zsh.sh b/example/cmd/_test/zsh.sh index 2de3b82bc..ab4b8ce7e 100644 --- a/example/cmd/_test/zsh.sh +++ b/example/cmd/_test/zsh.sh @@ -18,7 +18,13 @@ function _example_completion { local vals=(${c%%$'\t'*}) # shellcheck disable=SC2034,2206 local descriptions=(${c##*$'\t'}) - compadd -S '' -d descriptions -a -- vals + + local suffix=' ' + [[ ${vals[1]} == *$'\001' ]] && suffix='' + # shellcheck disable=SC2034,2206 + vals=(${vals%%$'\001'*}) + + compadd -S "${suffix}" -d descriptions -a -- vals } compquote '' 2>/dev/null && _example_completion compdef _example_completion example diff --git a/internal/bash/action.go b/internal/bash/action.go index e965f7f69..abea6959e 100644 --- a/internal/bash/action.go +++ b/internal/bash/action.go @@ -76,7 +76,9 @@ func commonValuePrefix(values ...common.RawValue) (prefix string) { return } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +const nospaceIndicator = "\001" + +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { filtered := make([]common.RawValue, 0) lastSegment := callbackValue // last segment of callbackValue split by COMP_WORDBREAKS @@ -99,7 +101,7 @@ func ActionRawValues(callbackValue string, values ...common.RawValue) string { // When all display values have the same prefix bash will insert is as partial completion (which skips prefixes/formatting). if valuePrefix := commonValuePrefix(filtered...); lastSegment != valuePrefix { // replace values with common value prefix (`\001` is removed in snippet and compopt nospace will be set) - filtered = common.RawValuesFrom(commonValuePrefix(filtered...) + "\001") + filtered = common.RawValuesFrom(commonValuePrefix(filtered...) + nospaceIndicator) } else { // prevent insertion of partial display values by prefixing one with space filtered[0].Display = " " + filtered[0].Display @@ -108,6 +110,10 @@ func ActionRawValues(callbackValue string, values ...common.RawValue) string { vals := make([]string, len(filtered)) for index, val := range filtered { + if nospace && !strings.HasSuffix(val.Value, nospaceIndicator) { + val.Value = val.Value + nospaceIndicator + } + if len(filtered) == 1 { vals[index] = quoter.Replace(sanitizer.Replace(val.Value)) } else { diff --git a/internal/elvish/action.go b/internal/elvish/action.go index af1853339..ed180e49c 100644 --- a/internal/elvish/action.go +++ b/internal/elvish/action.go @@ -23,18 +23,24 @@ func sanitize(values []common.RawValue) []common.RawValue { } type complexCandidate struct { - Value string - Display string + Value string + Display string + CodeSuffix string } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { + suffix := " " + if nospace { + suffix = "" + } + vals := make([]complexCandidate, len(values)) for index, val := range sanitize(values) { // TODO have a look at this again later: seems elvish does a good job quoting any problematic characterS so the sanitize step was removed if val.Description == "" { - vals[index] = complexCandidate{Value: val.Value, Display: val.Display} + vals[index] = complexCandidate{Value: val.Value, Display: val.Display, CodeSuffix: suffix} } else { - vals[index] = complexCandidate{Value: val.Value, Display: fmt.Sprintf(`%v (%v)`, val.Display, val.Description)} + vals[index] = complexCandidate{Value: val.Value, Display: fmt.Sprintf(`%v (%v)`, val.Display, val.Description), CodeSuffix: suffix} } } m, _ := json.Marshal(vals) diff --git a/internal/elvish/snippet.go b/internal/elvish/snippet.go index e676040a1..3dc833ede 100644 --- a/internal/elvish/snippet.go +++ b/internal/elvish/snippet.go @@ -8,7 +8,7 @@ import ( func Snippet(cmd *cobra.Command) string { return fmt.Sprintf(`edit:completion:arg-completer[%v] = [@arg]{ - %v _carapace elvish _ (all $arg) | from-json | all (one) | each [c]{ edit:complex-candidate $c[Value] &display=$c[Display] } + %v _carapace elvish _ (all $arg) | from-json | all (one) | each [c]{ edit:complex-candidate $c[Value] &display=$c[Display] &code-suffix=$c[CodeSuffix] } } `, cmd.Name(), uid.Executable()) } diff --git a/internal/fish/action.go b/internal/fish/action.go index a5c7e7afb..a3b06d989 100644 --- a/internal/fish/action.go +++ b/internal/fish/action.go @@ -12,7 +12,7 @@ var sanitizer = strings.NewReplacer( "\t", ``, ) -func ActionRawValues(callbackValues string, values ...common.RawValue) string { +func ActionRawValues(callbackValues string, nospace bool, values ...common.RawValue) string { vals := make([]string, len(values)) for index, val := range values { vals[index] = fmt.Sprintf("%v\t%v", sanitizer.Replace(val.Value), sanitizer.Replace(val.Description)) diff --git a/internal/ion/action.go b/internal/ion/action.go index 53e28cc09..e8207dab1 100644 --- a/internal/ion/action.go +++ b/internal/ion/action.go @@ -28,12 +28,16 @@ type suggestion struct { Display string } -func ActionRawValues(callbackValue string, values common.RawValues) string { +func ActionRawValues(callbackValue string, nospace bool, values common.RawValues) string { filtered := values.FilterPrefix(callbackValue) sort.Sort(common.ByDisplay(filtered)) vals := make([]suggestion, len(filtered)) for index, val := range sanitize(filtered) { + if !nospace { + val.Value = val.Value + " " + } + if val.Description == "" { vals[index] = suggestion{Value: val.Value, Display: val.Display} } else { diff --git a/internal/nushell/action.go b/internal/nushell/action.go index 50be79fd6..d3b862975 100644 --- a/internal/nushell/action.go +++ b/internal/nushell/action.go @@ -28,12 +28,16 @@ type suggestion struct { Display string } -func ActionRawValues(callbackValue string, values common.RawValues) string { +func ActionRawValues(callbackValue string, nospace bool, values common.RawValues) string { filtered := values.FilterPrefix(callbackValue) sort.Sort(common.ByDisplay(filtered)) vals := make([]suggestion, len(filtered)) for index, val := range sanitize(filtered) { + if !nospace { + val.Value = val.Value + " " + } + if val.Description == "" { vals[index] = suggestion{Value: val.Value, Display: val.Display} } else { diff --git a/internal/oil/action.go b/internal/oil/action.go index d2d0ff876..84a999845 100644 --- a/internal/oil/action.go +++ b/internal/oil/action.go @@ -20,7 +20,7 @@ func Sanitize(values ...string) []string { return sanitized } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { filtered := make([]common.RawValue, 0) for _, r := range values { diff --git a/internal/powershell/action.go b/internal/powershell/action.go index 8ada722cd..d68d2dadf 100644 --- a/internal/powershell/action.go +++ b/internal/powershell/action.go @@ -37,7 +37,7 @@ func ensureNotEmpty(s string) string { return s } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { filtered := common.ByValues(values).Filter(callbackValue) sort.Sort(common.ByDisplay(filtered)) @@ -50,6 +50,10 @@ func ActionRawValues(callbackValue string, values ...common.RawValue) string { val.Value = fmt.Sprintf("'%v'", val.Value) } + if !nospace { + val.Value = val.Value + " " + } + vals = append(vals, completionResult{ CompletionText: val.Value, ListItemText: ensureNotEmpty(sanitizer.Replace(val.Display)), diff --git a/internal/xonsh/action.go b/internal/xonsh/action.go index 08bda51ab..851d415b3 100644 --- a/internal/xonsh/action.go +++ b/internal/xonsh/action.go @@ -28,7 +28,7 @@ type richCompletion struct { Description string } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { filtered := make([]common.RawValue, 0) for _, r := range values { @@ -48,6 +48,11 @@ func ActionRawValues(callbackValue string, values ...common.RawValue) string { val.Value = fmt.Sprintf("'%v'", val.Value) } } + + if !nospace { + val.Value = val.Value + " " + } + vals[index] = richCompletion{Value: val.Value, Display: val.Display, Description: val.Description} } m, _ := json.Marshal(vals) diff --git a/internal/zsh/action.go b/internal/zsh/action.go index cedb216dc..2fb4f49f7 100644 --- a/internal/zsh/action.go +++ b/internal/zsh/action.go @@ -22,7 +22,7 @@ func Sanitize(values ...string) []string { return sanitized } -func ActionRawValues(callbackValue string, values ...common.RawValue) string { +func ActionRawValues(callbackValue string, nospace bool, values ...common.RawValue) string { filtered := make([]common.RawValue, 0) for _, r := range values { @@ -34,6 +34,9 @@ func ActionRawValues(callbackValue string, values ...common.RawValue) string { vals := make([]string, len(filtered)) for index, val := range filtered { val.Value = sanitizer.Replace(val.Value) + if nospace { + val.Value = val.Value + "\001" + } val.Display = sanitizer.Replace(val.Display) val.Description = sanitizer.Replace(val.Description) diff --git a/internal/zsh/snippet.go b/internal/zsh/snippet.go index 7f8382fba..e6689e8e5 100644 --- a/internal/zsh/snippet.go +++ b/internal/zsh/snippet.go @@ -28,7 +28,13 @@ function _%v_completion { local vals=(${c%%%%$'\t'*}) # shellcheck disable=SC2034,2206 local descriptions=(${c##*$'\t'}) - compadd -S '' -d descriptions -a -- vals + + local suffix=' ' + [[ ${vals[1]} == *$'\001' ]] && suffix='' + # shellcheck disable=SC2034,2206 + vals=(${vals%%%%$'\001'*}) + + compadd -S "${suffix}" -d descriptions -a -- vals } compquote '' 2>/dev/null && _%v_completion compdef _%v_completion %v