CompletionKind.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. /// The type of completion to use for an argument or option value.
  12. ///
  13. /// For all `CompletionKind`s, the completion shell script is configured with
  14. /// the following settings, which will not affect the requesting shell outside
  15. /// the completion script:
  16. ///
  17. /// ### bash
  18. ///
  19. /// ```shell
  20. /// shopt -s extglob
  21. /// set +o history +o posix
  22. /// ```
  23. ///
  24. /// ### fish
  25. ///
  26. /// no settings
  27. ///
  28. /// ### zsh
  29. ///
  30. /// ```shell
  31. /// emulate -RL zsh -G
  32. /// setopt extendedglob nullglob numericglobsort
  33. /// unsetopt aliases banghist
  34. /// ```
  35. public struct CompletionKind {
  36. internal enum Kind {
  37. case `default`
  38. case list([String])
  39. case file(extensions: [String])
  40. case directory
  41. case shellCommand(String)
  42. case custom(@Sendable ([String], Int, String) -> [String])
  43. #if !canImport(Dispatch)
  44. @available(*, unavailable, message: "DispatchSemaphore is unavailable")
  45. case customAsync(@Sendable ([String], Int, String) async -> [String])
  46. #else
  47. case customAsync(@Sendable ([String], Int, String) async -> [String])
  48. #endif
  49. case customDeprecated(@Sendable ([String]) -> [String])
  50. }
  51. internal var kind: Kind
  52. /// Use the default completion kind for the argument's or option value's type.
  53. public static var `default`: CompletionKind {
  54. CompletionKind(kind: .default)
  55. }
  56. /// The completion candidates are the strings in the given array.
  57. ///
  58. /// Completion candidates are interpreted by the requesting shell as literals.
  59. /// They must be neither escaped nor quoted; Swift Argument Parser escapes or
  60. /// quotes them as necessary for the requesting shell.
  61. ///
  62. /// The completion candidates are included in a completion script when it is
  63. /// generated.
  64. public static func list(_ words: [String]) -> CompletionKind {
  65. CompletionKind(kind: .list(words))
  66. }
  67. /// The completion candidates include directory and file names, the latter
  68. /// filtered by the given list of extensions.
  69. ///
  70. /// If the given list of extensions is empty, then file names are not
  71. /// filtered.
  72. ///
  73. /// Given file extensions must not include the `.` initial extension
  74. /// separator.
  75. ///
  76. /// Given file extensions are parsed by the requesting shell as globs; Swift
  77. /// Argument Parser does not perform any escaping or quoting.
  78. ///
  79. /// The directory/file filter and the given list of extensions are included in
  80. /// a completion script when it is generated.
  81. public static func file(extensions: [String] = []) -> CompletionKind {
  82. CompletionKind(kind: .file(extensions: extensions))
  83. }
  84. /// The completion candidates are directory names.
  85. ///
  86. /// The directory filter is included in a completion script when it is
  87. /// generated.
  88. public static var directory: CompletionKind {
  89. CompletionKind(kind: .directory)
  90. }
  91. /// The completion candidates are specified by the `stdout` output of the
  92. /// given string run as a shell command when a user requests completions.
  93. ///
  94. /// Swift Argument Parser does not perform any escaping or quoting on the
  95. /// given shell command.
  96. ///
  97. /// The given shell command is included in a completion script when it is
  98. /// generated.
  99. public static func shellCommand(_ command: String) -> CompletionKind {
  100. CompletionKind(kind: .shellCommand(command))
  101. }
  102. /// The completion candidates are the strings in the array returned by the
  103. /// given closure when it is executed in response to a user's request for
  104. /// completions.
  105. ///
  106. /// Completion candidates are interpreted by the requesting shell as literals.
  107. /// They must be neither escaped nor quoted; Swift Argument Parser escapes or
  108. /// quotes them as necessary for the requesting shell.
  109. ///
  110. /// The given closure is evaluated after a user invokes completion in their
  111. /// shell (normally by pressing TAB); it is not evaluated when a completion
  112. /// script is generated.
  113. ///
  114. /// The array of strings passed to the given closure contains all the shell
  115. /// words in the command line for the current command at completion
  116. /// invocation; this is exclusive of words for prior or subsequent commands or
  117. /// pipes, but inclusive of redirects and any other command line elements.
  118. /// Each word is its own element in the argument array; they appear in the
  119. /// same order as in the command line. Note that shell words may contain
  120. /// spaces if they are escaped or quoted.
  121. ///
  122. /// Shell words are passed to Swift verbatim, without processing or removing
  123. /// any quotes or escapes. For example, the shell word `"abc\\""def"` would be
  124. /// passed to Swift as `"abc\\""def"` (i.e. the Swift String's contents would
  125. /// include all 4 of the double quotes and the 2 consecutive backslashes).
  126. ///
  127. /// The second argument (an `Int`) is the 0-based index of the word for which
  128. /// completions are being requested within the given `[String]`.
  129. ///
  130. /// The third argument (a `String`) is the prefix of the word for which
  131. /// completions are being requested that precedes the cursor.
  132. ///
  133. /// ### bash
  134. ///
  135. /// In bash 3-, a process substitution (`<(…)`) in the command line prevents
  136. /// Swift custom completion functions from being called.
  137. ///
  138. /// In bash 4+, a process substitution (`<(…)`) is split into multiple
  139. /// elements in the argument array: one for the starting `<(`, and one for
  140. /// each unescaped/unquoted-space-separated token through the closing `)`.
  141. ///
  142. /// In bash, if the cursor is between the backslash and the single quote for
  143. /// the last escaped single quote in a word, all subsequent pipes or other
  144. /// commands are included in the words passed to Swift. This oddity might
  145. /// occur only when additional constraints are met. This or similar oddities
  146. /// might occur in other circumstances.
  147. ///
  148. /// ### fish
  149. ///
  150. /// In fish 3-, due to a bug, the argument array includes the fish words only
  151. /// through the word being completed. This is fixed in fish 4+.
  152. ///
  153. /// In fish, a redirect's symbol is not included, but its source/target is.
  154. ///
  155. /// In fish 3-, due to limitations, words are passed to Swift unquoted. For
  156. /// example, the shell word `"abc\\""def"` would be passed to Swift as
  157. /// `abc\def`. This is fixed in fish 4+.
  158. ///
  159. /// In fish 3-, the cursor index is provided based on the verbatim word, not
  160. /// based on the unquoted word, so it can be inconsistent with the unquoted
  161. /// word that is supplied to Swift. This problem does not exist in fish 4+.
  162. ///
  163. /// ### zsh
  164. ///
  165. /// In zsh, redirects (both their symbol and source/target) are omitted.
  166. ///
  167. /// In zsh, if the cursor is between a backslash and the character that it
  168. /// escapes, the shell cursor index will be indicated as after the escaped
  169. /// character, not as after the backslash.
  170. @preconcurrency
  171. public static func custom(
  172. _ completion: @Sendable @escaping ([String], Int, String) -> [String]
  173. ) -> CompletionKind {
  174. CompletionKind(kind: .custom(completion))
  175. }
  176. /// Generate completions using the given async closure.
  177. ///
  178. /// The same as `custom(@Sendable @escaping ([String], Int, String) -> [String])`,
  179. /// except that the closure is asynchronous.
  180. #if !canImport(Dispatch)
  181. @available(*, unavailable, message: "DispatchSemaphore is unavailable")
  182. @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
  183. public static func custom(
  184. _ completion: @Sendable @escaping ([String], Int, String) async -> [String]
  185. ) -> CompletionKind {
  186. fatalError("DispatchSemaphore is unavailable")
  187. }
  188. #else
  189. @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
  190. public static func custom(
  191. _ completion: @Sendable @escaping ([String], Int, String) async -> [String]
  192. ) -> CompletionKind {
  193. CompletionKind(kind: .customAsync(completion))
  194. }
  195. #endif
  196. /// Deprecated; only kept for backwards compatibility.
  197. ///
  198. /// The same as `custom(@Sendable @escaping ([String], Int, String) -> [String])`,
  199. /// except that the last two closure arguments are not supplied.
  200. @preconcurrency
  201. @available(
  202. *,
  203. deprecated,
  204. message:
  205. "Provide a three-parameter closure instead. See custom(@Sendable @escaping ([String], Int, String) -> [String])."
  206. )
  207. public static func custom(
  208. _ completion: @Sendable @escaping ([String]) -> [String]
  209. ) -> CompletionKind {
  210. CompletionKind(kind: .customDeprecated(completion))
  211. }
  212. }
  213. extension CompletionKind: Sendable {}
  214. extension CompletionKind.Kind: Sendable {}