From 06282c73d209adb0ba70275292c5fe4eb7bd73f5 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Tue, 16 Dec 2025 17:50:22 +0200 Subject: [PATCH] [terminal] IJPL-220311 Refactor completion specs DSL Use methods instead of `var` properties: 1. It makes no sense for the client to allow reading the current state of property, the only purpose is to set the value. 2. Method way allows making boolean setters without parameters, and all other setters accept not null values. So, client should specify something only when they need to change the default. GitOrigin-RevId: 7764edbc0165bec9fff999a96880b33c8ad4b6ad --- .../terminal/JavaShellCommandSpecsProvider.kt | 44 +++++------ .../vcs/git/terminal/GitAliasContributor.kt | 2 +- .../terminal/GitShellCommandOverrideSpec.kt | 73 +++++++++---------- .../completion/spec/ShellDataGenerators.kt | 14 ++-- .../spec/dsl/ShellArgumentContext.kt | 23 ++++-- .../spec/dsl/ShellArgumentContextImpl.kt | 17 ++++- .../spec/dsl/ShellCommandContext.kt | 10 +-- .../spec/dsl/ShellCommandContextImpl.kt | 12 ++- .../dsl/ShellCompletionSuggestionContext.kt | 10 +-- .../ShellCompletionSuggestionContextImpl.kt | 18 ++++- .../completion/spec/dsl/ShellOptionContext.kt | 37 ++++------ .../spec/dsl/ShellOptionContextImpl.kt | 36 +++++++-- .../spec/dsl/ShellSuggestionContext.kt | 14 ++-- .../spec/dsl/ShellSuggestionContextBase.kt | 20 ++++- .../spec/json/ShellJsonBasedArgumentSpec.kt | 10 +-- .../spec/specs/ShellCdCommandSpec.kt | 14 ++-- .../spec/specs/make/ShellMakeCommandSpec.kt | 4 +- .../ShellCommandSpecSuggestionsTest.kt | 56 +++++++------- .../completion/ShellCommandTreeBuilderTest.kt | 28 +++---- .../completion/TerminalCompletionPopupTest.kt | 8 +- 20 files changed, 259 insertions(+), 191 deletions(-) diff --git a/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt index 935cc75eb827..3b1ef48bde4a 100644 --- a/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt +++ b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt @@ -19,10 +19,12 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { } private fun getSpecs(): ShellCommandSpec = ShellCommandSpec("java") { - parserOptions = ShellCommandParserOptions.builder() - .flagsArePosixNonCompliant(true) - .optionsMustPrecedeArguments(true) - .build() + parserOptions( + ShellCommandParserOptions.builder() + .flagsArePosixNonCompliant(true) + .optionsMustPrecedeArguments(true) + .build() + ) dynamicOptions { terminalContext -> val javaContext = JavaShellCommandContext.create(terminalContext) @@ -38,7 +40,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { val errorStreamName: @Nls String = JavaTerminalBundle.message("error.stream.name") description(JavaTerminalBundle.message("java.command.terminal.description")) option("-?", "-help", "-h") { - exclusiveOn = listOf("--help") + exclusiveOn(listOf("--help")) description(JavaTerminalBundle.message("java.command.terminal.help.option.description", errorStreamName)) } option("-jar") { @@ -49,11 +51,11 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { description(JavaTerminalBundle.message("java.command.terminal.jar.option.description")) } option("-version") { - exclusiveOn = listOf("--version") + exclusiveOn(listOf("--version")) description(JavaTerminalBundle.message("java.command.terminal.version.option.description", errorStreamName)) } option("-classpath", "-cp") { - exclusiveOn = listOf("--class-path") + exclusiveOn(listOf("--class-path")) description(JavaTerminalBundle.message("java.command.terminal.classpath.option.description")) argument { suggestions(JavaShellCommandUtils.classpathSuggestionsGenerator()) @@ -61,7 +63,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { } } option("-showversion") { - exclusiveOn = listOf("--show-version") + exclusiveOn(listOf("--show-version")) description(JavaTerminalBundle.message("java.command.terminal.show.version.option.description", errorStreamName)) } argument { @@ -88,12 +90,12 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { description(optionDescription) val info = OPTION_UI_INFO_MAP[presentableName] ?: DEFAULT_UI_OPTION_INSTANCE - repeatTimes = info.repeatTimes - separator = info.separator + repeatTimes(info.repeatTimes) + info.separator?.let { separator(it) } val argumentName = info.argumentName if (argumentName != null) { argument { - isOptional = info.isArgumentOptional + if (info.isArgumentOptional) optional() displayName(argumentName) } } @@ -104,15 +106,15 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { private fun ShellChildOptionsContext.addOptionsFromJava11() { val outputStreamName = JavaTerminalBundle.message("output.stream.name") option("--version") { - exclusiveOn = listOf("-version") + exclusiveOn(listOf("-version")) description(JavaTerminalBundle.message("java.command.terminal.version.option.description", outputStreamName)) } option("--show-version") { - exclusiveOn = listOf("-show-version") + exclusiveOn(listOf("-show-version")) description(JavaTerminalBundle.message("java.command.terminal.show.version.option.description", outputStreamName)) } option("--dry-run") { - exclusiveOn = listOf("-dry-run") + exclusiveOn(listOf("-dry-run")) description(JavaTerminalBundle.message("java.command.terminal.dry.run.option.description")) } option("--class-path") { @@ -123,7 +125,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { } } option("--help") { - exclusiveOn = listOf("-?", "-help", "-h") + exclusiveOn(listOf("-?", "-help", "-h")) description(JavaTerminalBundle.message("java.command.terminal.help.option.description", outputStreamName)) } option("--enable-preview") { @@ -132,10 +134,10 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { option("-verbose") { description(JavaTerminalBundle.message("java.command.terminal.verbose.option.description")) - separator=":" - repeatTimes = 0 + separator(":") + repeatTimes(0) argument { - isOptional = true + optional() displayName(CLASS_GC_GNI_MODULE_ARGUMENT_NAME) } } @@ -144,10 +146,10 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { private fun ShellChildOptionsContext.addOptionsFromJava8() { option("-verbose") { description(JavaTerminalBundle.message("java.command.terminal.verbose.option.description")) - separator=":" - repeatTimes = 0 + separator(":") + repeatTimes(0) argument { - isOptional = true + optional() displayName(CLASS_GC_GNI_ARGUMENT_NAME) } } diff --git a/plugins/git4idea/terminal/src/com/intellij/vcs/git/terminal/GitAliasContributor.kt b/plugins/git4idea/terminal/src/com/intellij/vcs/git/terminal/GitAliasContributor.kt index 517d44b4d149..7e10ffb9d1b4 100644 --- a/plugins/git4idea/terminal/src/com/intellij/vcs/git/terminal/GitAliasContributor.kt +++ b/plugins/git4idea/terminal/src/com/intellij/vcs/git/terminal/GitAliasContributor.kt @@ -40,7 +40,7 @@ internal fun aliasGenerator(): ShellRuntimeDataGenerator") { description(GitTerminalBundle.message("suggestion.headn.description")) - insertValue = "HEAD~" + insertValue("HEAD~") } ) } @@ -224,8 +220,8 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { suggestions(allBranchesGenerator) - isOptional = true - isVariadic = true + optional() + variadic() } } @@ -237,8 +233,8 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { suggestions(allBranchesGenerator) - isOptional = true - isVariadic = true + optional() + variadic() } } @@ -250,48 +246,47 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { } suggestions(allBranchesGenerator) suggestions(remotesGenerator) - isOptional = true - isVariadic = false + optional() } argument { displayName(GitTerminalBundle.message("rebase.arg2.name")) suggestions(localBranchesGenerator) - isOptional = true + optional() } } subcommand("push") { argument { displayName(GitTerminalBundle.message("push.arg1.name")) suggestions(remotesGenerator) - isOptional = true + optional() } argument { displayName(GitTerminalBundle.message("push.arg2.name")) suggestions(localBranchesGenerator) - isOptional = true + optional() } } subcommand("pull") { option("--rebase") { - separator = "=" + separator("=") description(GitTerminalBundle.message("pull.opt-rebase.description")) argument { displayName(GitTerminalBundle.message("pull.opt-rebase.arg1.name")) suggestions("false", "true", "merges", "preserve", "interactive") suggestions(remotesGenerator) - isOptional = true + optional() } } argument { displayName(GitTerminalBundle.message("pull.arg1.name")) suggestions(remotesGenerator) - isOptional = true + optional() } argument { displayName(GitTerminalBundle.message("pull.arg2.name")) suggestions(localBranchesGenerator) - isOptional = true + optional() } } subcommand("remote") { @@ -319,16 +314,16 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { argument { displayName(GitTerminalBundle.message("fetch.arg1.name")) suggestions(remotesGenerator) - isOptional = true + optional() } argument { displayName(GitTerminalBundle.message("fetch.arg2.name")) suggestions(localBranchesGenerator) - isOptional = true + optional() } argument { displayName(GitTerminalBundle.message("fetch.arg3.name")) - isOptional = true + optional() } } subcommand("stash") { @@ -340,7 +335,7 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { } argument { displayName(GitTerminalBundle.message("stash.branch.arg2.name")) - isOptional = true + optional() } } } @@ -356,7 +351,7 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { ) } suggestions(localOrRemoteBranchesGenerator) - isVariadic = true + variadic() } } option("-d", "--delete") { @@ -369,7 +364,7 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { ) } suggestions(localOrRemoteBranchesGenerator) - isVariadic = true + variadic() } } option("-m", "--move") { @@ -400,16 +395,16 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { argument { displayName(GitTerminalBundle.message("branch.opt-set-upstream.arg1.name")) suggestions(allBranchesGenerator) - isOptional = true + optional() } } option("--set-upstream-to") { description(GitTerminalBundle.message("branch.opt-set-upstream.description")) - separator = "=" + separator("=") argument { displayName(GitTerminalBundle.message("branch.opt-set-upstream.arg1.name")) suggestions(allBranchesGenerator) - isOptional = true + optional() } } option("--unset-upstream") { @@ -417,7 +412,7 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { argument { displayName(GitTerminalBundle.message("branch.opt-unset-upstream.arg1.name")) suggestions(localBranchesGenerator) - isOptional = true + optional() } } trackingOptions() @@ -438,15 +433,15 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { suggestions(allBranchesGenerator) - isOptional = true + optional() } argument { displayName(GitTerminalBundle.message("checkout.arg2.name")) suggestions(ShellDataGenerators.fileSuggestionsGenerator(false)) - isVariadic = true - isOptional = true + variadic() + optional() } } subcommand("merge") { @@ -459,8 +454,8 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { suggestions(allBranchesGenerator) - isVariadic = true - isOptional = true + variadic() + optional() } } subcommand("switch") { @@ -477,7 +472,7 @@ internal val gitOverrideSpec = ShellCommandSpec("git") { } argument { displayName(GitTerminalBundle.message("switch.arg2.name")) - isOptional = true + optional() } } } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt index dfe4b5c36542..b43f1764191e 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt @@ -57,8 +57,8 @@ object ShellDataGenerators { val type = if (it.type == ShellFileInfo.Type.DIRECTORY) ShellSuggestionType.FOLDER else ShellSuggestionType.FILE val name = it.name + if (type == ShellSuggestionType.FOLDER) File.separator else "" val suggestion = ShellCompletionSuggestion(name) { - this.type = type - this.prefixReplacementIndex = prefixReplacementIndex + type(type) + prefixReplacementIndex(prefixReplacementIndex) } if (type == ShellSuggestionType.FILE) { listOf(suggestion) @@ -67,9 +67,9 @@ object ShellDataGenerators { // Directory suggestion has a trailing file separator, but suggestion without it is also valid. // It is needed for the parser to consider it as a valid suggestion and not mark it as something unknown. val hiddenSuggestion = ShellCompletionSuggestion(it.name) { - this.type = ShellSuggestionType.FOLDER - this.prefixReplacementIndex = prefixReplacementIndex - this.isHidden = true + type(ShellSuggestionType.FOLDER) + prefixReplacementIndex(prefixReplacementIndex) + hidden() } listOf(suggestion, hiddenSuggestion) } @@ -80,8 +80,8 @@ object ShellDataGenerators { // It is needed for the parser to consider current typed prefix as a valid file suggestion. return if (path.isNotEmpty() && path == adjustedPrefix) { val emptySuggestion = ShellCompletionSuggestion("") { - this.prefixReplacementIndex = prefixReplacementIndex - this.isHidden = true + prefixReplacementIndex(prefixReplacementIndex) + hidden() } suggestions + emptySuggestion } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContext.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContext.kt index b3049b4bfe52..72f1a849af92 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContext.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContext.kt @@ -29,28 +29,35 @@ sealed interface ShellArgumentContext { fun displayName(supplier: Supplier<@Nls String>) /** - * Whether this argument is not required to have a value. + * Specifies that this argument is not required to have a value. * - * False by default (the argument is required). + * By default, the argument is required. */ + fun optional() + + @Deprecated("Please use optional() method instead") var isOptional: Boolean /** * Specifies that this argument can be repeated infinitely. * For example, `git add` takes a variadic argument of filenames. * - * False by default. + * By default, the argument value can be specified only once. */ + fun variadic() + + @Deprecated("Please use variadic() method instead") var isVariadic: Boolean /** - * Specifies that the options can be placed between values of the variadic argument. - * For example, it is true for `git add` command. - * We can write like this: `git add file1 file2 -v file3`. Where `-v` is the option. + * Specifies that the options can't be placed between values of the variadic argument. + * By default, this behavior is allowed. * - * True by default. + * For example, it is not allowed for `echo` command. + * If we write: `echo hello -n world`, `-n` will be considered as a variadic argument value rather than an option. + * So, we should write `echo -n hello world` instead. */ - var optionsCanBreakVariadicArg: Boolean + fun optionsCantBreakVariadicArg() /** * Generate suggestions for the argument values. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContextImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContextImpl.kt index 09c9fe7e3a7b..4e546424aa7e 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContextImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellArgumentContextImpl.kt @@ -11,13 +11,14 @@ import java.util.function.Supplier /** * Params [parentNames] and [argNumber] used to build cache key/debug name of the generators */ +@Suppress("OVERRIDE_DEPRECATION") internal class ShellArgumentContextImpl( private val parentNames: List, private val argNumber: Int ) : ShellArgumentContext { override var isOptional: Boolean = false override var isVariadic: Boolean = false - override var optionsCanBreakVariadicArg: Boolean = true + private var optionsCanBreakVariadicArg: Boolean = true private var displayNameSupplier: Supplier? = null @@ -29,6 +30,18 @@ internal class ShellArgumentContextImpl( displayNameSupplier = supplier } + override fun optional() { + isOptional = true + } + + override fun variadic() { + isVariadic = true + } + + override fun optionsCantBreakVariadicArg() { + optionsCanBreakVariadicArg = false + } + private val generators: MutableList>> = mutableListOf() override fun suggestions(content: suspend (ShellRuntimeContext) -> List) { @@ -42,7 +55,7 @@ internal class ShellArgumentContextImpl( override fun suggestions(vararg names: String) { val generator = ShellRuntimeDataGenerator(debugName = createCacheKey()) { - names.map { ShellCompletionSuggestion(it) { type = ShellSuggestionType.ARGUMENT } } + names.map { ShellCompletionSuggestion(it) { type(ShellSuggestionType.ARGUMENT) } } } generators.add(generator) } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContext.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContext.kt index bd22a901fa12..a0fca5274755 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContext.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContext.kt @@ -17,17 +17,15 @@ import org.jetbrains.annotations.ApiStatus @ShellCommandSpecDsl sealed interface ShellCommandContext : ShellSuggestionContext { /** - * Whether this command can't be executed without mentioning the subcommand. - * - * False by default. + * Specifies that this command can't be executed without mentioning the subcommand. */ - var requiresSubcommand: Boolean + fun requiresSubcommand() /** * Allows modifying default parser options. * @see [ShellCommandParserOptions] */ - var parserOptions: ShellCommandParserOptions + fun parserOptions(options: ShellCommandParserOptions) /** * Specify the subcommands of the current command. @@ -62,7 +60,7 @@ sealed interface ShellCommandContext : ShellSuggestionContext { /** * Specifies that this Shell command should have an argument. * Note that arguments are not optional by default. - * If your argument is not necessary to be specified, then set [ShellArgumentContext.isOptional] to true. + * If your argument is not necessary to be specified, specify [ShellArgumentContext.optional]. * Arguments should be defined in the same order as it is expected in the command line. * @param [content] description of the argument */ diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContextImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContextImpl.kt index 888bbe297dbb..0a2f129bb1a9 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContextImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCommandContextImpl.kt @@ -14,8 +14,8 @@ internal class ShellCommandContextImpl( names: List, private val parentNames: List = emptyList() ) : ShellSuggestionContextBase(names), ShellCommandContext { - override var requiresSubcommand: Boolean = false - override var parserOptions: ShellCommandParserOptions = ShellCommandParserOptions.DEFAULT + private var requiresSubcommand: Boolean = false + private var parserOptions: ShellCommandParserOptions = ShellCommandParserOptions.DEFAULT private var subcommandSuppliers: MutableList List> = mutableListOf() private var dynamicOptionSuppliers: MutableList List> = mutableListOf() @@ -24,6 +24,14 @@ internal class ShellCommandContextImpl( private val parentNamesWithSelf: List = parentNames + names.first() + override fun requiresSubcommand() { + requiresSubcommand = true + } + + override fun parserOptions(options: ShellCommandParserOptions) { + parserOptions = options + } + override fun subcommands(content: suspend ShellChildCommandsContext.(ShellRuntimeContext) -> Unit) { val supplier: suspend (ShellRuntimeContext) -> List = { shellContext -> val context = ShellChildCommandsContextImpl(parentNamesWithSelf) diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContext.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContext.kt index 5e1b02d5958d..b45bac1d8586 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContext.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContext.kt @@ -15,7 +15,7 @@ sealed interface ShellCompletionSuggestionContext : ShellSuggestionContext { /** * Used for now only to automatically configure the icon. */ - var type: ShellSuggestionType + fun type(type: ShellSuggestionType) /** * Custom icon instead of autodetected from [type]. @@ -30,15 +30,15 @@ sealed interface ShellCompletionSuggestionContext : ShellSuggestionContext { * 2. If typed prefix is `foo/b` and suggestion name is `bar` (we want to suggest the part of the path after `/`), * then the replacement index should be `4`. */ - var prefixReplacementIndex: Int + fun prefixReplacementIndex(index: Int) /** - * If true, then this suggestion won't be shown in the completion popup. - * It may be needed to specify that this suggestion is also a valid value for the argument. + * Marks this suggestion to not show it in the completion popup. + * It may be necessary to specify that this suggestion is also a valid value for the argument. * So parser will be able to distinguish it and not mark it as something unknown. * * For example, if there is a directory suggestion, then it may have a trailing file separator or may not. * Both options are acceptable, but only one of them should be shown in the completion popup. */ - var isHidden: Boolean + fun hidden() } \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContextImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContextImpl.kt index 0082ceb4e89f..e496f71087b4 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContextImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellCompletionSuggestionContextImpl.kt @@ -11,15 +11,27 @@ import javax.swing.Icon internal class ShellCompletionSuggestionContextImpl( private val name: String, ) : ShellSuggestionContextBase(listOf(name)), ShellCompletionSuggestionContext { - override var type: ShellSuggestionType = ShellSuggestionType.ARGUMENT + private var type: ShellSuggestionType = ShellSuggestionType.ARGUMENT private var icon: Icon? = null - override var prefixReplacementIndex: Int = 0 - override var isHidden: Boolean = false + private var prefixReplacementIndex: Int = 0 + private var isHidden: Boolean = false + + override fun type(type: ShellSuggestionType) { + this.type = type + } override fun icon(icon: Icon) { this.icon = icon } + override fun prefixReplacementIndex(index: Int) { + this.prefixReplacementIndex = index + } + + override fun hidden() { + isHidden = true + } + fun build(): ShellCompletionSuggestion { return ShellCompletionSuggestionImpl( name = name, diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContext.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContext.kt index 4bdd8502aea6..7582f2361e0e 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContext.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContext.kt @@ -16,56 +16,47 @@ import org.jetbrains.annotations.ApiStatus @ShellCommandSpecDsl sealed interface ShellOptionContext : ShellSuggestionContext { /** - * Whether this option can be available for all subcommands of the current Shell command. - * - * False by default. + * Makes this option available for all subcommands of the current Shell command. */ - var isPersistent: Boolean + fun persistent() /** - * Whether this option always must be present in the Shell command call. - * - * False by default. + * Specifies that this option always must be present in the Shell command call. */ - var isRequired: Boolean + fun required() /** - * The separator between the option name and the argument value (if option has an argument). + * Specifies the separator between the option name and the argument value (if the option has an argument). * For example, in case of `--opt=value`, the separator should be `=`. - * - * Whitespace is used as a separator by default (but the value of the property is null in this case). + * Whitespace is used as a separator by default. */ - var separator: String? + fun separator(separator: String) /** - * The maximum amount of times this option can be present in the command line. + * Specifies the maximum number of times this option can be present in the command line. * Zero value means that it can be repeated infinitely. * If the option is already present this number of times in the command, it won't be shown in the completion popup anymore. * * One by default. */ - var repeatTimes: Int + fun repeatTimes(times: Int) /** - * Names of the options with those this option cannot be used. + * Specifies the names of the options with those this option cannot be used. * If any of such options is used in the command, this option won't be shown in the completion popup. - * - * Empty list by default. */ - var exclusiveOn: List + fun exclusiveOn(on: List) /** - * Names of the options required to use this option. + * Specifies names of the options required to use this option. * Until all such options are used in the command, this option won't be shown in the completion popup. - * - * Empty list by default. */ - var dependsOn: List + fun dependsOn(on: List) /** * Specifies that this Shell option should have an argument. * Note that arguments are not optional by default. - * If your argument is not necessary to be specified, then set [ShellArgumentContext.isOptional] to true. + * If your argument is not necessary to be specified, specify [ShellArgumentContext.optional]. * Arguments should be defined in the same order as it is expected in the command line. * @param [content] description of the argument */ diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContextImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContextImpl.kt index da4b3352d2c8..920ebe5a1b7e 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContextImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellOptionContextImpl.kt @@ -12,15 +12,39 @@ internal class ShellOptionContextImpl( names: List, private val parentCommandNames: List ) : ShellSuggestionContextBase(names), ShellOptionContext { - override var isPersistent: Boolean = false - override var isRequired: Boolean = false - override var separator: String? = null - override var repeatTimes: Int = 1 - override var exclusiveOn: List = emptyList() - override var dependsOn: List = emptyList() + private var isPersistent: Boolean = false + private var isRequired: Boolean = false + private var separator: String? = null + private var repeatTimes: Int = 1 + private var exclusiveOn: List = emptyList() + private var dependsOn: List = emptyList() private val arguments: MutableList = mutableListOf() + override fun persistent() { + isPersistent = true + } + + override fun required() { + isRequired = true + } + + override fun separator(separator: String) { + this.separator = separator + } + + override fun repeatTimes(times: Int) { + repeatTimes = times + } + + override fun exclusiveOn(on: List) { + exclusiveOn = on + } + + override fun dependsOn(on: List) { + dependsOn = on + } + override fun argument(content: ShellArgumentContext.() -> Unit) { val context = ShellArgumentContextImpl(parentCommandNames + names.first(), argNumber = arguments.size + 1) content.invoke(context) diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContext.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContext.kt index e3ea0fadaf7f..b2e04da15019 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContext.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContext.kt @@ -3,7 +3,6 @@ package org.jetbrains.plugins.terminal.block.completion.spec.dsl import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls -import org.jetbrains.annotations.NonNls import java.util.function.Supplier /** @@ -12,12 +11,10 @@ import java.util.function.Supplier @ApiStatus.Experimental @ShellCommandSpecDsl sealed interface ShellSuggestionContext { - val names: List<@NonNls String> - /** - * The string to be shown in the completion popup instead of command/option name. + * Sets the string to be shown in the completion popup instead of command/option name. */ - var displayName: String? + fun displayName(name: String) /** * Text to be shown in the documentation popup for this command/option. @@ -34,12 +31,15 @@ sealed interface ShellSuggestionContext { * Supports specifying caret position after completion item insertion in a form `some{caret}item`. * In this example `someitem` text will be inserted and caret is placed between `some` and `item`. */ - var insertValue: String? + fun insertValue(value: String) + + @set:Deprecated("Please use priority() method instead") + var priority: Int /** * Int from 0 to 100 with default 50. * Allows specifying the order of the items in the completion popup. * The greater the number, the closer the item will be to the first place. */ - var priority: Int + fun priority(priority: Int) } \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContextBase.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContextBase.kt index e677f22fdab6..f02d055eb26f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContextBase.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/dsl/ShellSuggestionContextBase.kt @@ -5,10 +5,12 @@ import org.jetbrains.annotations.Nls import java.util.function.Supplier internal abstract class ShellSuggestionContextBase( - final override val names: List, + protected val names: List, ) : ShellSuggestionContext { - override var displayName: String? = null - override var insertValue: String? = null + protected var displayName: String? = null + protected var insertValue: String? = null + + @Suppress("OVERRIDE_DEPRECATION") override var priority: Int = 50 set(value) { if (value in 0..100) { @@ -19,6 +21,10 @@ internal abstract class ShellSuggestionContextBase( protected var descriptionSupplier: Supplier<@Nls String>? = null + override fun displayName(name: String) { + displayName = name + } + override fun description(text: String) { descriptionSupplier = Supplier { text } } @@ -27,6 +33,14 @@ internal abstract class ShellSuggestionContextBase( descriptionSupplier = supplier } + override fun insertValue(value: String) { + this.insertValue = value + } + + override fun priority(priority: Int) { + this.priority = priority + } + init { if (names.isEmpty()) { error("At least one name must be provided") diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/json/ShellJsonBasedArgumentSpec.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/json/ShellJsonBasedArgumentSpec.kt index 1b95c5f01cd9..d5f8814dc5c8 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/json/ShellJsonBasedArgumentSpec.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/json/ShellJsonBasedArgumentSpec.kt @@ -39,11 +39,11 @@ internal class ShellJsonBasedArgumentSpec( s.names.map { name -> // TODO: there should be a way to localize the json-based descriptions ShellCompletionSuggestion(name) { - type = ShellSuggestionType.ARGUMENT - displayName = s.displayName - if (s.description != null) description(s.description!!) - insertValue = s.insertValue - priority = s.priority + type(ShellSuggestionType.ARGUMENT) + s.displayName?.let { displayName(it) } + s.description?.let { description(it) } + s.insertValue?.let { insertValue(it) } + priority(s.priority) } } } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/ShellCdCommandSpec.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/ShellCdCommandSpec.kt index 116611c3e5fd..edca1fde4527 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/ShellCdCommandSpec.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/ShellCdCommandSpec.kt @@ -25,21 +25,21 @@ internal fun cdCommandSpec() = ShellCommandSpec("cd") { val replacementIndex = path.length + if (context.typedPrefix.startsWith('"')) 1 else 0 val suggestions = directories.flatMap { val suggestion = ShellCompletionSuggestion(it.name + File.separator) { - type = ShellSuggestionType.FOLDER - prefixReplacementIndex = replacementIndex + type(ShellSuggestionType.FOLDER) + prefixReplacementIndex(replacementIndex) } val hiddenSuggestion = ShellCompletionSuggestion(it.name) { - type = ShellSuggestionType.FOLDER - prefixReplacementIndex = replacementIndex - isHidden = true + type(ShellSuggestionType.FOLDER) + prefixReplacementIndex(replacementIndex) + hidden() } listOf(suggestion, hiddenSuggestion) } val adjustedPrefix = context.typedPrefix.removePrefix("\"").removeSuffix("'") if (path.isNotEmpty() && path == adjustedPrefix) { val emptySuggestion = ShellCompletionSuggestion("") { - prefixReplacementIndex = replacementIndex - isHidden = true + prefixReplacementIndex(replacementIndex) + hidden() } suggestions + emptySuggestion } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/make/ShellMakeCommandSpec.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/make/ShellMakeCommandSpec.kt index 47d457c6e363..eba50b19136a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/make/ShellMakeCommandSpec.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/specs/make/ShellMakeCommandSpec.kt @@ -19,7 +19,7 @@ object ShellMakeCommandSpec { fun create(): ShellCommandSpec = ShellCommandSpec("make") { description(TerminalBundle.messagePointer("make.command.description")) argument { - isVariadic = true + variadic() displayName(TerminalBundle.messagePointer("make.command.arg.displayName")) @@ -52,7 +52,7 @@ object ShellMakeCommandSpec { .mapNotNull { MakefileTarget.parse(it) } .map { makefileTarget -> ShellCompletionSuggestion(makefileTarget.name) { - type = ShellSuggestionType.ARGUMENT + type(ShellSuggestionType.ARGUMENT) @Suppress("HardCodedStringLiteral") val desc = listOfNotNull( makefileTarget.comment diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt index c4a9d160ffe8..b3df5c41eefb 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt @@ -50,18 +50,18 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin private val spec = ShellCommandSpec(commandName) { option("-a", "--asd") option("--bcde") { - isPersistent = true - repeatTimes = 2 + persistent() + repeatTimes(2) } option("--argum") { argument { - isOptional = true + optional() suggestions("all", "none", "default") } } argument { - isOptional = true + optional() suggestions("abc") } @@ -69,7 +69,7 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin subcommand("sub") { option("-o", "--opt1") option("-a") { - repeatTimes = 0 + repeatTimes(0) } option("--long") option("--withReqArg") { @@ -79,7 +79,7 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin } option("--withOptArg") { argument { - isOptional = true + optional() } } @@ -87,26 +87,26 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin suggestions("file") } argument { - isOptional = true + optional() suggestions("s1") } } subcommand("excl") { option("-a") { - exclusiveOn = listOf("-b") + exclusiveOn(listOf("-b")) } option("-b") { - exclusiveOn = listOf("-a") + exclusiveOn(listOf("-a")) } option("-c") option("-d") { - dependsOn = listOf("-a", "-c") + dependsOn(listOf("-a", "-c")) } } subcommand("reqSub") { - requiresSubcommand = true + requiresSubcommand() subcommands { subcommand("abc") } @@ -115,14 +115,14 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin subcommand("manyArgs") { argument { - isOptional = true + optional() suggestions("arg1") } argument { suggestions("arg2", "arg22") } argument { - isOptional = true + optional() suggestions("arg3") } argument { @@ -131,13 +131,15 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin } subcommand("optPrecedeArgs") { - parserOptions = ShellCommandParserOptions.builder() - .optionsMustPrecedeArguments(true) - .build() + parserOptions( + ShellCommandParserOptions.builder() + .optionsMustPrecedeArguments(true) + .build() + ) option("-c") option("-d") argument { - isOptional = true + optional() suggestions("arg") } @@ -156,7 +158,7 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin option("-a") option("--var") { argument { - isVariadic = true + variadic() suggestions("var1", "var2") } } @@ -164,11 +166,11 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin suggestions("req") } argument { - isVariadic = true + variadic() suggestions("v") } argument { - isOptional = true + optional() suggestions("opt") } } @@ -177,14 +179,14 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin option("-b") option("---") { argument { - isVariadic = true - optionsCanBreakVariadicArg = false + variadic() + optionsCantBreakVariadicArg() suggestions("var") } } argument { - isVariadic = true - optionsCanBreakVariadicArg = false + variadic() + optionsCantBreakVariadicArg() suggestions("v") } argument { @@ -209,17 +211,17 @@ internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngin option("--opt") { argument { suggestions("3", "4", "5") - isOptional = true + optional() } } argument { suggestions("1", "2", "3") - isOptional = true + optional() } argument { suggestions("2", "3", "4") - isOptional = true + optional() } } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt index 3a133d070b6f..245136b90804 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt @@ -44,13 +44,13 @@ internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { option("--bcde") option("--argum") { argument { - isOptional = true + optional() suggestions("someArg") } } argument { - isOptional = true + optional() suggestions("aaa", "bbb") } @@ -61,7 +61,7 @@ internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { option("--manyArgs") { argument() argument { - isOptional = true + optional() suggestions("a2") } } @@ -71,7 +71,7 @@ internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { option("-a") argument { - isOptional = true + optional() suggestions { listOf( ShellAliasSuggestion("alias-1", "al --manyArgs somearg"), @@ -93,23 +93,23 @@ internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { } option("--withOptArg") { argument { - isOptional = true + optional() } } option("--manyArgs") { argument() argument { - isOptional = true + optional() suggestions("a2") } } option("--skippedArg") { argument { - isOptional = true + optional() } argument() argument { - isOptional = true + optional() suggestions("opt2") } } @@ -118,22 +118,24 @@ internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { suggestions("somePath") } argument { - isOptional = true + optional() suggestions("arg1", "arg2") } } subcommand("nonPosix") { - parserOptions = ShellCommandParserOptions.builder() - .flagsArePosixNonCompliant(true) - .build() + parserOptions( + ShellCommandParserOptions.builder() + .flagsArePosixNonCompliant(true) + .build() + ) option("-a") option("-b") } subcommand("sep") { option("--withSeparator") { - separator = "=" + separator("=") argument() } } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionPopupTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionPopupTest.kt index f5d614a9de40..5ca7d7740d3b 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionPopupTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionPopupTest.kt @@ -31,14 +31,14 @@ class TerminalCompletionPopupTest : BasePlatformTestCase() { subcommand("sync") subcommand("show") { argument { - isOptional = true + optional() suggestions("roots", "files", "statuses") } } subcommand("start") { argument { - isOptional = true + optional() suggestions("platform/", "platform-ui/", "shared\\", "shared-ui\\") } } @@ -58,8 +58,8 @@ class TerminalCompletionPopupTest : BasePlatformTestCase() { } } - val abPriority = ShellCompletionSuggestion("ab") { priority = 100 } - val acPriority = ShellCompletionSuggestion("ac") { priority = 100 } + val abPriority = ShellCompletionSuggestion("ab") { priority(100) } + val acPriority = ShellCompletionSuggestion("ac") { priority(100) } listOf(abPriority, acPriority) + items.map { ShellCompletionSuggestion(it) } } }