Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable use of carapace completer as a library #655

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ad387ff
Merge upstream master
maxlandon Dec 20, 2022
8fc83ce
Enable using carapace completer as a library
maxlandon Dec 26, 2022
53d7461
Fix bugs from previous commits/merges
maxlandon Dec 30, 2022
d2b5b0c
Merge branch 'master' of github.com:rsteube/carapace into library
maxlandon Jan 5, 2023
8e704bc
Fixes after merge
maxlandon Jan 5, 2023
8ae1b48
Merge branch 'master' of github.com:rsteube/carapace
maxlandon Jan 5, 2023
58dda9b
For some reason fish action doubles the fmt import...
maxlandon Jan 5, 2023
382407e
Make UniqueList() to keep individual suffix matchers
maxlandon Jan 6, 2023
3bc923c
Merge branch 'master' of github.com:rsteube/carapace
maxlandon Apr 16, 2023
fc9b8c3
Merge branch 'master' into library
maxlandon Apr 16, 2023
a5e6f95
Only use namespace multipart of flags when they need it.
maxlandon May 10, 2023
c37cf28
Merge branch 'master' of github.com:rsteube/carapace
maxlandon May 15, 2023
47d0a68
Recommit correct configuration file load
maxlandon May 15, 2023
b5307f7
Merge branch 'master' into library
maxlandon May 16, 2023
6362679
Add help completion
maxlandon May 17, 2023
feaf11d
Refactor values in internal/shell
maxlandon May 27, 2023
1ea9c3a
Add support for Windows absolute path + fixes to relative paths
maxlandon May 28, 2023
f11fb22
Merge branch 'powershell-path' into library
maxlandon May 28, 2023
867bc04
Merge branch 'fixes' into library
maxlandon May 28, 2023
e8d757e
Remove backslash replacer, not needed
maxlandon Jun 2, 2023
080d3bd
Merge branch 'master' into library
maxlandon Aug 8, 2023
a30f518
Merge branch 'master' into library
maxlandon Aug 16, 2023
656cf21
Merge upstream master into library
maxlandon Nov 29, 2023
fa41dc4
Merge branch 'master' into library
maxlandon Nov 29, 2023
4ce9864
Merge upstream fixes
maxlandon Nov 29, 2023
c05a3a8
Fix missing master stuff
maxlandon Nov 29, 2023
45d01ed
Remove old code
maxlandon Nov 29, 2023
356c9d5
Remove old code again
maxlandon Nov 29, 2023
0b978c5
Fix test typo in strings
maxlandon Nov 29, 2023
41ee6d5
Remove unused function
maxlandon Nov 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion action.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,12 @@ func (a Action) List(divider string) Action {
// UniqueList wraps the Action in an ActionMultiParts with given divider.
func (a Action) UniqueList(divider string) Action {
return ActionMultiParts(divider, func(c Context) Action {
return a.Invoke(c).Filter(c.Parts).ToA().NoSpace()
noSpace := make([]rune, 0)
if runes := []rune(divider); len(runes) > 0 {
noSpace = append(noSpace, runes[len(runes)-1])
}
noSpace = append(noSpace, []rune(a.meta.Nospace.String())...)
return a.Invoke(c).Filter(c.Parts).ToA().NoSpace([]rune(noSpace)...)
})
}

Expand Down
71 changes: 67 additions & 4 deletions complete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,26 @@ import (
"github.com/rsteube/carapace/internal/config"
"github.com/rsteube/carapace/internal/pflagfork"
"github.com/rsteube/carapace/pkg/ps"
"github.com/rsteube/carapace/pkg/style"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

// Complete can be used by Go programs wishing to produce completions for
// themselves, without passing through shell snippets/output or export formats.
//
// The `onFinalize` function parameter, if non nil, will be called after having
// generated the completions from the given command/tree. This function is generally
// used to reset the command tree, which is needed when the Go program is a shell itself.
// Also, and before calling `onFinalize` if not nil, the completion storage is cleared.
func Complete(cmd *cobra.Command, args []string, onFinalize func()) (common.RawValues, common.Meta) {
// Generate the completion as normally done for an external system shell
action, current := generate(cmd, args)

// And adapt/fetch the results from invoked action
return internalValues(action, current, onFinalize)
}

func complete(cmd *cobra.Command, args []string) (string, error) {
switch len(args) {
case 0:
Expand All @@ -20,16 +36,28 @@ func complete(cmd *cobra.Command, args []string) (string, error) {
}

shell := args[0]

// Generate the completions and get the resulting invoked action.
action, current := generate(cmd, args)

// And generate the completions string for the target shell caller.
return action.value(shell, current), nil
}

func generate(cmd *cobra.Command, args []string) (InvokedAction, string) {
current := args[len(args)-1]
previous := args[len(args)-2]
previous := ""
if len(args) > 1 {
previous = args[len(args)-2]
}

if err := config.Load(); err != nil {
return ActionMessage("failed to load config: "+err.Error()).Invoke(Context{CallbackValue: current}).value(shell, current), nil
return ActionMessage("failed to load config: " + err.Error()).Invoke(Context{CallbackValue: current}), current
}

targetCmd, targetArgs, err := findTarget(cmd, args)
if err != nil {
return ActionMessage(err.Error()).Invoke(Context{CallbackValue: current}).value(shell, current), nil
return ActionMessage(err.Error()).Invoke(Context{CallbackValue: current}), current
}

context := NewContext(append(targetArgs, current))
Expand Down Expand Up @@ -67,7 +95,8 @@ func complete(cmd *cobra.Command, args []string) (string, error) {
}
}
}
return targetAction.Invoke(context).value(shell, current), nil

return targetAction.Invoke(context), current
}

func findAction(targetCmd *cobra.Command, targetArgs []string) Action {
Expand Down Expand Up @@ -100,3 +129,37 @@ func lookupFlag(cmd *cobra.Command, arg string) (flag *pflag.Flag) {
}
return
}

func internalValues(a InvokedAction, current string, onFinalize func()) (common.RawValues, common.Meta) {
unsorted := common.RawValues(a.rawValues)
sorted := make(common.RawValues, 0)

// Ensure values are sorted.
unsorted.EachTag(func(tag string, values common.RawValues) {
vals := make(common.RawValues, len(values))
for index, val := range values {
if !a.meta.Nospace.Matches(val.Value) {
val.Value += " "
}
if val.Style != "" {
val.Style = style.SGR(val.Style)
}

vals[index] = val
}
sorted = append(sorted, vals...)
})

// Merge/filter completions and meta stuff.
filtered := sorted.FilterPrefix(current)
filtered = a.meta.Messages.Integrate(filtered, current)

// Reset the storage (empty all commands) and run the finalize function, which is
// generally in charge of binding new command instances, with blank flags.
if onFinalize != nil {
storage = make(_storage)
onFinalize()
}

return filtered, a.meta
}
4 changes: 2 additions & 2 deletions defaultActions.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func ActionImport(output []byte) Action {
}

// ActionExecute executes completion on an internal command
// TODO example
// TODO example.
func ActionExecute(cmd *cobra.Command) Action {
return ActionCallback(func(c Context) Action {
args := []string{"_carapace", "export", cmd.Name()}
Expand Down Expand Up @@ -179,7 +179,7 @@ func ActionMessage(msg string, args ...interface{}) Action {
})
}

// ActionMultiParts completes multiple parts of words separately where each part is separated by some char (CallbackValue is set to the currently completed part during invocation)
// ActionMultiParts completes multiple parts of words separately where each part is separated by some char (CallbackValue is set to the currently completed part during invocation).
func ActionMultiParts(divider string, callback func(c Context) Action) Action {
return ActionCallback(func(c Context) Action {
index := strings.LastIndex(c.CallbackValue, string(divider))
Expand Down
1 change: 0 additions & 1 deletion example/cmd/special.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,4 @@ func init() {
carapace.ActionValues("positional1", "p1", "positional1 with space"),
carapace.ActionValues("positional2", "p2", "positional2 with space"),
)

}
19 changes: 15 additions & 4 deletions example/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,20 @@ var tests = map[string]string{
`example action `: "p",
`example action positional`: "positional1",
`example action positional1`: "positional1 with space",
//`example action "positional1 `: "positional1 with space", // TODO this test does not yet work with bash as it's missing quote handling in the snippet
//`example action --`: "--values_described", // weird: causes regex match in expect/xonsh not to work
//`example action -`: "-o", // weird: causes regex match in expect/xonsh not to work
// `example action "positional1 `: "positional1 with space", // TODO this test does not yet work with bash as it's missing quote handling in the snippet
// `example action --`: "--values_described", // weird: causes regex match in expect/xonsh not to work
// `example action -`: "-o", // weird: causes regex match in expect/xonsh not to work
`example flag --optarg `: "p",
`example flag --optarg positional`: "positional1",
`example flag --optar`: "--optarg",
`example flag --optarg=`: "optarg",
`example flag -o`: "count flag",
`example flag -oc`: "count flag",
`example flag -o `: "p",
`example flag -o pos`: "positional",
// `example action "positional1 `: "positional1 with space", // TODO this test does not yet work with bash as it's missing quote handling in the snippet
// `example action --`: "--values_described", // weird: causes regex match in expect/xonsh not to work
// `example action -`: "-o", // weird: causes regex match in expect/xonsh not to work
`example special --optarg `: "p",
`example special --optarg positional`: "positional1",
`example special --optar`: "--optarg",
Expand Down Expand Up @@ -244,7 +255,7 @@ func TestOil(t *testing.T) {
}
}

//func TestPowershell(t *testing.T) {
// func TestPowershell(t *testing.T) {
// if err := exec.Command("pwsh", "--version").Run(); err != nil {
// t.Skip("skipping powershell")
// }
Expand Down
6 changes: 3 additions & 3 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
func Write(file string, rawValues []common.RawValue) (err error) {
var m []byte
if m, err = json.Marshal(rawValues); err == nil {
err = os.WriteFile(file, m, 0600)
err = os.WriteFile(file, m, 0o600)
}
return
}
Expand All @@ -44,13 +44,13 @@ func CacheDir(name string) (dir string, err error) {
var userCacheDir string
if userCacheDir, err = os.UserCacheDir(); err == nil {
dir = fmt.Sprintf("%v/carapace/%v/%v", userCacheDir, uid.Executable(), name)
err = os.MkdirAll(dir, 0700)
err = os.MkdirAll(dir, 0o700)
}
return
}

// File returns the cache filename for given values
// TODO cleanup
// TODO cleanup.
func File(callerFile string, callerLine int, keys ...cache.Key) (file string, err error) {
uid := uidKeys(callerFile, strconv.Itoa(callerLine))
ids := make([]string, 0)
Expand Down
4 changes: 4 additions & 0 deletions internal/common/suffix.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func (sm SuffixMatcher) Matches(s string) bool {
return false
}

func (sm SuffixMatcher) String() string {
return sm.string
}

func (sm SuffixMatcher) MarshalJSON() ([]byte, error) {
return json.Marshal(sm.string)
}
Expand Down
10 changes: 6 additions & 4 deletions internal/common/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,20 @@ func (r RawValues) FilterPrefix(prefix string) RawValues {
}

func (r RawValues) EachTag(f func(tag string, values RawValues)) {
tags := make([]string, 0)
tagGroups := make(map[string]RawValues)
for _, val := range r {
if _, exists := tagGroups[val.Tag]; !exists {
tagGroups[val.Tag] = make(RawValues, 0)
tags = append(tags, val.Tag)
}
tagGroups[val.Tag] = append(tagGroups[val.Tag], val)
}

tags := make([]string, 0)
for tag := range tagGroups {
tags = append(tags, tag)
}
// tags := make([]string, 0)
// for tag := range tagGroups {
// tags = append(tags, tag)
// }
sort.Strings(tags)

for _, tag := range tags {
Expand Down
1 change: 0 additions & 1 deletion internal/pflagfork/flagset.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,4 @@ func (f *FlagSet) VisitAll(fn func(*Flag)) {
f.FlagSet.VisitAll(func(flag *pflag.Flag) {
fn(&Flag{flag})
})

}
3 changes: 2 additions & 1 deletion internal/shell/fish/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package fish

import (
"fmt"
"github.com/rsteube/carapace/internal/common"
"strings"

"github.com/rsteube/carapace/internal/common"
)

var sanitizer = strings.NewReplacer(
Expand Down
1 change: 0 additions & 1 deletion internal/shell/tcsh/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ func ActionRawValues(currentWord string, meta common.Meta, values common.RawValu
if valuePrefix := commonValuePrefix(values...); lastSegment != valuePrefix {
// replace values with common value prefix (`\001` is removed in snippet and compopt nospace will be set)
values = common.RawValuesFrom(commonValuePrefix(values...)) // TODO nospaceIndicator
//values = common.RawValuesFrom(commonValuePrefix(values...) + nospaceIndicator)
} else {
// prevent insertion of partial display values by prefixing one with space
values[0].Display = " " + values[0].Display
Expand Down
5 changes: 2 additions & 3 deletions internal/shell/zsh/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var sanitizer = strings.NewReplacer(
"\t", ``,
)

// TODO verify these are correct/complete (copied from bash)
// TODO verify these are correct/complete (copied from bash).
var quoter = strings.NewReplacer(
`\`, `\\`,
`&`, `\&`,
Expand Down Expand Up @@ -45,9 +45,8 @@ func quoteValue(s string) string {
return quoter.Replace(s)
}

// ActionRawValues formats values for zsh
// ActionRawValues formats values for zsh.
func ActionRawValues(currentWord string, meta common.Meta, values common.RawValues) string {

tagGroup := make([]string, 0)
values.EachTag(func(tag string, values common.RawValues) {
vals := make([]string, len(values))
Expand Down
6 changes: 3 additions & 3 deletions internal/shell/zsh/namedDirectory.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

type namedDirectories map[string]string

// NamedDirectories provides rudimentary named directory support as these aren't expanded by zsh in the `${words}` provided to the compdef function
// NamedDirectories provides rudimentary named directory support as these aren't expanded by zsh in the `${words}` provided to the compdef function.
var NamedDirectories = make(namedDirectories)

func (nd *namedDirectories) match(s string) string {
Expand All @@ -17,12 +17,12 @@ func (nd *namedDirectories) match(s string) string {
return ""
}

// Matches checks if given string has a known named directory prefix
// Matches checks if given string has a known named directory prefix.
func (nd *namedDirectories) Matches(s string) bool {
return nd.match(s) != ""
}

// Replace replaces a known named directory prefix with the actual folder
// Replace replaces a known named directory prefix with the actual folder.
func (nd *namedDirectories) Replace(s string) string {
if match := nd.match(s); match != "" {
if !strings.HasSuffix(match, "/") {
Expand Down
2 changes: 1 addition & 1 deletion internal/shell/zsh/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra"
)

// Snippet creates the zsh completion script
// Snippet creates the zsh completion script.
func Snippet(cmd *cobra.Command) string {
return fmt.Sprintf(`#compdef %v
function _%v_completion {
Expand Down
1 change: 0 additions & 1 deletion internal/shell/zsh/zstyle.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func (z zstyles) valueSGR(val common.RawValue) string {
return style.SGR(style.Carapace.Value)
}
return style.SGR(style.Default)

}

func (z zstyles) hasAliases() bool {
Expand Down
2 changes: 1 addition & 1 deletion internalActions.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func actionPath(fileSuffixes []string, dirOnly bool) Action {
return ActionStyledValues(vals...).Invoke(Context{}).Prefix("./").ToA()
}
return ActionStyledValues(vals...)
})
}).Tag("files").NoSpace('/')
}

func actionFlags(cmd *cobra.Command) Action {
Expand Down
1 change: 0 additions & 1 deletion invokedAction.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ func (a InvokedAction) ToMultiPartsA(dividers ...string) Action {
splittedCV = append(splittedCV, "")
break
}

}

uniqueVals := make(map[string]common.RawValue)
Expand Down
2 changes: 1 addition & 1 deletion log.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func init() {
}

file := fmt.Sprintf("%v/%v.log", tmpdir, uid.Executable())
if logfileWriter, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666); err != nil {
if logfileWriter, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666); err != nil {
log.Fatal(err.Error())
} else {
logger = _logger{log.New(logfileWriter, ps.DetermineShell()+" ", log.Flags()|log.Lmsgprefix)}
Expand Down
5 changes: 2 additions & 3 deletions pkg/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,14 @@ func (s *Sandbox) Files(args ...string) {

path := fmt.Sprintf("%v/%v", s.mock.Dir, file)

if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil && !os.IsExist(err) {
s.t.Fatal(err.Error())
}

if err := os.WriteFile(path, []byte(content), 0644); err != nil {
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
s.t.Fatal(err.Error())
}
}

}

// Reply mocks a command for given arguments (Only works for `(Context).Command`).
Expand Down
3 changes: 1 addition & 2 deletions pkg/style/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ var Carapace = carapace{
Highlight12: Of(Bold, Dim),
}

// Highlight returns the style for given level (0..n)
// Highlight returns the style for given level (0..n).
func (c carapace) Highlight(level int) string {
switch level {
case 0:
Expand Down Expand Up @@ -122,7 +122,6 @@ func (c carapace) Highlight(level int) string {
default:
return Default
}

}

func init() {
Expand Down
Loading