diff --git a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift index d52fca856..f10749074 100644 --- a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift @@ -138,7 +138,7 @@ struct CompletionsGenerator { case .bash: return [command].bashCompletionScript case .fish: - return FishCompletionsGenerator.generateCompletionScript(command) + return [command].fishCompletionScript default: fatalError("Invalid CompletionShell: \(shell)") } diff --git a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift index 3054187b7..b0a8f59bf 100644 --- a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift @@ -9,113 +9,298 @@ // //===----------------------------------------------------------------------===// -struct FishCompletionsGenerator { - static func generateCompletionScript(_ type: ParsableCommand.Type) -> String { - let programName = type._commandName - let helperFunctions = [ - preprocessorFunction(commandName: programName), - helperFunction(commandName: programName), - ] - let completions = generateCompletions([type]) - - return helperFunctions.joined(separator: "\n\n") + "\n\n" - + completions.joined(separator: "\n") - } -} +extension [ParsableCommand.Type] { + var fishCompletionScript: String { + // swift-format-ignore: NeverForceUnwrap + // Preconditions: + // - first must be non-empty for a fish completion script to be of use. + // - first is guaranteed non-empty in the one place where this computed var is used. + let commandName = first!._commandName + return """ + function \(shouldOfferCompletionsForFunctionName) -a expected_commands -a expected_positional_index + set -l unparsed_tokens (\(tokensFunctionName) -pc) + set -l positional_index 0 + set -l commands -// MARK: - Private functions + switch $unparsed_tokens[1] + \(commandCases) + end -extension FishCompletionsGenerator { - private static func generateCompletions(_ commands: [ParsableCommand.Type]) - -> [String] - { - guard let type = commands.last else { return [] } - let isRootCommand = commands.count == 1 - let programName = commands[0]._commandName - var subcommands = type.configuration.subcommands - .filter { $0.configuration.shouldDisplay } + test "$commands" = "$expected_commands" -a \\( -z "$expected_positional_index" -o "$expected_positional_index" -eq "$positional_index" \\) + end - if !subcommands.isEmpty && isRootCommand { - subcommands.append(HelpCommand.self) - } + function \(tokensFunctionName) + if test (string split -m 1 -f 1 -- . "$FISH_VERSION") -gt 3 + commandline --tokens-raw $argv + else + commandline -o $argv + end + end - let helperFunctionName = helperFunctionName(commandName: programName) + function \(parseSubcommandFunctionName) -S + argparse -s r -- $argv + set -l positional_count $argv[1] + set -l option_specs $argv[2..] - var prefix = - "complete -c \(programName) -n '\(helperFunctionName) \"\(commands.map { $0._commandName }.joined(separator: separator))\"" - if !subcommands.isEmpty { - prefix += - " \"\(subcommands.map { $0._commandName }.joined(separator: separator))\"" - } - prefix += "'" + set -a commands $unparsed_tokens[1] + set -e unparsed_tokens[1] - func complete(suggestion: String) -> String { - "\(prefix) \(suggestion)" - } + set positional_index 0 - let subcommandCompletions: [String] = subcommands.map { subcommand in - let escapedAbstract = subcommand.configuration.abstract.fishEscape() - let suggestion = - "-f -a '\(subcommand._commandName)' -d '\(escapedAbstract)'" - return complete(suggestion: suggestion) - } + while true + argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null + set unparsed_tokens $argv + set positional_index (math $positional_index + 1) + if test (count $unparsed_tokens) -eq 0 -o \\( -z "$_flag_r" -a "$positional_index" -gt "$positional_count" \\) + return 0 + end + set -e unparsed_tokens[1] + end + end + + function \(completeDirectoriesFunctionName) + set -l token (commandline -t) + string match -- '*/' $token + set -l subdirs $token*/ + printf '%s\\n' $subdirs + end + + function \(customCompletionFunctionName) + set -x \(CompletionShell.shellEnvironmentVariableName) fish + set -x \(CompletionShell.shellVersionEnvironmentVariableName) $FISH_VERSION + + set -l tokens (\(tokensFunctionName) -p) + if test -z (\(tokensFunctionName) -t) + set -l index (count (\(tokensFunctionName) -pc)) + set tokens $tokens[..$index] \\'\\' $tokens[(math $index + 1)..] + end + command $tokens[1] $argv $tokens + end + + complete -c '\(commandName)' -f + \(completions.joined(separator: "\n")) + """ + } + + private var commandCases: String { + let subcommands = subcommands + // swift-format-ignore: NeverForceUnwrap + // Precondition: last is guaranteed to be non-empty + return """ + case '\(last!._commandName)' + \(parseSubcommandFunctionName) \(positionalArgumentCountArguments) \( + completableArguments + .compactMap(\.optionSpec) + .map { "'\($0.fishEscapeForSingleQuotedString())'" } + .joined(separator: separator) + )\( + subcommands.isEmpty + ? "" + : """ + + switch $unparsed_tokens[1] + \(subcommands.map { (self + [$0]).commandCases }.joined(separator: "\n")) + end + """ + ) + """ + .indentingEachLine(by: 4) + } + + private var completions: [String] { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + let prefix = """ + complete -c '\(first!._commandName)'\ + -n '\(shouldOfferCompletionsForFunctionName)\ + "\(map { $0._commandName }.joined(separator: separator))" + """ + + let subcommands = subcommands + + var positionalIndex = 0 let argumentCompletions = - commands - .argumentsForHelp(visibility: .default) - .compactMap { $0.argumentSegments(commands) } - .map { $0.joined(separator: " ") } - .map { complete(suggestion: $0) } - - let completionsFromSubcommands = subcommands.flatMap { subcommand in - generateCompletions(commands + [subcommand]) - } + completableArguments + .map { (arg: ArgumentDefinition) in + """ + \(prefix)\(arg.isPositional + ? """ + \({ + positionalIndex += 1 + return " \(positionalIndex)" + }()) + """ + : "" + )' \(argumentSegments(arg).joined(separator: separator)) + """ + } + + positionalIndex += 1 - return completionsFromSubcommands + argumentCompletions - + subcommandCompletions + return + argumentCompletions + + subcommands.map { subcommand in + "\(prefix) \(positionalIndex)' -fa '\(subcommand._commandName)' -d '\(subcommand.configuration.abstract.fishEscapeForSingleQuotedString())'" + } + + subcommands.flatMap { subcommand in + (self + [subcommand]).completions + } } -} -extension ArgumentDefinition { - fileprivate func argumentSegments(_ commands: [ParsableCommand.Type]) - -> [String]? - { - guard help.visibility.base == .default - else { return nil } + private var subcommands: Self { + // swift-format-ignore: NeverForceUnwrap + // Precondition: last is guaranteed to be non-empty + var subcommands = last!.configuration.subcommands + .filter { $0.configuration.shouldDisplay } + if count == 1 { + subcommands.addHelpSubcommandIfMissing() + } + return subcommands + } + private var completableArguments: [ArgumentDefinition] { + argumentsForHelp(visibility: .default).compactMap { arg in + switch arg.completion.kind { + case .default where arg.names.isEmpty: + return nil + default: + return + arg.help.visibility.base == .default + ? arg + : nil + } + } + } + + private func argumentSegments(_ arg: ArgumentDefinition) -> [String] { var results: [String] = [] - if !names.isEmpty { - results += names.map { $0.asFishSuggestion } + if !arg.names.isEmpty { + results += arg.names.map { $0.asFishSuggestion } + if !arg.help.abstract.isEmpty { + results += [ + "-d '\(arg.help.abstract.fishEscapeForSingleQuotedString())'" + ] + } } - if !help.abstract.isEmpty { - results += ["-d '\(help.abstract.fishEscape())'"] - } + let r = arg.isPositional ? "" : "r" - switch completion.kind { - case .default where names.isEmpty: - return nil + switch arg.completion.kind { case .default: + if case .unary = arg.update { + results += ["-\(r)fka ''"] + } break case .list(let list): - results += ["-r -f -k -a '\(list.joined(separator: " "))'"] + results += ["-\(r)fka '\(list.joined(separator: separator))'"] case .file(let extensions): - let pattern = "*.{\(extensions.joined(separator: ","))}" - results += ["-r -f -a '(for i in \(pattern); echo $i;end)'"] + switch extensions.count { + case 0: + results += ["-\(r)F"] + case 1: + results += [ + """ + -\(r)fa '(\ + for p in (string match -e -- \\'*/\\' (commandline -t);or printf \\n)*.\\'\(extensions.map { $0.fishEscapeForSingleQuotedString(iterationCount: 2) }.joined())\\';printf %s\\n $p;end;\ + __fish_complete_directories (commandline -t) \\'\\'\ + )' + """ + ] + default: + results += [ + """ + -\(r)fa '(\ + set -l exts \(extensions.map { "\\'\($0.fishEscapeForSingleQuotedString(iterationCount: 2))\\'" }.joined(separator: separator));\ + for p in (string match -e -- \\'*/\\' (commandline -t);or printf \\n)*.{$exts};printf %s\\n $p;end;\ + __fish_complete_directories (commandline -t) \\'\\'\ + )' + """ + ] + } case .directory: - results += ["-r -f -a '(__fish_complete_directories)'"] + results += ["-\(r)fa '(\(completeDirectoriesFunctionName))'"] case .shellCommand(let shellCommand): - results += ["-r -f -a '(\(shellCommand))'"] + results += ["-\(r)fka '(\(shellCommand))'"] case .custom: - guard let commandName = commands.first?._commandName else { return nil } results += [ - "-r -f -a '(command \(commandName) \(customCompletionCall(commands)) (commandline -opc)[1..-1])'" + """ + -\(r)fka '(\(customCompletionFunctionName) \(arg.customCompletionCall(self)))' + """ ] } return results } + + var positionalArgumentCountArguments: String { + let positionalArguments = positionalArguments + return """ + \(positionalArguments.contains(where: { $0.isRepeatingPositional }) ? "-r " : "")\(positionalArguments.count) + """ + } + + private var shouldOfferCompletionsForFunctionName: String { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + "__\(first!._commandName)_should_offer_completions_for" + } + + private var tokensFunctionName: String { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + "__\(first!._commandName)_tokens" + } + + private var parseSubcommandFunctionName: String { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + "__\(first!._commandName)_parse_subcommand" + } + + private var completeDirectoriesFunctionName: String { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + "__\(first!._commandName)_complete_directories" + } + + private var customCompletionFunctionName: String { + // swift-format-ignore: NeverForceUnwrap + // Precondition: first is guaranteed to be non-empty + "__\(first!._commandName)_custom_completion" + } +} + +extension ArgumentDefinition { + fileprivate var optionSpec: String? { + guard let shortName = name(.short) else { + guard let longName = name(.long) else { + return nil + } + return optionSpecRequiresValue(longName) + } + guard let longName = name(.long) else { + return optionSpecRequiresValue(shortName) + } + return optionSpecRequiresValue("\(shortName)/\(longName)") + } + + private func name(_ nameType: Name.Case) -> String? { + names.first(where: { + $0.case == nameType + })? + .valueString + } + + private func optionSpecRequiresValue(_ optionSpec: String) -> String { + switch update { + case .unary: + return "\(optionSpec)=" + default: + return optionSpec + } + } } extension Name { @@ -132,70 +317,15 @@ extension Name { } extension String { - fileprivate func fishEscape() -> String { - replacingOccurrences(of: "'", with: #"\'"#) + fileprivate func fishEscapeForSingleQuotedString( + iterationCount: UInt64 = 1 + ) -> Self { + iterationCount == 0 + ? self + : replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "'", with: "\\'") + .fishEscapeForSingleQuotedString(iterationCount: iterationCount - 1) } } -extension FishCompletionsGenerator { - - private static var separator: String { " " } - - private static func preprocessorFunctionName(commandName: String) -> String { - "_swift_\(commandName)_preprocessor" - } - - private static func preprocessorFunction(commandName: String) -> String { - """ - # A function which filters options which starts with "-" from $argv. - function \(preprocessorFunctionName(commandName: commandName)) - set -l results - for i in (seq (count $argv)) - switch (echo $argv[$i] | string sub -l 1) - case '-' - case '*' - echo $argv[$i] - end - end - end - """ - } - - private static func helperFunctionName(commandName: String) -> String { - "_swift_" + commandName + "_using_command" - } - - private static func helperFunction(commandName: String) -> String { - let functionName = helperFunctionName(commandName: commandName) - let preprocessorFunctionName = preprocessorFunctionName( - commandName: commandName) - return """ - function \(functionName) - set -gx \(CompletionShell.shellEnvironmentVariableName) fish - set -gx \(CompletionShell.shellVersionEnvironmentVariableName) "$FISH_VERSION" - set -l currentCommands (\(preprocessorFunctionName) (commandline -opc)) - set -l expectedCommands (string split \"\(separator)\" $argv[1]) - set -l subcommands (string split \"\(separator)\" $argv[2]) - if [ (count $currentCommands) -ge (count $expectedCommands) ] - for i in (seq (count $expectedCommands)) - if [ $currentCommands[$i] != $expectedCommands[$i] ] - return 1 - end - end - if [ (count $currentCommands) -eq (count $expectedCommands) ] - return 0 - end - if [ (count $subcommands) -gt 1 ] - for i in (seq (count $subcommands)) - if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ] - return 1 - end - end - end - return 0 - end - return 1 - end - """ - } -} +private var separator: String { " " } diff --git a/Tests/ArgumentParserExampleTests/Snapshots/testMathFishCompletionScript().fish b/Tests/ArgumentParserExampleTests/Snapshots/testMathFishCompletionScript().fish index d109f9410..4bce4f100 100644 --- a/Tests/ArgumentParserExampleTests/Snapshots/testMathFishCompletionScript().fish +++ b/Tests/ArgumentParserExampleTests/Snapshots/testMathFishCompletionScript().fish @@ -1,70 +1,111 @@ -# A function which filters options which starts with "-" from $argv. -function _swift_math_preprocessor - set -l results - for i in (seq (count $argv)) - switch (echo $argv[$i] | string sub -l 1) - case '-' - case '*' - echo $argv[$i] +function __math_should_offer_completions_for -a expected_commands -a expected_positional_index + set -l unparsed_tokens (__math_tokens -pc) + set -l positional_index 0 + set -l commands + + switch $unparsed_tokens[1] + case 'math' + __math_parse_subcommand 0 'version' 'h/help' + switch $unparsed_tokens[1] + case 'add' + __math_parse_subcommand -r 1 'x/hex-output' 'version' 'h/help' + case 'multiply' + __math_parse_subcommand -r 1 'x/hex-output' 'version' 'h/help' + case 'stats' + __math_parse_subcommand 0 'version' 'h/help' + switch $unparsed_tokens[1] + case 'average' + __math_parse_subcommand -r 1 'kind=' 'version' 'h/help' + case 'stdev' + __math_parse_subcommand -r 1 'version' 'h/help' + case 'quantiles' + __math_parse_subcommand -r 3 'file=' 'directory=' 'shell=' 'custom=' 'version' 'h/help' + end + case 'help' + __math_parse_subcommand -r 1 'version' end end + + test "$commands" = "$expected_commands" -a \( -z "$expected_positional_index" -o "$expected_positional_index" -eq "$positional_index" \) end -function _swift_math_using_command - set -gx SAP_SHELL fish - set -gx SAP_SHELL_VERSION "$FISH_VERSION" - set -l currentCommands (_swift_math_preprocessor (commandline -opc)) - set -l expectedCommands (string split " " $argv[1]) - set -l subcommands (string split " " $argv[2]) - if [ (count $currentCommands) -ge (count $expectedCommands) ] - for i in (seq (count $expectedCommands)) - if [ $currentCommands[$i] != $expectedCommands[$i] ] - return 1 - end - end - if [ (count $currentCommands) -eq (count $expectedCommands) ] +function __math_tokens + if test (string split -m 1 -f 1 -- . "$FISH_VERSION") -gt 3 + commandline --tokens-raw $argv + else + commandline -o $argv + end +end + +function __math_parse_subcommand -S + argparse -s r -- $argv + set -l positional_count $argv[1] + set -l option_specs $argv[2..] + + set -a commands $unparsed_tokens[1] + set -e unparsed_tokens[1] + + set positional_index 0 + + while true + argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null + set unparsed_tokens $argv + set positional_index (math $positional_index + 1) + if test (count $unparsed_tokens) -eq 0 -o \( -z "$_flag_r" -a "$positional_index" -gt "$positional_count" \) return 0 end - if [ (count $subcommands) -gt 1 ] - for i in (seq (count $subcommands)) - if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ] - return 1 - end - end - end - return 0 + set -e unparsed_tokens[1] + end +end + +function __math_complete_directories + set -l token (commandline -t) + string match -- '*/' $token + set -l subdirs $token*/ + printf '%s\n' $subdirs +end + +function __math_custom_completion + set -x SAP_SHELL fish + set -x SAP_SHELL_VERSION $FISH_VERSION + + set -l tokens (__math_tokens -p) + if test -z (__math_tokens -t) + set -l index (count (__math_tokens -pc)) + set tokens $tokens[..$index] \'\' $tokens[(math $index + 1)..] end - return 1 + command $tokens[1] $argv $tokens end -complete -c math -n '_swift_math_using_command "math add"' -l hex-output -s x -d 'Use hexadecimal notation for the result.' -complete -c math -n '_swift_math_using_command "math add"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math add"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math multiply"' -l hex-output -s x -d 'Use hexadecimal notation for the result.' -complete -c math -n '_swift_math_using_command "math multiply"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math multiply"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math stats average"' -l kind -d 'The kind of average to provide.' -r -f -k -a 'mean median mode' -complete -c math -n '_swift_math_using_command "math stats average"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math stats average"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math stats stdev"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math stats stdev"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -r -f -k -a 'alphabet alligator branch braggart' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -r -f -a '(command math ---completion stats quantiles -- customArg (commandline -opc)[1..-1])' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -l file -r -f -a '(for i in *.{txt,md}; echo $i;end)' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -l directory -r -f -a '(__fish_complete_directories)' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -l shell -r -f -a '(head -100 /usr/share/dict/words | tail -50)' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -l custom -r -f -a '(command math ---completion stats quantiles -- --custom (commandline -opc)[1..-1])' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math stats quantiles"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math stats" "average stdev quantiles"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math stats" "average stdev quantiles"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math stats" "average stdev quantiles"' -f -a 'average' -d 'Print the average of the values.' -complete -c math -n '_swift_math_using_command "math stats" "average stdev quantiles"' -f -a 'stdev' -d 'Print the standard deviation of the values.' -complete -c math -n '_swift_math_using_command "math stats" "average stdev quantiles"' -f -a 'quantiles' -d 'Print the quantiles of the values (TBD).' -complete -c math -n '_swift_math_using_command "math help"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -l version -d 'Show the version.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -s h -l help -d 'Show help information.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -f -a 'add' -d 'Print the sum of the values.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -f -a 'multiply' -d 'Print the product of the values.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -f -a 'stats' -d 'Calculate descriptive statistics.' -complete -c math -n '_swift_math_using_command "math" "add multiply stats help"' -f -a 'help' -d 'Show subcommand help information.' +complete -c 'math' -f +complete -c 'math' -n '__math_should_offer_completions_for "math"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math" 1' -fa 'add' -d 'Print the sum of the values.' +complete -c 'math' -n '__math_should_offer_completions_for "math" 1' -fa 'multiply' -d 'Print the product of the values.' +complete -c 'math' -n '__math_should_offer_completions_for "math" 1' -fa 'stats' -d 'Calculate descriptive statistics.' +complete -c 'math' -n '__math_should_offer_completions_for "math" 1' -fa 'help' -d 'Show subcommand help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math add"' -l hex-output -s x -d 'Use hexadecimal notation for the result.' +complete -c 'math' -n '__math_should_offer_completions_for "math add"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math add"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math multiply"' -l hex-output -s x -d 'Use hexadecimal notation for the result.' +complete -c 'math' -n '__math_should_offer_completions_for "math multiply"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math multiply"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats" 1' -fa 'average' -d 'Print the average of the values.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats" 1' -fa 'stdev' -d 'Print the standard deviation of the values.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats" 1' -fa 'quantiles' -d 'Print the quantiles of the values (TBD).' +complete -c 'math' -n '__math_should_offer_completions_for "math stats average"' -l kind -d 'The kind of average to provide.' -rfka 'mean median mode' +complete -c 'math' -n '__math_should_offer_completions_for "math stats average"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats average"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats stdev"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats stdev"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles" 1' -fka 'alphabet alligator branch braggart' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles" 2' -fka '(__math_custom_completion ---completion stats quantiles -- customArg)' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -l file -rfa '(set -l exts \'txt\' \'md\';for p in (string match -e -- \'*/\' (commandline -t);or printf \n)*.{$exts};printf %s\n $p;end;__fish_complete_directories (commandline -t) \'\')' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -l directory -rfa '(__math_complete_directories)' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -l shell -rfka '(head -100 /usr/share/dict/words | tail -50)' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -l custom -rfka '(__math_custom_completion ---completion stats quantiles -- --custom)' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -l version -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for "math stats quantiles"' -s h -l help -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for "math help"' -l version -d 'Show the version.' diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testBase_Fish().fish b/Tests/ArgumentParserUnitTests/Snapshots/testBase_Fish().fish index 5636f921a..03a03de7b 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testBase_Fish().fish +++ b/Tests/ArgumentParserUnitTests/Snapshots/testBase_Fish().fish @@ -1,61 +1,92 @@ -# A function which filters options which starts with "-" from $argv. -function _swift_base-test_preprocessor - set -l results - for i in (seq (count $argv)) - switch (echo $argv[$i] | string sub -l 1) - case '-' - case '*' - echo $argv[$i] +function __base-test_should_offer_completions_for -a expected_commands -a expected_positional_index + set -l unparsed_tokens (__base-test_tokens -pc) + set -l positional_index 0 + set -l commands + + switch $unparsed_tokens[1] + case 'base-test' + __base-test_parse_subcommand 2 'name=' 'kind=' 'other-kind=' 'path1=' 'path2=' 'path3=' 'one' 'two' 'three' 'kind-counter' 'rep1=' 'r/rep2=' 'h/help' + switch $unparsed_tokens[1] + case 'sub-command' + __base-test_parse_subcommand 0 'h/help' + case 'escaped-command' + __base-test_parse_subcommand 1 'one=' 'h/help' + case 'help' + __base-test_parse_subcommand -r 1 end end + + test "$commands" = "$expected_commands" -a \( -z "$expected_positional_index" -o "$expected_positional_index" -eq "$positional_index" \) end -function _swift_base-test_using_command - set -gx SAP_SHELL fish - set -gx SAP_SHELL_VERSION "$FISH_VERSION" - set -l currentCommands (_swift_base-test_preprocessor (commandline -opc)) - set -l expectedCommands (string split " " $argv[1]) - set -l subcommands (string split " " $argv[2]) - if [ (count $currentCommands) -ge (count $expectedCommands) ] - for i in (seq (count $expectedCommands)) - if [ $currentCommands[$i] != $expectedCommands[$i] ] - return 1 - end - end - if [ (count $currentCommands) -eq (count $expectedCommands) ] +function __base-test_tokens + if test (string split -m 1 -f 1 -- . "$FISH_VERSION") -gt 3 + commandline --tokens-raw $argv + else + commandline -o $argv + end +end + +function __base-test_parse_subcommand -S + argparse -s r -- $argv + set -l positional_count $argv[1] + set -l option_specs $argv[2..] + + set -a commands $unparsed_tokens[1] + set -e unparsed_tokens[1] + + set positional_index 0 + + while true + argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null + set unparsed_tokens $argv + set positional_index (math $positional_index + 1) + if test (count $unparsed_tokens) -eq 0 -o \( -z "$_flag_r" -a "$positional_index" -gt "$positional_count" \) return 0 end - if [ (count $subcommands) -gt 1 ] - for i in (seq (count $subcommands)) - if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ] - return 1 - end - end - end - return 0 + set -e unparsed_tokens[1] + end +end + +function __base-test_complete_directories + set -l token (commandline -t) + string match -- '*/' $token + set -l subdirs $token*/ + printf '%s\n' $subdirs +end + +function __base-test_custom_completion + set -x SAP_SHELL fish + set -x SAP_SHELL_VERSION $FISH_VERSION + + set -l tokens (__base-test_tokens -p) + if test -z (__base-test_tokens -t) + set -l index (count (__base-test_tokens -pc)) + set tokens $tokens[..$index] \'\' $tokens[(math $index + 1)..] end - return 1 + command $tokens[1] $argv $tokens end -complete -c base-test -n '_swift_base-test_using_command "base-test sub-command"' -s h -l help -d 'Show help information.' -complete -c base-test -n '_swift_base-test_using_command "base-test escaped-command"' -l one -d 'Escaped chars: \'[]\.' -complete -c base-test -n '_swift_base-test_using_command "base-test escaped-command"' -r -f -a '(command base-test ---completion escaped-command -- two (commandline -opc)[1..-1])' -complete -c base-test -n '_swift_base-test_using_command "base-test escaped-command"' -s h -l help -d 'Show help information.' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l name -d 'The user\'s name.' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l kind -r -f -k -a 'one two custom-three' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l other-kind -r -f -k -a 'b1_fish b2_fish b3_fish' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l path1 -r -f -a '(for i in *.{}; echo $i;end)' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l path2 -r -f -a '(for i in *.{}; echo $i;end)' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l path3 -r -f -k -a 'c1_fish c2_fish c3_fish' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l one -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l two -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l three -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l kind-counter -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -l rep1 -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -s r -l rep2 -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -r -f -a '(command base-test ---completion -- argument (commandline -opc)[1..-1])' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -r -f -a '(command base-test ---completion -- nested.nestedArgument (commandline -opc)[1..-1])' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -s h -l help -d 'Show help information.' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -f -a 'sub-command' -d '' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -f -a 'escaped-command' -d '' -complete -c base-test -n '_swift_base-test_using_command "base-test" "sub-command escaped-command help"' -f -a 'help' -d 'Show subcommand help information.' \ No newline at end of file +complete -c 'base-test' -f +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l name -d 'The user\'s name.' -rfka '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l kind -rfka 'one two custom-three' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l other-kind -rfka 'b1_fish b2_fish b3_fish' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l path1 -rF +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l path2 -rF +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l path3 -rfka 'c1_fish c2_fish c3_fish' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l one +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l two +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l three +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l kind-counter +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -l rep1 -rfka '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -s r -l rep2 -rfka '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test" 1' -fka '(__base-test_custom_completion ---completion -- argument)' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test" 2' -fka '(__base-test_custom_completion ---completion -- nested.nestedArgument)' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test"' -s h -l help -d 'Show help information.' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test" 3' -fa 'sub-command' -d '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test" 3' -fa 'escaped-command' -d '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test" 3' -fa 'help' -d 'Show subcommand help information.' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test sub-command"' -s h -l help -d 'Show help information.' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test escaped-command"' -l one -d 'Escaped chars: \'[]\\.' -rfka '' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test escaped-command" 1' -fka '(__base-test_custom_completion ---completion escaped-command -- two)' +complete -c 'base-test' -n '__base-test_should_offer_completions_for "base-test escaped-command"' -s h -l help -d 'Show help information.' \ No newline at end of file