From 75194a48a15c9bd44bd0218594ed5613470a8a4c Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Tue, 13 May 2025 00:00:31 -0400 Subject: [PATCH 1/2] Replace the cursor index within the completing word parameter of custom-completion closures with a `String` `completionPrefix` parameter. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Examples/math/Math.swift | 2 +- .../Parsable Properties/CompletionKind.swift | 18 +++++++-------- .../Parsing/CommandParser.swift | 22 ++++++++++++++++--- Sources/ArgumentParserToolInfo/ToolInfo.swift | 4 ++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Examples/math/Math.swift b/Examples/math/Math.swift index b4160dda..eec24fee 100644 --- a/Examples/math/Math.swift +++ b/Examples/math/Math.swift @@ -259,7 +259,7 @@ extension Math.Statistics { } } -func customCompletion(_ s: [String], _: Int, _: Int) -> [String] { +func customCompletion(_ s: [String], _: Int, _: String) -> [String] { (s.last ?? "").starts(with: "a") ? ["aardvark", "aaaaalbert"] : ["hello", "helicopter", "heliotrope"] diff --git a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift index 2d229ba2..e6ca3803 100644 --- a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift +++ b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift @@ -40,7 +40,7 @@ public struct CompletionKind { case file(extensions: [String]) case directory case shellCommand(String) - case custom(@Sendable ([String], Int, Int) -> [String]) + case custom(@Sendable ([String], Int, String) -> [String]) case customDeprecated(@Sendable ([String]) -> [String]) } @@ -126,11 +126,11 @@ public struct CompletionKind { /// 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). /// - /// The first of the two `Int` arguments is the 0-based index of the word - /// for which completions are being requested within the given `[String]`. + /// The second argument (an `Int`) is the 0-based index of the word for which + /// completions are being requested within the given `[String]`. /// - /// The second of the two `Int` arguments is the 0-based index of the shell - /// cursor within the word for which completions are being requested. + /// The third argument (a `String`) is the prefix of the word for which + /// completions are being requested that precedes the cursor. /// /// ### bash /// @@ -171,21 +171,21 @@ public struct CompletionKind { /// character, not as after the backslash. @preconcurrency public static func custom( - _ completion: @Sendable @escaping ([String], Int, Int) -> [String] + _ completion: @Sendable @escaping ([String], Int, String) -> [String] ) -> CompletionKind { CompletionKind(kind: .custom(completion)) } /// Deprecated; only kept for backwards compatibility. /// - /// The same as `custom(@Sendable @escaping ([String], Int, Int) -> [String])`, - /// except that index arguments are not supplied. + /// The same as `custom(@Sendable @escaping ([String], Int, String) -> [String])`, + /// except that the last two closure arguments are not supplied. @preconcurrency @available( *, deprecated, message: - "Provide a three-parameter closure instead. See custom(@Sendable @escaping ([String], Int, Int) -> [String])." + "Provide a three-parameter closure instead. See custom(@Sendable @escaping ([String], Int, String) -> [String])." ) public static func custom( _ completion: @Sendable @escaping ([String]) -> [String] diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index 5c24586a..f7ac158f 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -456,16 +456,32 @@ extension CommandParser { } guard - let s = args.popFirst(), - let cursorIndexWithinCompletingArgument = Int(s) + let arg = args.popFirst(), + let cursorIndexWithinCompletingArgument = Int(arg) else { throw ParserError.invalidState } + let completingPrefix: String + if let completingArgument = args.last { + completingPrefix = String( + completingArgument.prefix( + upTo: completingArgument.index( + completingArgument.startIndex, + offsetBy: cursorIndexWithinCompletingArgument + ) + ) + ) + } else if cursorIndexWithinCompletingArgument == 0 { + completingPrefix = "" + } else { + throw ParserError.invalidState + } + completions = complete( Array(args), completingArgumentIndex, - cursorIndexWithinCompletingArgument + completingPrefix ) case .customDeprecated(let complete): completions = complete(args) diff --git a/Sources/ArgumentParserToolInfo/ToolInfo.swift b/Sources/ArgumentParserToolInfo/ToolInfo.swift index df35f15f..808106a8 100644 --- a/Sources/ArgumentParserToolInfo/ToolInfo.swift +++ b/Sources/ArgumentParserToolInfo/ToolInfo.swift @@ -149,9 +149,9 @@ public struct ArgumentInfoV0: Codable, Hashable { case directory /// Call the given shell command to generate completions. case shellCommand(command: String) - /// Generate completions using the given closure including index arguments. + /// Generate completions using the given three-parameter closure. case custom - /// Generate completions using the given closure without index arguments. + /// Generate completions using the given one-parameter closure. @available(*, deprecated, message: "Use custom instead.") case customDeprecated } From f5ebfb553361c5cccafcca00fec9ab1cf9ccf7b1 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Sun, 18 May 2025 15:04:47 -0400 Subject: [PATCH 2/2] =?UTF-8?q?Simplify=20String.prefix(=E2=80=A6)=20call?= =?UTF-8?q?=20in=20CommandParser.swift.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Sources/ArgumentParser/Parsing/CommandParser.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index f7ac158f..f45939c6 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -465,12 +465,7 @@ extension CommandParser { let completingPrefix: String if let completingArgument = args.last { completingPrefix = String( - completingArgument.prefix( - upTo: completingArgument.index( - completingArgument.startIndex, - offsetBy: cursorIndexWithinCompletingArgument - ) - ) + completingArgument.prefix(cursorIndexWithinCompletingArgument) ) } else if cursorIndexWithinCompletingArgument == 0 { completingPrefix = ""