diff --git a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift index 826af9d2b..c570fb703 100644 --- a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift @@ -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 diff --git a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift index e74f33945..03fdedcc2 100644 --- a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift +++ b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift @@ -9,56 +9,151 @@ // //===----------------------------------------------------------------------===// -/// 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 and 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 and 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. 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 executed in response to a user's request for + /// 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 and 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, without processing or removing + /// any quotes or escapes. For example, the shell word `"abc\\""def"` would be + /// passed to Swift as `"abc\\""def"` (i.e. the Swift String's contents would + /// include all 4 of the double quotes and the 2 consecutive backslashes). + /// + /// ### 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. For + /// example, the shell word `"abc\\""def"` would be passed to Swift as + /// `abc\def`. This is fixed in fish 4+. + /// + /// ### zsh + /// + /// In zsh, redirects (both their symbol and source/target) are omitted. @preconcurrency public static func custom( _ completion: @Sendable @escaping ([String]) -> [String] diff --git a/Tests/ArgumentParserExampleTests/Snapshots/testMathZshCompletionScript().zsh b/Tests/ArgumentParserExampleTests/Snapshots/testMathZshCompletionScript().zsh index cf7c3695a..fe6007067 100644 --- a/Tests/ArgumentParserExampleTests/Snapshots/testMathZshCompletionScript().zsh +++ b/Tests/ArgumentParserExampleTests/Snapshots/testMathZshCompletionScript().zsh @@ -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 diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testBase_Zsh().zsh b/Tests/ArgumentParserUnitTests/Snapshots/testBase_Zsh().zsh index ef2da963f..0b0762c78 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testBase_Zsh().zsh +++ b/Tests/ArgumentParserUnitTests/Snapshots/testBase_Zsh().zsh @@ -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