Skip to content

Commit

Permalink
nix_completer: Add action for completing attributes on flakes
Browse files Browse the repository at this point in the history
Fixes: #2374

This will only complete attributes for flakes that are in
the user's registry or are a local path. This means
that the flake eval cache should be very useful for these
completions, and that carapace won't go out and fetch
random flakes when the user is trying to tab complete.
  • Loading branch information
aftix authored and rsteube committed Dec 22, 2024
1 parent c598f71 commit 937bd7d
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 3 deletions.
105 changes: 105 additions & 0 deletions completers/nix_completer/cmd/action/flake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package action

import (
"bytes"
"fmt"
"os"
"slices"
"strings"

"github.com/carapace-sh/carapace"
"github.com/carapace-sh/carapace-bin/pkg/actions/tools/nix"
)

// ActionFlakeRefs completes a full flake reference
// It will only complete attributes for local flakes or flakes
// in the flake registry.
// It takes in the subcommand being completed
//
// nixpkgs#hello
// .#foo
func ActionFlakeRefs(fullCmd []string) carapace.Action {
return carapace.ActionMultiPartsN("#", 2, func(c carapace.Context) carapace.Action {
switch len(c.Parts) {
case 0:
return nix.ActionFlakes().Suffix("#")
default:
return ActionFlakeAttributes(fullCmd, c.Parts[0], c.Value)
}
})
}

// ActionFlakeAttributes completes attributes on a flake
// Completions are only supplied for local flakes or flakes
// in the registry.
//
// hello
// packages.x86_64-linux.hello
func ActionFlakeAttributes(fullCmd []string, flake, flakePath string) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
// Get completions from the flake registry
c.Setenv("NIX_GET_COMPLETIONS", fmt.Sprint(len(fullCmd)))
completionArgs := append(fullCmd[1:], flake)

var stdout, stderr bytes.Buffer
cmd := c.Command("nix", completionArgs...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
return carapace.ActionValues()
}

// Check if the given flake is in the registry or is a local path
// For some reason, nix adds \b to completion items
rawLines := strings.ReplaceAll(stdout.String(), "\r\b", "")
lines := strings.Split(rawLines, "\n")

flakeInRegistry := slices.ContainsFunc(lines[1:], func(s string) bool {
return strings.Trim(s, " \t") == flake
})
flakeIsLocal := directoryExists(flake)

if !flakeInRegistry && !flakeIsLocal {
return carapace.ActionValues()
}

// Get completions using the nix cli
stdout.Reset()
stderr.Reset()

completionArgs = append(fullCmd[1:], flake+"#"+flakePath)
cmd = c.Command("nix", completionArgs...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
return carapace.ActionValues()
}

// Remove \r for consistency with line endings
// For some reason, nix adds \b to completion items
rawLines = strings.ReplaceAll(stdout.String(), "\r\b", "")
lines = strings.Split(rawLines, "\n")

// The first line is always "attrs"
completionResults := lines[1:]

// Remove the flake# prefix
for i := range completionResults {
completionResults[i] = strings.Trim(completionResults[i], " \t")
completionResults[i] = strings.TrimPrefix(completionResults[i], flake+"#")
}

return carapace.ActionValues(completionResults...)
})
}

func directoryExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}

return info.IsDir()
}
3 changes: 2 additions & 1 deletion completers/nix_completer/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"github.com/carapace-sh/carapace"
"github.com/carapace-sh/carapace-bin/completers/nix_completer/cmd/action"
"github.com/carapace-sh/carapace-bin/pkg/actions/tools/nix"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -39,5 +40,5 @@ func init() {
"profile": carapace.ActionFiles(),
"reference-lock-file": carapace.ActionFiles("lock"),
})
carapace.Gen(buildCmd).PositionalCompletion(nix.ActionFlakes())
carapace.Gen(buildCmd).PositionalCompletion(action.ActionFlakeRefs([]string{"nix", "build"}))
}
3 changes: 2 additions & 1 deletion completers/nix_completer/cmd/develop.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"github.com/carapace-sh/carapace"
"github.com/carapace-sh/carapace-bin/completers/nix_completer/cmd/action"
"github.com/carapace-sh/carapace-bin/pkg/actions/os"
"github.com/carapace-sh/carapace-bin/pkg/actions/tools/nix"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -50,5 +51,5 @@ func init() {
"reference-lock-file": carapace.ActionFiles("lock"),
"unset": os.ActionEnvironmentVariables(),
})
carapace.Gen(developCmd).PositionalCompletion(nix.ActionDevShells())
carapace.Gen(developCmd).PositionalCompletion(action.ActionFlakeRefs([]string{"nix", "develop"}))
}
2 changes: 1 addition & 1 deletion pkg/actions/tools/nix/flake.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func ActionFlakes() carapace.Action {
carapace.ActionDirectories(),
carapace.ActionStyledValuesDescribed(vals...),
).ToA()
}).Cache(time.Minute).Suffix("#")
}).Cache(time.Minute)
}

func styleForRegistry(s string) string {
Expand Down

0 comments on commit 937bd7d

Please sign in to comment.