Skip to content

Improve CompletionKind DocC #740

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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ extension [ParsableCommand.Type] {
\(isRootCommand
? """
emulate -RL zsh -G
setopt extendedglob
setopt extendedglob nullglob numericglobsort
unsetopt aliases banghist

local -xr \(CompletionShell.shellEnvironmentVariableName)=zsh
Expand Down
129 changes: 111 additions & 18 deletions Sources/ArgumentParser/Parsable Properties/CompletionKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,149 @@
//
//===----------------------------------------------------------------------===//

/// The type of completion to use for an argument or option.
/// The type of completion to use for an argument or option value.
///
/// For all `CompletionKind`s, the completion shell script is configured with
/// the following settings, which will not affect the requesting shell outside
/// the completion script:
///
/// ### bash
///
/// ```shell
/// shopt -s extglob
/// set +o history +o posix
/// ```
///
/// ### fish
///
/// no settings
///
/// ### zsh
///
/// ```shell
/// emulate -RL zsh -G
/// setopt extendedglob nullglob numericglobsort
/// unsetopt aliases banghist
/// ```
public struct CompletionKind {
internal enum Kind {
/// Use the default completion kind for the value's type.
case `default`

/// Use the specified list of completion strings.
case list([String])

/// Complete file names with the specified extensions.
case file(extensions: [String])

/// Complete directory names that match the specified pattern.
case directory

/// Call the given shell command to generate completions.
case shellCommand(String)

/// Generate completions using the given closure.
case custom(@Sendable ([String]) -> [String])
}

internal var kind: Kind

/// Use the default completion kind for the value's type.
/// Use the default completion kind for the argument's or option value's type.
public static var `default`: CompletionKind {
CompletionKind(kind: .default)
}

/// Use the specified list of completion strings.
/// The completion candidates are the strings in the given array.
///
/// Completion candidates are interpreted by the requesting shell as literals.
/// They must be neither escaped nor quoted; Swift Argument Parser escapes or
/// quotes them as necessary for the requesting shell.
///
/// The completion candidates are included in a completion script when it is
/// generated.
public static func list(_ words: [String]) -> CompletionKind {
CompletionKind(kind: .list(words))
}

/// Complete file names.
/// The completion candidates include directory & file names, the latter
/// filtered by the given list of extensions.
///
/// If the given list of extensions is empty, then file names are not
/// filtered.
///
/// Given file extensions must not include the `.` initial extension
/// separator.
///
/// Given file extensions are parsed by the requesting shell as globs; Swift
/// Argument Parser does not perform any escaping or quoting.
///
/// The directory/file filter & the given list of extensions are included in a
/// completion script when it is generated.
public static func file(extensions: [String] = []) -> CompletionKind {
CompletionKind(kind: .file(extensions: extensions))
}

/// Complete directory names.
/// The completion candidates are directory names.
///
/// The directory filter is included in a completion script when it is
/// generated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like these cues about how/where the different completion configurations are used 👍🏻

public static var directory: CompletionKind {
CompletionKind(kind: .directory)
}

/// Call the given shell command to generate completions.
/// The completion candidates are specified by the stdout output of the given
/// string run as a shell command when a user requests completions.
///
/// Swift Argument Parser does not perform any escaping or quoting on the
/// given shell command.
///
/// The given shell command is included in a completion script when it is
/// generated.
public static func shellCommand(_ command: String) -> CompletionKind {
CompletionKind(kind: .shellCommand(command))
}

/// Generate completions using the given closure.
/// The completion candidates are the strings in the array returned by the
/// given closure when it is run when a user requests completions.
///
/// Completion candidates are interpreted by the requesting shell as literals.
/// They must be neither escaped nor quoted; Swift Argument Parser escapes or
/// quotes them as necessary for the requesting shell.
///
/// The given closure is evaluated after a user invokes completion in their
/// shell (normally by pressing TAB); it is not evaluated when a completion
/// script is generated.
///
/// The array of strings passed to the given closure contains all the shell
/// words in the command line for the current command at completion
/// invocation; this is exclusive of words for prior or subsequent commands or
/// pipes, but inclusive of redirects & any other command line elements. Each
/// word is its own element in the argument array; they appear in the same
/// order as in the command line. Note that shell words may contain spaces if
/// they are escaped or quoted.
///
/// Shell words are passed to Swift verbatim, not unquoted. e.g., the
/// representation in Swift of the shell word `"abc\\""def"` would be exactly
/// the same, including the double quotes & the double backslash.
///
/// ### bash
///
/// In bash 3-, a process substitution (`<(…)`) in the command line prevents
/// Swift custom completion functions from being called.
///
/// In bash 4+, a process substitution (`<(…)`) is split into multiple
/// elements in the argument array: one for the starting `<(`, and one for
/// each unescaped/unquoted-space-separated token through the closing `)`.
///
/// In bash, if the cursor is between the backslash and the single quote for
/// the last escaped single quote in a word, all subsequent pipes or other
/// commands are included in the words passed to Swift. This oddity might
/// occur only when additional constraints are met. This or similar oddities
/// might occur in other circumstances.
///
/// ### fish
///
/// In fish 3-, due to a bug, the argument array includes the fish words only
/// through the word being completed. This is fixed in fish 4+.
///
/// In fish, a redirect's symbol is not included, but its source/target is.
///
/// In fish 3-, due to limitations, words are passed to Swift unquoted. e.g.,
/// the aforementioned example word would be passed as `abc\def`. This is
/// fixed in fish 4+.
///
/// ### zsh
///
/// In zsh, redirects (both their symbol & source/target) are omitted.
@preconcurrency
public static func custom(
_ completion: @Sendable @escaping ([String]) -> [String]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ __math_custom_complete() {

_math() {
emulate -RL zsh -G
setopt extendedglob
setopt extendedglob nullglob numericglobsort
unsetopt aliases banghist

local -xr SAP_SHELL=zsh
Expand Down
2 changes: 1 addition & 1 deletion Tests/ArgumentParserUnitTests/Snapshots/testBase_Zsh().zsh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ __base-test_custom_complete() {

_base-test() {
emulate -RL zsh -G
setopt extendedglob
setopt extendedglob nullglob numericglobsort
unsetopt aliases banghist

local -xr SAP_SHELL=zsh
Expand Down