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

Fix zsh completion for commands which output is influenced by the permissions it is run with. #2131

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions command.go
Expand Up @@ -254,6 +254,12 @@ type Command struct {
// SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions.
// Must be > 0.
SuggestionsMinimumDistance int

// InfluencedByPermissions indicates that the output is influenced by the
// permission it is run with. Thus, when a command such as sudo appears on the
// command-line, it will use commands like sudo or doas to gain extra privileges
// when retrieving information for completion. Available only to Zsh.
InfluencedByPermissions bool
}

// Context returns underlying command context. If command was executed
Expand Down
32 changes: 26 additions & 6 deletions zsh_completions.go
Expand Up @@ -21,6 +21,15 @@ import (
"os"
)

const (
// ZstyleGainPrivileges enables the use of commands like sudo or doas to
// gain extra privileges when retrieving information for completion.
ZstyleGainPrivileges = `zstyle ':completion:*:%s\*' gain-privileges yes`
// ZshCompRunCmdPermFlag indicates the command output is influenced by the
// permissions it is run with.
ZshCompRunCmdPermFlag = ` -p`
)

// GenZshCompletionFile generates zsh completion file including descriptions.
func (c *Command) GenZshCompletionFile(filename string) error {
return c.genZshCompletionFile(filename, true)
Expand Down Expand Up @@ -79,16 +88,23 @@ func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error

func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
buf := new(bytes.Buffer)
genZshComp(buf, c.Name(), includeDesc)
genZshComp(buf, c.Name(), includeDesc, c.InfluencedByPermissions)
_, err := buf.WriteTo(w)
return err
}

func genZshComp(buf io.StringWriter, name string, includeDesc bool) {
func genZshComp(buf io.StringWriter, name string, includeDesc bool, permission bool) {
compCmd := ShellCompRequestCmd
if !includeDesc {
compCmd = ShellCompNoDescRequestCmd
}
zstyleGain := ""
permFlag := ""
if permission {
zstyleGain = fmt.Sprintf(ZstyleGainPrivileges, name)
permFlag = ZshCompRunCmdPermFlag
}

WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s
compdef _%[1]s %[1]s

Expand Down Expand Up @@ -145,10 +161,14 @@ _%[1]s()
requestComp="${requestComp} \"\""
fi

__%[1]s_debug "About to call: eval ${requestComp}"
# Set zstyle if gain-privileges is requested
__%[1]s_debug "Setting zstyle: %[10]s"
%[10]s

__%[1]s_debug "About to call: _call_program%[11]s %[1]s-tag ${requestComp}"

# Use eval to handle any environment variables and such
out=$(eval ${requestComp} 2>/dev/null)
# Use _call_program to call the completion code
out=$(_call_program%[11]s %[1]s-tag ${requestComp})
__%[1]s_debug "completion output: ${out}"

# Extract the directive integer following a : from the last line
Expand Down Expand Up @@ -304,5 +324,5 @@ fi
`, name, compCmd,
ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
activeHelpMarker))
activeHelpMarker, zstyleGain, permFlag))
}
12 changes: 12 additions & 0 deletions zsh_completions_test.go
Expand Up @@ -31,3 +31,15 @@ func TestZshCompletionWithActiveHelp(t *testing.T) {
activeHelpVar := activeHelpEnvVar(c.Name())
checkOmit(t, output, fmt.Sprintf("%s=0", activeHelpVar))
}

func TestZshCompletionWithInfluencedPermission(t *testing.T) {
c := &Command{Use: "c", Run: emptyRun, InfluencedByPermissions: true}

buf := new(bytes.Buffer)
assertNoErr(t, c.GenZshCompletion(buf))
output := buf.String()

// check that related commands are being generated
check(t, output, fmt.Sprintf(ZstyleGainPrivileges, c.Name()))
check(t, output, fmt.Sprintf("_call_program -p %s-tag", c.Name()))
}