mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[Java] IDEA-385244 Support javac standard options completion in terminal
GitOrigin-RevId: b167cb6dbf75bb5431533356620b9e1d2061f938
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f2a9c94d66
commit
f8df55e707
@@ -1,3 +1,4 @@
|
||||
# java -X
|
||||
vm.option.enable.assertions.description=Enables assertions with specified granularity.
|
||||
vm.option.enable.system.assertions.description=Enables system assertions.
|
||||
vm.option.disable.assertions.description=Enables assertions with specified granularity.
|
||||
@@ -47,4 +48,47 @@ vm.option.finalization.description=Controls whether the JVM performs finalizatio
|
||||
vm.option.add.exports.description=Updates \\<module\\> to export \\<package\\> to \\<target-module\\>, regardless of module declaration. \\<target-module\\> can be ALL-UNNAMED to export to all unnamed modules.
|
||||
vm.option.source.description=Sets the \\<version\\> of the source in source-file mode.
|
||||
vm.option.disable.files.description=Disables further argument file expansion.
|
||||
vm.option.illegal.access.description=Permits or denies access to members of types in named modules by code in unnamed modules. \\<value\\> is one of "deny", "permit", "warn", or "debug" This option will be removed in a future release.
|
||||
vm.option.illegal.access.description=Permits or denies access to members of types in named modules by code in unnamed modules. \\<value\\> is one of "deny", "permit", "warn", or "debug" This option will be removed in a future release.
|
||||
|
||||
# javac -help
|
||||
vm.option.annotation.processing.description=Specifies options to pass to annotation processors.
|
||||
vm.option.add.modules.description=Specifies root modules to resolve in addition to the initial modules.
|
||||
vm.option.boot.classpath.description=Overrides the location of the bootstrap class files.
|
||||
vm.option.classpath.description=Specifies where to find user class files and annotation processors.
|
||||
vm.option.directory.description=Sets the destination directory (or class output directory) for class files.
|
||||
vm.option.deprecation.description=Enables or disables processing of deprecation warnings.
|
||||
vm.option.enable.preview.description=Enables preview language features. Used in conjunction with either -source or --release.
|
||||
vm.option.encoding.description=Specifies character encoding used by source files, such as EUC-JP and UTF-8.
|
||||
vm.option.endorsed.dirs.description=Overrides the location of the endorsed standards path.
|
||||
vm.option.extension.dirs.description=Overrides the location of the installed extensions.
|
||||
vm.option.generate.debug.information.description=Generates all debugging information, including local variables.
|
||||
vm.option.generate.none.debug.information.description=Does not generate debugging information.
|
||||
vm.option.generate.choice.debug.information.description=Generates only the kinds of debugging information specified by the comma-separated list of keywords.
|
||||
vm.option.native.header.description=Specifies where to place generated native header files.
|
||||
vm.option.help.description=Prints a synopsis of the standard options.
|
||||
vm.option.help.extra.description=Prints a synopsis of the set of extra options.
|
||||
vm.option.implicit.description=Specifies whether or not to generate class files for implicitly referenced files.
|
||||
vm.option.j.description=Passes option to the runtime system.
|
||||
vm.option.limit.modules.javac.description=Limits the universe of observable modules.
|
||||
vm.option.module.description=Compiles those source files in the named modules that are newer than the corresponding files in the output directory.
|
||||
vm.option.module.path.description=Specifies where to find application modules.
|
||||
vm.option.module.source.path.description=Specifies where to find source files when compiling code in multiple modules.
|
||||
vm.option.module.version.description=Specifies the version of modules that are being compiled.
|
||||
vm.option.nowarn.description=Disables warning messages (equivalent to -Xlint:none).
|
||||
vm.option.parameters.description=Generates metadata for reflection on method parameters.
|
||||
vm.option.proc.description=Controls annotation processing and compilation: none (compilation without annotation processing), only (only annotation processing), or full (annotation processing and compilation).
|
||||
vm.option.processor.description=Names of the annotation processors to run.
|
||||
vm.option.processor.module.path.description=Specifies the module path used for finding annotation processors.
|
||||
vm.option.processor.path.description=Specifies where to find annotation processors.
|
||||
vm.option.profile.description=Checks that the API used is available in the specified profile.
|
||||
vm.option.release.description=Compiles source code according to the rules of the Java programming language for a specified Java SE release and generates class files which target that release.
|
||||
vm.option.source.output.description=Specifies the directory used to place the generated source files.
|
||||
vm.option.source.release.description=Compiles source code according to the rules of the Java programming language for the specified Java SE release.
|
||||
vm.option.source.path.description=Specifies where to find source files.
|
||||
vm.option.system.description=Overrides the location of system modules.
|
||||
vm.option.target.description=Generates class files suitable for the specified Java SE release. The target release must be equal to or higher than the source release.
|
||||
vm.option.upgrade.module.path.description=Overrides the location of upgradeable modules.
|
||||
vm.option.verbose.description=Outputs messages about what the compiler is doing, including information about each class loaded and source file compiled.
|
||||
vm.option.version.description=Prints version information.
|
||||
vm.option.werror.description=Terminates compilation when warnings occur.
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.vmOptions
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessRunner
|
||||
import com.intellij.execution.process.OSProcessHandler
|
||||
import com.intellij.execution.process.ProcessNotCreatedException
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.containers.CollectionFactory
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
internal abstract class CommandOptionsExtractor {
|
||||
private val commandData: ConcurrentMap<String, CompletableFuture<JdkOptionsData>> =
|
||||
CollectionFactory.createConcurrentSoftValueMap()
|
||||
|
||||
protected abstract val commandName: String
|
||||
|
||||
/**
|
||||
* Command options used to print all available options
|
||||
*/
|
||||
protected abstract val commandOptions: List<String>
|
||||
|
||||
protected abstract fun getOptions(javaHome: String): JdkOptionsData
|
||||
|
||||
fun getOrComputeOptions(
|
||||
javaHome: String,
|
||||
): CompletableFuture<JdkOptionsData> {
|
||||
val future = commandData.computeIfAbsent(javaHome) { CompletableFuture.supplyAsync { getOptions(it) } }
|
||||
if (future.isDone) {
|
||||
// sometimes the timeout may appear and in order not to block the possibility to get the completion afterwards, it is better to retry
|
||||
val data = future.get()
|
||||
if (data == null) {
|
||||
commandData.remove(javaHome)
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
protected fun getCommandExecutablePath(javaHome: String): String {
|
||||
val vmExeName = if (SystemInfo.isWindows) "$commandName.exe" else commandName // do not use JavaW.exe because of issues with encoding
|
||||
return Path.of(getConvertedPath(javaHome), "bin", vmExeName).toString()
|
||||
}
|
||||
|
||||
private fun getConvertedPath(javaHome: String): String {
|
||||
// it is copied from com.intellij.openapi.projectRoots.impl.JavaSdkImpl.getConvertedHomePath
|
||||
var systemDependentName = FileUtil.toSystemDependentName(javaHome)
|
||||
if (javaHome.endsWith(File.separator)) {
|
||||
systemDependentName += File.separator
|
||||
}
|
||||
return systemDependentName
|
||||
}
|
||||
|
||||
protected fun opt(name: String, doc: String): VMOption {
|
||||
return VMOption(name, null, null, VMOptionKind.Standard, doc, VMOptionVariant.DASH, null)
|
||||
}
|
||||
|
||||
object Java : CommandOptionsExtractor() {
|
||||
@JvmStatic
|
||||
@get:JvmName("getStandardOptionList")
|
||||
val STANDARD_OPTION_LIST: List<VMOption> = listOf(
|
||||
opt("ea", VMOptionsBundle.message("vm.option.enable.assertions.description")),
|
||||
opt("enableassertions", VMOptionsBundle.message("vm.option.enable.assertions.description")),
|
||||
opt("da", VMOptionsBundle.message("vm.option.disable.assertions.description")),
|
||||
opt("disableassertions", VMOptionsBundle.message("vm.option.disable.assertions.description")),
|
||||
opt("esa", VMOptionsBundle.message("vm.option.enable.system.assertions.description")),
|
||||
opt("enablesystemassertions", VMOptionsBundle.message("vm.option.enable.system.assertions.description")),
|
||||
opt("dsa", VMOptionsBundle.message("vm.option.disable.system.assertions.description")),
|
||||
opt("disablesystemassertions", VMOptionsBundle.message("vm.option.disable.system.assertions.description")),
|
||||
opt("agentpath:", VMOptionsBundle.message("vm.option.agentpath.description")),
|
||||
opt("agentlib:", VMOptionsBundle.message("vm.option.agentlib.description")),
|
||||
opt("javaagent:", VMOptionsBundle.message("vm.option.javaagent.description")),
|
||||
opt("D", VMOptionsBundle.message("vm.option.system.property.description")),
|
||||
opt("XX:", VMOptionsBundle.message("vm.option.advanced.option.description")),
|
||||
)
|
||||
|
||||
override val commandName: String = "java"
|
||||
override val commandOptions: List<String> =
|
||||
listOf("-XX:+PrintFlagsFinal", "-XX:+UnlockDiagnosticVMOptions", "-XX:+UnlockExperimentalVMOptions", "-X")
|
||||
|
||||
override fun getOptions(javaHome: String): JdkOptionsData {
|
||||
val options =
|
||||
getOptionsForJava(javaHome)
|
||||
return JdkOptionsData(options)
|
||||
}
|
||||
|
||||
|
||||
private fun getOptionsForJava(javaHome: String): List<VMOption> {
|
||||
val output = getProcessOutput(javaHome) ?: return STANDARD_OPTION_LIST
|
||||
|
||||
val xxOptions = VMOptionsParser.parseXXOptions(output.stdout)
|
||||
val xOptions = VMOptionsParser.parseXOptions(output.stderr)
|
||||
if (xOptions != null) {
|
||||
return xOptions + xxOptions + STANDARD_OPTION_LIST
|
||||
}
|
||||
return xxOptions + STANDARD_OPTION_LIST
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object Javac : CommandOptionsExtractor() {
|
||||
val STANDARD_OPTIONS_LIST: List<VMOption> = listOf(
|
||||
opt("A", "vm.option.annotation.processing.description"),
|
||||
opt("g", "vm.option.generate.debug.information.description"),
|
||||
opt("g:", "vm.option.generate.choice.debug.information.description"),
|
||||
opt("g:none", "vm.option.generate.none.debug.information.description"),
|
||||
opt("h", "vm.option.native.header.description"),
|
||||
opt("J", "vm.option.j.description"),
|
||||
opt("d", "vm.option.directory.description"),
|
||||
opt("nowarn", "vm.option.nowarn.description"),
|
||||
opt("parameters", "vm.option.parameters.description"),
|
||||
opt("processor", "vm.option.processor.description"),
|
||||
opt("profile", "vm.option.profile.description"),
|
||||
opt("s", "vm.option.source.output.description"),
|
||||
opt("verbose", "vm.option.verbose.description"),
|
||||
opt("Werror", "vm.option.werror.description"),
|
||||
opt("proc:", "vm.option.proc.description"),
|
||||
opt("implicit:", "vm.option.implicit.description"),
|
||||
opt("encoding", "vm.option.encoding.description"),
|
||||
opt("endorseddirs", "vm.option.endorsed.dirs.description"),
|
||||
opt("extdirs", "vm.option.extension.dirs.description"),
|
||||
)
|
||||
|
||||
override val commandName: String = "javac"
|
||||
override val commandOptions: List<String>
|
||||
get() = listOf("-help", "-X")
|
||||
|
||||
override fun getOptions(javaHome: String): JdkOptionsData {
|
||||
return JdkOptionsData(getOptionsForJavac(javaHome))
|
||||
}
|
||||
|
||||
private fun getOptionsForJavac(javaHome: String): List<VMOption> {
|
||||
val processOutput = getProcessOutput(javaHome) ?: return emptyList()
|
||||
val parsedStandardOptions = VMOptionsParser.parseJavacDoubleDashedOptions(processOutput.stdout)
|
||||
return if (parsedStandardOptions != null) parsedStandardOptions + STANDARD_OPTIONS_LIST else STANDARD_OPTIONS_LIST
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getProcessOutput(javaHome: String): ProcessOutput? {
|
||||
val vmPath = getCommandExecutablePath(javaHome)
|
||||
val generalCommandLine = GeneralCommandLine(vmPath)
|
||||
generalCommandLine.addParameters(commandOptions)
|
||||
val handler = try {
|
||||
OSProcessHandler(generalCommandLine)
|
||||
}
|
||||
catch (_: ProcessNotCreatedException) {
|
||||
return null
|
||||
}
|
||||
val runner = CapturingProcessRunner(handler)
|
||||
val output = runner.runProcess(1000)
|
||||
if (output.isTimeout) {
|
||||
return null
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,32 +43,58 @@ internal object VMOptionsParser {
|
||||
}
|
||||
|
||||
internal fun parseXOptions(stderr: String): List<VMOption>? {
|
||||
val options = ArrayList<VMOption>()
|
||||
var currentOption: OptionBuilder? = null
|
||||
var tailIndex = stderr.indexOf("These extra options are subject to change without notice.")
|
||||
if (tailIndex == -1) {
|
||||
tailIndex = stderr.indexOf("The -X options are non-standard and subject to change without notice.")
|
||||
}
|
||||
if (tailIndex == -1) return null
|
||||
val separators = charArrayOf(' ', '<')
|
||||
for (line in stderr.substring(0, tailIndex).trimStart().lines()) {
|
||||
|
||||
return parseLines(ParsingType.JavaExtraOptions, stderr.substring(0, tailIndex).trimStart().lines(), mapOf("-X" to VMOptionVariant.X, "--" to VMOptionVariant.DASH_DASH), separators)
|
||||
}
|
||||
|
||||
internal fun parseJavacDoubleDashedOptions(input: String): List<VMOption>? {
|
||||
val lines = input.lines()
|
||||
val lastLine = lines.indexOf("")
|
||||
if (lastLine == -1) return null
|
||||
val beforeStartLine = lines.indexOf("where possible options include:")
|
||||
if (beforeStartLine == -1) return null
|
||||
|
||||
return parseLines(ParsingType.JavacStandardOptions, lines.subList(beforeStartLine + 1, lastLine), mapOf("--" to VMOptionVariant.DASH_DASH), charArrayOf(' '))
|
||||
}
|
||||
|
||||
private fun parseLines(type: ParsingType, lines: List<String>, allowedOptionsStart: Map<String, VMOptionVariant>, separators: CharArray): List<VMOption> {
|
||||
val options = ArrayList<VMOption>()
|
||||
var currentOption: OptionBuilder? = null
|
||||
for (line in lines) {
|
||||
val trimmed = line.trim()
|
||||
val variant = when {
|
||||
trimmed.startsWith("-X") -> VMOptionVariant.X
|
||||
trimmed.startsWith("--") -> VMOptionVariant.DASH_DASH
|
||||
else -> null
|
||||
|
||||
var variant : VMOptionVariant? = null
|
||||
for ((startString, value) in allowedOptionsStart) {
|
||||
if (trimmed.startsWith(startString)) {
|
||||
variant = value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (variant != null) {
|
||||
if (currentOption != null) {
|
||||
options.add(currentOption.build())
|
||||
}
|
||||
val indexOfSeparator = trimmed.indexOfAny(separators)
|
||||
if (indexOfSeparator != -1) {
|
||||
currentOption = OptionBuilder(variant, trimmed.substring(2, indexOfSeparator))
|
||||
currentOption.doc.add(trimmed.substring(indexOfSeparator).trim())
|
||||
currentOption = OptionBuilder(type, variant, trimmed.substring(variant.prefix().length, indexOfSeparator))
|
||||
val docCandidate = trimmed.substring(indexOfSeparator).trim()
|
||||
if (docCandidate.startsWith("<")) {
|
||||
// Option has a format "-opt <path> docs" - documentation starts after second space
|
||||
currentOption.doc.add(docCandidate)
|
||||
} else {
|
||||
// Option has a format "-opt docs" - documentation starts after first space
|
||||
currentOption.doc.add(docCandidate)
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentOption = OptionBuilder(variant, trimmed.substring(2))
|
||||
currentOption = OptionBuilder(type,variant, trimmed.substring(variant.prefix().length))
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -82,65 +108,124 @@ internal object VMOptionsParser {
|
||||
return options
|
||||
}
|
||||
|
||||
private class OptionBuilder(val variant: VMOptionVariant, name: String) {
|
||||
val name = name.split("<")[0]
|
||||
private class OptionBuilder(private val type: ParsingType, val variant: VMOptionVariant, name: String) {
|
||||
val name = type.parseName(name)
|
||||
val doc = ArrayList<String>()
|
||||
|
||||
|
||||
fun build(): VMOption {
|
||||
val vmOptionMapKey = "${variant.prefix()}$name"
|
||||
val key = VM_OPTION_DESCRIPTION_PROPERTY_KEY_MAP[vmOptionMapKey]
|
||||
val key = type.optionDescriptionPropertyKey[vmOptionMapKey]
|
||||
val description = if (key != null) {
|
||||
VMOptionsBundle.message(key)
|
||||
} else {
|
||||
LOG.warn("Option $vmOptionMapKey is not localized. Output of java command will be used instead. Please, localize it in VMOptionsBundle")
|
||||
doc.joinToString(separator = " ")
|
||||
}
|
||||
return VMOption(name, type = null, defaultValue = null, kind = VMOptionKind.Product, doc = description, variant)
|
||||
return VMOption(name, type = null, defaultValue = null, kind = type.kind, doc = description, variant)
|
||||
}
|
||||
}
|
||||
|
||||
private val VM_OPTION_DESCRIPTION_PROPERTY_KEY_MAP: Map<String, @PropertyKey(resourceBundle = BUNDLE) String> = mapOf(
|
||||
Pair("-Xbatch", "vm.option.batch.description"),
|
||||
Pair("-Xbootclasspath:", "vm.option.bootclasspath.description"),
|
||||
Pair("-Xbootclasspath/p:", "vm.option.bootclasspath.p.description"),
|
||||
Pair("-Xbootclasspath/a:", "vm.option.bootclasspath.a.description"),
|
||||
Pair("-Xdebug", "vm.option.debug.description"),
|
||||
Pair("-Xcheck:jni", "vm.option.check.jni.description"),
|
||||
Pair("-Xcomp", "vm.option.comp.description"),
|
||||
Pair("-Xdiag", "vm.option.diag.description"),
|
||||
Pair("-Xfuture", "vm.option.future.description"),
|
||||
Pair("-Xinternalversion", "vm.option.internalversion.description"),
|
||||
Pair("-Xlog:", "vm.option.log.description"),
|
||||
Pair("-Xloggc:", "vm.option.loggc.description"),
|
||||
Pair("-Xmixed", "vm.option.mixed.description"),
|
||||
Pair("-Xmn", "vm.option.mn.description"),
|
||||
Pair("-Xms", "vm.option.ms.description"),
|
||||
Pair("-Xmx", "vm.option.mx.description"),
|
||||
Pair("-Xrs", "vm.option.rs.description"),
|
||||
Pair("-Xnoclassgc", "vm.option.noclassgc.description"),
|
||||
Pair("-Xshare:auto", "vm.option.share.auto.description"),
|
||||
Pair("-Xshare:off", "vm.option.share.off.description"),
|
||||
Pair("-Xshare:on", "vm.option.share.on.description"),
|
||||
Pair("-XshowSettings", "vm.option.showSettings.description"),
|
||||
Pair("-XshowSettings:all", "vm.option.showSettings.all.description"),
|
||||
Pair("-XshowSettings:locale", "vm.option.showSettings.locale.description"),
|
||||
Pair("-XshowSettings:properties", "vm.option.showSettings.properties.description"),
|
||||
Pair("-XshowSettings:vm", "vm.option.showSettings.vm.description"),
|
||||
Pair("-XshowSettings:system", "vm.option.showSettings.system.description"),
|
||||
Pair("-Xss", "vm.option.ss.description"),
|
||||
Pair("-Xverify", "vm.option.verify.description"),
|
||||
Pair("-Xincgc", "vm.option.incgc.description"),
|
||||
Pair("-Xprof", "vm.option.prof.description"),
|
||||
Pair("-Xint", "vm.option.int.description"),
|
||||
Pair("--add-reads", "vm.option.add.reads.description"),
|
||||
Pair("--add-opens", "vm.option.add.opens.description"),
|
||||
Pair("--limit-modules", "vm.option.limit.modules.description"),
|
||||
Pair("--patch-module", "vm.option.patch.module.description"),
|
||||
Pair("--finalization=", "vm.option.finalization.description"),
|
||||
Pair("--add-exports", "vm.option.add.exports.description"),
|
||||
Pair("--source", "vm.option.source.description"),
|
||||
Pair("--disable-@files", "vm.option.disable.files.description"),
|
||||
Pair("--illegal-access=", "vm.option.illegal.access.description")
|
||||
)
|
||||
private interface ParsingType {
|
||||
val optionDescriptionPropertyKey: Map<String, @PropertyKey(resourceBundle = BUNDLE) String>
|
||||
|
||||
val kind: VMOptionKind
|
||||
|
||||
fun parseName(input: String): String
|
||||
|
||||
object JavaExtraOptions : ParsingType {
|
||||
override val optionDescriptionPropertyKey: Map<String, @PropertyKey(resourceBundle = "messages.VMOptionsBundle") String> = mapOf(
|
||||
Pair("-Xbatch", "vm.option.batch.description"),
|
||||
Pair("-Xbootclasspath:", "vm.option.bootclasspath.description"),
|
||||
Pair("-Xbootclasspath/p:", "vm.option.bootclasspath.p.description"),
|
||||
Pair("-Xbootclasspath/a:", "vm.option.bootclasspath.a.description"),
|
||||
Pair("-Xdebug", "vm.option.debug.description"),
|
||||
Pair("-Xcheck:jni", "vm.option.check.jni.description"),
|
||||
Pair("-Xcomp", "vm.option.comp.description"),
|
||||
Pair("-Xdiag", "vm.option.diag.description"),
|
||||
Pair("-Xfuture", "vm.option.future.description"),
|
||||
Pair("-Xinternalversion", "vm.option.internalversion.description"),
|
||||
Pair("-Xlog:", "vm.option.log.description"),
|
||||
Pair("-Xloggc:", "vm.option.loggc.description"),
|
||||
Pair("-Xmixed", "vm.option.mixed.description"),
|
||||
Pair("-Xmn", "vm.option.mn.description"),
|
||||
Pair("-Xms", "vm.option.ms.description"),
|
||||
Pair("-Xmx", "vm.option.mx.description"),
|
||||
Pair("-Xrs", "vm.option.rs.description"),
|
||||
Pair("-Xnoclassgc", "vm.option.noclassgc.description"),
|
||||
Pair("-Xshare:auto", "vm.option.share.auto.description"),
|
||||
Pair("-Xshare:off", "vm.option.share.off.description"),
|
||||
Pair("-Xshare:on", "vm.option.share.on.description"),
|
||||
Pair("-XshowSettings", "vm.option.showSettings.description"),
|
||||
Pair("-XshowSettings:all", "vm.option.showSettings.all.description"),
|
||||
Pair("-XshowSettings:locale", "vm.option.showSettings.locale.description"),
|
||||
Pair("-XshowSettings:properties", "vm.option.showSettings.properties.description"),
|
||||
Pair("-XshowSettings:vm", "vm.option.showSettings.vm.description"),
|
||||
Pair("-XshowSettings:system", "vm.option.showSettings.system.description"),
|
||||
Pair("-Xss", "vm.option.ss.description"),
|
||||
Pair("-Xverify", "vm.option.verify.description"),
|
||||
Pair("-Xincgc", "vm.option.incgc.description"),
|
||||
Pair("-Xprof", "vm.option.prof.description"),
|
||||
Pair("-Xint", "vm.option.int.description"),
|
||||
Pair("--add-reads", "vm.option.add.reads.description"),
|
||||
Pair("--add-opens", "vm.option.add.opens.description"),
|
||||
Pair("--limit-modules", "vm.option.limit.modules.description"),
|
||||
Pair("--patch-module", "vm.option.patch.module.description"),
|
||||
Pair("--finalization=", "vm.option.finalization.description"),
|
||||
Pair("--add-exports", "vm.option.add.exports.description"),
|
||||
Pair("--source", "vm.option.source.description"),
|
||||
Pair("--disable-@files", "vm.option.disable.files.description"),
|
||||
Pair("--illegal-access=", "vm.option.illegal.access.description")
|
||||
)
|
||||
|
||||
override val kind: VMOptionKind = VMOptionKind.Product
|
||||
|
||||
override fun parseName(input: String): String {
|
||||
return input.split("<").first()
|
||||
}
|
||||
}
|
||||
|
||||
object JavacStandardOptions : ParsingType {
|
||||
override val optionDescriptionPropertyKey: Map<String, @PropertyKey(resourceBundle = "messages.VMOptionsBundle") String> = mapOf(
|
||||
Pair("--add-modules", "vm.option.add.modules.description"),
|
||||
Pair("--boot-class-path", "vm.option.boot.classpath.description"),
|
||||
Pair("--class-path", "vm.option.classpath.description"),
|
||||
Pair("-deprecation", "vm.option.deprecation.description"),
|
||||
Pair("--enable-preview", "vm.option.enable.preview.description"),
|
||||
Pair("--help,", "vm.option.help.description"),
|
||||
Pair("--help-extra,", "vm.option.help.extra.description"),
|
||||
Pair("--limit-modules", "vm.option.limit.modules.javac.description"),
|
||||
Pair("--module", "vm.option.module.description"),
|
||||
Pair("--module-path", "vm.option.module.path.description"),
|
||||
Pair("--module-source-path", "vm.option.module.source.path.description"),
|
||||
Pair("--module-version", "vm.option.module.version.description"),
|
||||
Pair("--processor-module-path", "vm.option.processor.module.path.description"),
|
||||
Pair("--processor-path", "vm.option.processor.path.description"),
|
||||
Pair("--release", "vm.option.release.description"),
|
||||
Pair("--source", "vm.option.source.release.description"),
|
||||
Pair("--source-path", "vm.option.source.path.description"),
|
||||
Pair("--system", "vm.option.system.description"),
|
||||
Pair("--target", "vm.option.target.description"),
|
||||
Pair("--upgrade-module-path", "vm.option.upgrade.module.path.description"),
|
||||
Pair("--version,", "vm.option.version.description"),
|
||||
)
|
||||
|
||||
override val kind: VMOptionKind = VMOptionKind.Standard
|
||||
|
||||
override fun parseName(input: String): String {
|
||||
// parsing names "-opt:{arguments}"
|
||||
input.extractPrefix(":{", ":")?.let { return it }
|
||||
|
||||
// parsing names "-opt[=value]"
|
||||
input.extractPrefix("[", "")?.let { return it }
|
||||
|
||||
return input.extractPrefix("<", shouldHaveTwoParts = false) ?: throw IllegalArgumentException("Cannot parse option name: $input")
|
||||
}
|
||||
|
||||
private fun String.extractPrefix(delimiter: String, suffix: String = "", shouldHaveTwoParts: Boolean = true): String? {
|
||||
val parts = split(delimiter)
|
||||
return if (parts.size == 2 || !shouldHaveTwoParts) parts.first().trimEnd(',') else null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,24 @@ interface VMOptionsService {
|
||||
fun getInstance(): VMOptionsService = service()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates all the options for the `java` command.
|
||||
*/
|
||||
fun getOrComputeOptionsForJdk(javaHome: String): CompletableFuture<JdkOptionsData>
|
||||
|
||||
/**
|
||||
* Calculates all the options for the `javac` command.
|
||||
*/
|
||||
fun getOrComputeOptionsForJavac(javaHome: String): CompletableFuture<JdkOptionsData> =
|
||||
CompletableFuture.completedFuture(JdkOptionsData(emptyList()))
|
||||
|
||||
/**
|
||||
* @return options that appears in all `java` commands starting from java 8 except informational (help, version)
|
||||
*/
|
||||
fun getStandardOptions(): JdkOptionsData = JdkOptionsData(emptyList())
|
||||
|
||||
/**
|
||||
* @return options that appears in all `javac` commands starting from java 8 except informational (help, version)
|
||||
*/
|
||||
fun getStandardJavacOptions(): JdkOptionsData = JdkOptionsData(emptyList())
|
||||
}
|
||||
@@ -1,98 +1,24 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.vmOptions
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessRunner
|
||||
import com.intellij.execution.process.OSProcessHandler
|
||||
import com.intellij.execution.process.ProcessNotCreatedException
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.containers.CollectionFactory
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
class VMOptionsServiceImpl : VMOptionsService {
|
||||
companion object {
|
||||
private val ourData: ConcurrentMap<String, CompletableFuture<JdkOptionsData>> = CollectionFactory.createConcurrentSoftValueMap()
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("getStandardOptionList")
|
||||
val STANDARD_OPTION_LIST : List<VMOption> = listOf(
|
||||
opt("ea", VMOptionsBundle.message("vm.option.enable.assertions.description")),
|
||||
opt("enableassertions", VMOptionsBundle.message("vm.option.enable.assertions.description")),
|
||||
opt("da", VMOptionsBundle.message("vm.option.disable.assertions.description")),
|
||||
opt("disableassertions", VMOptionsBundle.message("vm.option.disable.assertions.description")),
|
||||
opt("esa", VMOptionsBundle.message("vm.option.enable.system.assertions.description")),
|
||||
opt("enablesystemassertions", VMOptionsBundle.message("vm.option.enable.system.assertions.description")),
|
||||
opt("dsa", VMOptionsBundle.message("vm.option.disable.system.assertions.description")),
|
||||
opt("disablesystemassertions", VMOptionsBundle.message("vm.option.disable.system.assertions.description")),
|
||||
opt("agentpath:",VMOptionsBundle.message("vm.option.agentpath.description")),
|
||||
opt("agentlib:", VMOptionsBundle.message("vm.option.agentlib.description")),
|
||||
opt("javaagent:", VMOptionsBundle.message("vm.option.javaagent.description")),
|
||||
opt("D", VMOptionsBundle.message("vm.option.system.property.description")),
|
||||
opt("XX:", VMOptionsBundle.message("vm.option.advanced.option.description")),
|
||||
)
|
||||
|
||||
private fun opt(name: String, doc: String): VMOption {
|
||||
return VMOption(name, null, null, VMOptionKind.Standard, doc, VMOptionVariant.DASH, null)
|
||||
}
|
||||
val STANDARD_OPTION_LIST : List<VMOption> = CommandOptionsExtractor.Java.STANDARD_OPTION_LIST
|
||||
}
|
||||
|
||||
override fun getOrComputeOptionsForJdk(javaHome: String): CompletableFuture<JdkOptionsData> {
|
||||
val future = ourData.computeIfAbsent(javaHome) { CompletableFuture.supplyAsync { computeOptionsData(it) } }
|
||||
if (future.isDone) {
|
||||
// sometimes the timeout may appear and in order not to block the possibility to get the completion afterwards, it is better to retry
|
||||
val data = future.get()
|
||||
if (data == null) {
|
||||
ourData.remove(javaHome)
|
||||
}
|
||||
}
|
||||
return future
|
||||
return CommandOptionsExtractor.Java.getOrComputeOptions(javaHome)
|
||||
}
|
||||
|
||||
override fun getStandardOptions(): JdkOptionsData = JdkOptionsData(STANDARD_OPTION_LIST)
|
||||
|
||||
// when null is returned, it was a timeout
|
||||
private fun computeOptionsData(javaHome: String): JdkOptionsData {
|
||||
return JdkOptionsData(getOptionsForJdk(javaHome))
|
||||
override fun getOrComputeOptionsForJavac(javaHome: String): CompletableFuture<JdkOptionsData> {
|
||||
return CommandOptionsExtractor.Javac.getOrComputeOptions(javaHome)
|
||||
}
|
||||
|
||||
private fun getOptionsForJdk(javaHome: String): List<VMOption> {
|
||||
val vmPath = getVmPath(javaHome)
|
||||
val generalCommandLine = GeneralCommandLine(vmPath)
|
||||
generalCommandLine.addParameters("-XX:+PrintFlagsFinal", "-XX:+UnlockDiagnosticVMOptions", "-XX:+UnlockExperimentalVMOptions", "-X")
|
||||
val handler = try {
|
||||
OSProcessHandler(generalCommandLine)
|
||||
}
|
||||
catch (e: ProcessNotCreatedException) {
|
||||
return STANDARD_OPTION_LIST
|
||||
}
|
||||
val runner = CapturingProcessRunner(handler)
|
||||
val output = runner.runProcess(1000)
|
||||
if (output.isTimeout) {
|
||||
return STANDARD_OPTION_LIST
|
||||
}
|
||||
val xxOptions = VMOptionsParser.parseXXOptions(output.stdout)
|
||||
val xOptions = VMOptionsParser.parseXOptions(output.stderr)
|
||||
if (xOptions != null) {
|
||||
return xOptions + xxOptions + STANDARD_OPTION_LIST
|
||||
}
|
||||
return xxOptions + STANDARD_OPTION_LIST
|
||||
}
|
||||
override fun getStandardOptions(): JdkOptionsData = JdkOptionsData(CommandOptionsExtractor.Java.STANDARD_OPTION_LIST)
|
||||
|
||||
private fun getVmPath(javaHome: String): String {
|
||||
val vmExeName = if (SystemInfo.isWindows) "java.exe" else "java" // do not use JavaW.exe because of issues with encoding
|
||||
return Path.of(getConvertedPath(javaHome), "bin", vmExeName).toString()
|
||||
}
|
||||
|
||||
private fun getConvertedPath(javaHome: String): String {
|
||||
// it is copied from com.intellij.openapi.projectRoots.impl.JavaSdkImpl.getConvertedHomePath
|
||||
var systemDependentName = FileUtil.toSystemDependentName(javaHome)
|
||||
if (javaHome.endsWith(File.separator)) {
|
||||
systemDependentName += File.separator
|
||||
}
|
||||
return systemDependentName
|
||||
}
|
||||
override fun getStandardJavacOptions(): JdkOptionsData = JdkOptionsData(CommandOptionsExtractor.Javac.STANDARD_OPTIONS_LIST)
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
</dependencies>
|
||||
<extensions defaultExtensionNs="org.jetbrains.plugins.terminal">
|
||||
<commandSpecsProvider implementation="com.intellij.java.terminal.JavaShellCommandSpecsProvider"/>
|
||||
<commandSpecsProvider implementation="com.intellij.java.terminal.JavacShellCommandSpecsProvider"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij.openapi.projectRoots">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# "java" command
|
||||
java.command.terminal.description=Runs java.
|
||||
java.command.terminal.help.option.description=Prints the help message to the {0} stream.
|
||||
java.command.terminal.jar.option.description=Launches the main class in a JAR file.
|
||||
@@ -9,4 +10,11 @@ java.command.terminal.enable.preview.option.description=Allows classes to depend
|
||||
java.command.terminal.verbose.option.description=Enables verbose output for the given subsystem.
|
||||
external.java.configuration.run.command=Run ''{0}''\u2026
|
||||
error.stream.name=error
|
||||
output.stream.name=output
|
||||
output.stream.name=output
|
||||
|
||||
# "javac" command
|
||||
java.c.command.terminal.description=Reads Java declarations and compile them into class files.
|
||||
java.c.command.terminal.classpath.option.description=Specifies where to find user class files, and (optionally) annotation processors and source files.
|
||||
java.c.command.terminal.annotation.processing.option.description=Specifies options to pass to annotation processors.
|
||||
java.c.command.terminal.extra.options.command.description=Prints the help for extra options.
|
||||
|
||||
|
||||
@@ -15,10 +15,9 @@ class JavaShellCommandContext private constructor(private val propertyMap: Map<S
|
||||
|
||||
companion object {
|
||||
private const val PROPERTY_SEPARATOR = " = "
|
||||
const val JAVA_SHOW_SETTINGS_PROPERTIES_VERSION_COMMAND: String = "java -XshowSettings:properties -version"
|
||||
|
||||
suspend fun create(context: ShellRuntimeContext): JavaShellCommandContext? {
|
||||
val result = context.runShellCommand(JAVA_SHOW_SETTINGS_PROPERTIES_VERSION_COMMAND)
|
||||
val result = context.createProcessBuilder("java").args("-XshowSettings:properties", "-version").execute()
|
||||
if (result.exitCode != 0) return null
|
||||
val propertyMap = result.output.split('\n').dropLastWhile { it.isBlank() }.asSequence().map { it.trim() }
|
||||
.filter { it.contains(PROPERTY_SEPARATOR) }.mapNotNull {
|
||||
|
||||
+12
-38
@@ -1,9 +1,11 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal
|
||||
|
||||
import com.intellij.execution.vmOptions.VMOptionKind
|
||||
import com.intellij.execution.vmOptions.VMOptionVariant
|
||||
import com.intellij.execution.vmOptions.VMOptionsService
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.CLASSPATH_ARGUMENT_NAME
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.UIOptionInfo
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.addClassPathOption
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.addOptionsFromData
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.terminal.completion.spec.ShellCommandParserOptions
|
||||
import com.intellij.terminal.completion.spec.ShellCommandSpec
|
||||
@@ -38,6 +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"))
|
||||
@@ -50,22 +53,19 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider {
|
||||
}
|
||||
description(JavaTerminalBundle.message("java.command.terminal.jar.option.description"))
|
||||
}
|
||||
|
||||
option("-version") {
|
||||
exclusiveOn(listOf("--version"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.version.option.description", errorStreamName))
|
||||
}
|
||||
option("-classpath", "-cp") {
|
||||
exclusiveOn(listOf("--class-path"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.classpath.option.description"))
|
||||
argument {
|
||||
suggestions(JavaShellCommandUtils.classpathSuggestionsGenerator())
|
||||
displayName(CLASSPATH_ARGUMENT_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
option("-showversion") {
|
||||
exclusiveOn(listOf("--show-version"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.show.version.option.description", errorStreamName))
|
||||
}
|
||||
|
||||
addClassPathOption()
|
||||
|
||||
argument {
|
||||
displayName(MAIN_CLASS_ARGUMENT_NAME)
|
||||
suggestions(ShellDataGenerators.fileSuggestionsGenerator())
|
||||
@@ -78,29 +78,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider {
|
||||
if (path == null) return@withContext null
|
||||
optionsService.getOrComputeOptionsForJdk(path).get()
|
||||
} ?: optionsService.getStandardOptions()
|
||||
jdkOptionsData.options
|
||||
.filter { (it.kind == VMOptionKind.Standard || it.kind == VMOptionKind.Product) && (it.variant != VMOptionVariant.XX)}
|
||||
.toList()
|
||||
.forEach {
|
||||
val optionName = it.optionName
|
||||
val presentableName = "${it.variant.prefix()}$optionName"
|
||||
option(presentableName) {
|
||||
val optionDescription = it.doc
|
||||
if (optionDescription == null) return@option
|
||||
description(optionDescription)
|
||||
|
||||
val info = OPTION_UI_INFO_MAP[presentableName] ?: DEFAULT_UI_OPTION_INSTANCE
|
||||
repeatTimes(info.repeatTimes)
|
||||
info.separator?.let { separator(it) }
|
||||
val argumentName = info.argumentName
|
||||
if (argumentName != null) {
|
||||
argument {
|
||||
if (info.isArgumentOptional) optional()
|
||||
displayName(argumentName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addOptionsFromData(jdkOptionsData, OPTION_UI_INFO_MAP)
|
||||
}
|
||||
|
||||
private fun ShellChildOptionsContext.addOptionsFromJava11() {
|
||||
@@ -166,7 +144,6 @@ private const val MAIN_CLASS_ARGUMENT_NAME: @NlsSafe String = "mainclass"
|
||||
private const val JAR_FILE_ARGUMENT_NAME: @NlsSafe String = "jar file"
|
||||
private const val CLASS_GC_GNI_ARGUMENT_NAME: @NlsSafe String = "class|gc|gni"
|
||||
private const val CLASS_GC_GNI_MODULE_ARGUMENT_NAME: @NlsSafe String = "$CLASS_GC_GNI_ARGUMENT_NAME|<module>"
|
||||
private val CLASSPATH_ARGUMENT_NAME: @NlsSafe String = "filepath[${JavaShellCommandUtils.getClassPathSeparator()}filepath]"
|
||||
|
||||
private val OPTION_UI_INFO_MAP = mapOf(
|
||||
"-Xms" to UIOptionInfo(separator = "", argumentName = SIZE_ARGUMENT_NAME),
|
||||
@@ -195,7 +172,4 @@ private val OPTION_UI_INFO_MAP = mapOf(
|
||||
"-D" to UIOptionInfo(separator = "", repeatTimes = 0, argumentName = "<name>=<value>"),
|
||||
"-XX:" to UIOptionInfo(repeatTimes = 0),
|
||||
"--source" to UIOptionInfo(argumentName = "version")
|
||||
)
|
||||
|
||||
private data class UIOptionInfo(val separator: String? = null, @NlsSafe val argumentName: String? = null, val repeatTimes: Int = 1, val isArgumentOptional: Boolean = false)
|
||||
private val DEFAULT_UI_OPTION_INSTANCE = UIOptionInfo()
|
||||
)
|
||||
@@ -1,16 +1,65 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal
|
||||
|
||||
import com.intellij.execution.vmOptions.JdkOptionsData
|
||||
import com.intellij.execution.vmOptions.VMOptionKind
|
||||
import com.intellij.execution.vmOptions.VMOptionVariant
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.terminal.completion.spec.ShellCompletionSuggestion
|
||||
import com.intellij.terminal.completion.spec.ShellRuntimeContext
|
||||
import com.intellij.terminal.completion.spec.ShellRuntimeDataGenerator
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellRuntimeDataGenerator
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.dsl.ShellChildOptionsContext
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.dsl.ShellCommandContext
|
||||
|
||||
object JavaShellCommandUtils {
|
||||
internal val DEFAULT_UI_OPTION_INSTANCE = UIOptionInfo()
|
||||
private const val SEPARATOR_NOT_FOUND_INDEX = -1
|
||||
|
||||
internal fun ShellChildOptionsContext.addOptionsFromData(jdkOptionsData: JdkOptionsData, uiMap: Map<String, UIOptionInfo>) {
|
||||
jdkOptionsData.options
|
||||
.filter { (it.kind == VMOptionKind.Standard || it.kind == VMOptionKind.Product) && (it.variant != VMOptionVariant.XX)}
|
||||
.toList()
|
||||
.forEach {
|
||||
val optionName = it.optionName
|
||||
val presentableName = "${it.variant.prefix()}$optionName"
|
||||
option(presentableName) {
|
||||
val optionDescription = it.doc
|
||||
if (optionDescription != null) {
|
||||
description(optionDescription)
|
||||
}
|
||||
|
||||
val info = uiMap[presentableName] ?: DEFAULT_UI_OPTION_INSTANCE
|
||||
repeatTimes(info.repeatTimes)
|
||||
info.separator?.let { separator(it) }
|
||||
val argumentName = info.argumentName
|
||||
if (argumentName != null) {
|
||||
argument {
|
||||
if (info.isArgumentOptional) optional()
|
||||
displayName(argumentName)
|
||||
|
||||
if (info.suggestionsGenerator != null) suggestions(info.suggestionsGenerator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ShellCommandContext.addClassPathOption() {
|
||||
option("-classpath", "-cp") {
|
||||
exclusiveOn(listOf("--class-path"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.classpath.option.description"))
|
||||
argument {
|
||||
suggestions(classpathSuggestionsGenerator())
|
||||
displayName(CLASSPATH_ARGUMENT_NAME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val CLASSPATH_ARGUMENT_NAME: @NlsSafe String = "filepath[${getClassPathSeparator()}filepath]"
|
||||
|
||||
fun getClassPathSeparator(): String = when {
|
||||
SystemInfo.isWindows -> ";"
|
||||
else -> ":"
|
||||
@@ -42,5 +91,13 @@ object JavaShellCommandUtils {
|
||||
}
|
||||
}
|
||||
|
||||
internal data class UIOptionInfo(
|
||||
val separator: String? = null,
|
||||
@NlsSafe val argumentName: String? = null,
|
||||
val repeatTimes: Int = 1,
|
||||
val isArgumentOptional: Boolean = false,
|
||||
val suggestionsGenerator: ShellRuntimeDataGenerator<List<ShellCompletionSuggestion>>? = null,
|
||||
)
|
||||
|
||||
private data class PathInfo(val typedPrefix: String, val replacementIndexDelta: Int = 0)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal
|
||||
|
||||
import com.intellij.execution.vmOptions.VMOptionsService
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.UIOptionInfo
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.addClassPathOption
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.addOptionsFromData
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils.classpathSuggestionsGenerator
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.terminal.completion.spec.ShellCommandParserOptions
|
||||
import com.intellij.terminal.completion.spec.ShellCommandSpec
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.*
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.dsl.ShellChildOptionsContext
|
||||
|
||||
internal class JavacShellCommandSpecsProvider : ShellCommandSpecsProvider {
|
||||
override fun getCommandSpecs(): List<ShellCommandSpecInfo> {
|
||||
return listOf(ShellCommandSpecInfo.create(getSpecs(), ShellCommandSpecConflictStrategy.REPLACE))
|
||||
}
|
||||
|
||||
private fun getSpecs(): ShellCommandSpec = ShellCommandSpec("javac") {
|
||||
parserOptions(
|
||||
ShellCommandParserOptions.builder()
|
||||
.flagsArePosixNonCompliant(true)
|
||||
.optionsMustPrecedeArguments(true)
|
||||
.build()
|
||||
)
|
||||
|
||||
description(JavaTerminalBundle.message("java.c.command.terminal.description"))
|
||||
|
||||
dynamicOptions { terminalContext ->
|
||||
val javaContext = JavaShellCommandContext.create(terminalContext)
|
||||
val path = javaContext?.getJrePath()
|
||||
addJavacOptions(path)
|
||||
}
|
||||
|
||||
|
||||
val streamName: @Nls String = JavaTerminalBundle.message("output.stream.name")
|
||||
|
||||
option("-?", "-help") {
|
||||
exclusiveOn(listOf("--help"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.help.option.description", streamName))
|
||||
}
|
||||
|
||||
option("-X") {
|
||||
exclusiveOn(listOf("--help-extra"))
|
||||
description(JavaTerminalBundle.message("java.c.command.terminal.extra.options.command.description"))
|
||||
}
|
||||
|
||||
option("-version") {
|
||||
exclusiveOn(listOf("--version"))
|
||||
description(JavaTerminalBundle.message("java.command.terminal.version.option.description", streamName))
|
||||
}
|
||||
|
||||
addClassPathOption()
|
||||
|
||||
argument {
|
||||
displayName(SOURCE_FILE_ARGUMENT_NAME)
|
||||
suggestions(ShellDataGenerators.fileSuggestionsGenerator())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ShellChildOptionsContext.addJavacOptions(path: String?) {
|
||||
val optionsService = VMOptionsService.getInstance()
|
||||
val jdkOptionsData = withContext(Dispatchers.IO) {
|
||||
|
||||
if (path == null) return@withContext null
|
||||
optionsService.getOrComputeOptionsForJavac(path).get()
|
||||
} ?: optionsService.getStandardJavacOptions()
|
||||
|
||||
addOptionsFromData(jdkOptionsData, UI_OPTION_INFO_MAP)
|
||||
}
|
||||
}
|
||||
|
||||
private const val SOURCE_FILE_ARGUMENT_NAME: @NlsSafe String = "[sourcefiles-or-classnames]"
|
||||
private val UI_OPTION_INFO_MAP = mapOf(
|
||||
"-A" to UIOptionInfo(separator = "", argumentName = "key[=value]"),
|
||||
"--add-modules" to UIOptionInfo(argumentName = "module[,module]*"),
|
||||
"--boot-class-path" to UIOptionInfo(argumentName = "path", suggestionsGenerator = classpathSuggestionsGenerator()),
|
||||
"--class-path" to UIOptionInfo(argumentName = "path", suggestionsGenerator = classpathSuggestionsGenerator()),
|
||||
"-d" to UIOptionInfo(argumentName = "directory", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator(true)),
|
||||
"-encoding" to UIOptionInfo(argumentName = "encoding"),
|
||||
"-endorseddirs" to UIOptionInfo(argumentName = "dirs"),
|
||||
"-extdirs" to UIOptionInfo(argumentName = "directories"),
|
||||
"-g:" to UIOptionInfo(separator = "", argumentName = "none|lines|vars"),
|
||||
"-h" to UIOptionInfo(argumentName = "directory", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator(true)),
|
||||
"-implicit:" to UIOptionInfo(separator = "", argumentName = "none|class", isArgumentOptional = true),
|
||||
"-J" to UIOptionInfo(separator = "", argumentName = "option"),
|
||||
"--limit-modules" to UIOptionInfo(argumentName = "module[,module]*"),
|
||||
"--module" to UIOptionInfo(argumentName = "module-name"),
|
||||
"--module-path" to UIOptionInfo(argumentName = "path"),
|
||||
"--module-source-path" to UIOptionInfo(argumentName = "path"),
|
||||
"--module-version" to UIOptionInfo(argumentName = "version"),
|
||||
"-proc:" to UIOptionInfo(separator = "", argumentName = "none|only|full", isArgumentOptional = true),
|
||||
"-processor" to UIOptionInfo(argumentName = "class[,class]*"),
|
||||
"--processor-module-path" to UIOptionInfo(argumentName = "path", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator()),
|
||||
"--processor-path" to UIOptionInfo(argumentName = "path" , suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator()),
|
||||
"-profile" to UIOptionInfo(argumentName = "profile"),
|
||||
"--release" to UIOptionInfo(argumentName = "release"),
|
||||
"-s" to UIOptionInfo(argumentName = "directory", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator(true)),
|
||||
"--source" to UIOptionInfo(argumentName = "release"),
|
||||
"--source-path" to UIOptionInfo(argumentName = "path", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator()),
|
||||
"--system" to UIOptionInfo(argumentName = "jdk"),
|
||||
"--target" to UIOptionInfo(argumentName = "release"),
|
||||
"--upgrade-module-path" to UIOptionInfo(argumentName = "path", suggestionsGenerator = ShellDataGenerators.fileSuggestionsGenerator()),
|
||||
)
|
||||
+10
-67
@@ -1,28 +1,20 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal.completion
|
||||
|
||||
import com.intellij.execution.vmOptions.*
|
||||
import com.intellij.java.terminal.JavaShellCommandContext
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.terminal.completion.spec.ShellCommandResult
|
||||
import com.intellij.terminal.completion.spec.ShellCompletionSuggestion
|
||||
import com.intellij.terminal.completion.spec.ShellFileInfo
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.testFramework.replaceService
|
||||
import junit.framework.TestCase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.plugins.terminal.TerminalEngine
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellFileSystemSupport
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessExecutor
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessOptions
|
||||
import org.jetbrains.plugins.terminal.testFramework.completion.ShellCompletionTestFixture
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class JavaShellCommandSpecsProviderTest(private val engine: TerminalEngine) : BasePlatformTestCase() {
|
||||
internal class JavaShellCommandSpecsProviderTest(engine: TerminalEngine) : JdkCommandsShellSpecsProviderTestBase(engine) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@@ -31,7 +23,13 @@ class JavaShellCommandSpecsProviderTest(private val engine: TerminalEngine) : Ba
|
||||
|
||||
@Test
|
||||
fun `default options are present`() = runBlocking {
|
||||
val fixture = ShellCompletionTestFixture.builder(project).mockShellCommandResults { _ ->
|
||||
val fixture = ShellCompletionTestFixture.builder(project)
|
||||
.mockProcessesExecutor(object : ShellDataGeneratorProcessExecutor {
|
||||
override suspend fun executeProcess(options: ShellDataGeneratorProcessOptions): ShellCommandResult {
|
||||
return ShellCommandResult.create("", exitCode = 1)
|
||||
}
|
||||
})
|
||||
.mockShellCommandResults { _ ->
|
||||
return@mockShellCommandResults ShellCommandResult.create("", exitCode = 1)
|
||||
}.build()
|
||||
assertSameElements(fixture.getCompletionNames(), listOf("-ea", "-enableassertions", "-da", "-disableassertions", "-esa", "-enablesystemassertions",
|
||||
@@ -101,7 +99,6 @@ class JavaShellCommandSpecsProviderTest(private val engine: TerminalEngine) : Ba
|
||||
assertTrue(completion.all { it.prefixReplacementIndex == argument.length - 1 })
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `classpath suggestion generator simple`() = runBlocking {
|
||||
val fixture = createFixture()
|
||||
@@ -129,58 +126,4 @@ class JavaShellCommandSpecsProviderTest(private val engine: TerminalEngine) : Ba
|
||||
assertSameElements(completion.map { it.name }, listOf("file1.jar", "file2.jar", "dir1/"))
|
||||
assertTrue(completion.all { it.prefixReplacementIndex == argument.length})
|
||||
}
|
||||
|
||||
private fun createFixture(javaVersion: Int = 11): ShellCompletionTestFixture {
|
||||
ApplicationManager.getApplication().replaceService(VMOptionsService::class.java, MockVMOptionsService(), testRootDisposable)
|
||||
val fixture = ShellCompletionTestFixture.builder(project)
|
||||
.setIsReworkedTerminal(engine == TerminalEngine.REWORKED)
|
||||
.mockShellCommandResults { command ->
|
||||
if (command == JavaShellCommandContext.JAVA_SHOW_SETTINGS_PROPERTIES_VERSION_COMMAND) {
|
||||
return@mockShellCommandResults ShellCommandResult.create("java.home = /jre/home\njava.version = ${javaVersion}", exitCode = 0)
|
||||
}
|
||||
|
||||
if (command.startsWith("__jetbrains_intellij_get_directory_files")) {
|
||||
return@mockShellCommandResults ShellCommandResult.create("file1.jar\nfile2.jar\ndir1/", exitCode = 0)
|
||||
}
|
||||
|
||||
return@mockShellCommandResults ShellCommandResult.create("", exitCode = 1)
|
||||
}
|
||||
.mockFileSystemSupport(object : ShellFileSystemSupport {
|
||||
override suspend fun listDirectoryFiles(path: String): List<ShellFileInfo> {
|
||||
return listOf(
|
||||
ShellFileInfo.create("file1.jar", ShellFileInfo.Type.FILE),
|
||||
ShellFileInfo.create("file2.jar", ShellFileInfo.Type.FILE),
|
||||
ShellFileInfo.create("dir1", ShellFileInfo.Type.DIRECTORY),
|
||||
)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
return fixture
|
||||
}
|
||||
|
||||
private suspend fun ShellCompletionTestFixture.getCompletionNames(command: String = "java "): List<String> {
|
||||
val actual: List<ShellCompletionSuggestion> = getCompletions(command)
|
||||
return actual.map { it.name }
|
||||
}
|
||||
|
||||
private class MockVMOptionsService : VMOptionsService {
|
||||
override fun getOrComputeOptionsForJdk(javaHome: String): CompletableFuture<JdkOptionsData> {
|
||||
TestCase.assertEquals("/jre/home", javaHome)
|
||||
return CompletableFuture.completedFuture(
|
||||
JdkOptionsData(
|
||||
listOf(
|
||||
VMOption("settings", null, null, VMOptionKind.Product, null, VMOptionVariant.X),
|
||||
VMOption("lint", null, null, VMOptionKind.Standard, null, VMOptionVariant.X),
|
||||
VMOption("experiment", null, null, VMOptionKind.Experimental, null, VMOptionVariant.X),
|
||||
VMOption("diagnose", null, null, VMOptionKind.Diagnostic, null, VMOptionVariant.X),
|
||||
VMOption("advanced", null, null, VMOptionKind.Product, null, VMOptionVariant.XX),
|
||||
VMOption("add-opens", null, null, VMOptionKind.Product, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-exports", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-experiment-exports", null, null, VMOptionKind.Experimental, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-diagnostic-exports", null, null, VMOptionKind.Experimental, null, VMOptionVariant.DASH_DASH),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal.completion
|
||||
|
||||
import com.intellij.java.terminal.JavaShellCommandUtils
|
||||
import com.intellij.terminal.completion.spec.ShellCommandResult
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.plugins.terminal.TerminalEngine
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessExecutor
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessOptions
|
||||
import org.jetbrains.plugins.terminal.testFramework.completion.ShellCompletionTestFixture
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
internal class JavacShellCommandSpecsProviderTest(engine: TerminalEngine) : JdkCommandsShellSpecsProviderTestBase(engine) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun engine(): List<TerminalEngine> = listOf(TerminalEngine.REWORKED, TerminalEngine.NEW_TERMINAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default options are present`() = runBlocking {
|
||||
val fixture = ShellCompletionTestFixture.builder(project)
|
||||
.mockProcessesExecutor(object : ShellDataGeneratorProcessExecutor {
|
||||
override suspend fun executeProcess(options: ShellDataGeneratorProcessOptions): ShellCommandResult {
|
||||
return ShellCommandResult.create("", exitCode = 1)
|
||||
}
|
||||
})
|
||||
.mockShellCommandResults { _ ->
|
||||
return@mockShellCommandResults ShellCommandResult.create("", exitCode = 1)
|
||||
}.build()
|
||||
assertSameElements(fixture.getCompletionNames("javac "), listOf(
|
||||
"-A", "-g", "-g:", "-g:none", "-h", "-J", "-d", "-nowarn", "-parameters", "-processor",
|
||||
"-profile", "-s", "-verbose", "-Werror", "-proc:", "-implicit:",
|
||||
"-encoding", "-endorseddirs", "-extdirs", "-cp", "-classpath", "-?", "-help", "-X", "-version"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `double dash options are present`() = runBlocking {
|
||||
val fixture = createFixture()
|
||||
UsefulTestCase.assertContainsElements(fixture.getCompletionNames("javac "), listOf("--enable-preview", "--release", "--source", "--target"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `classpath suggestion generator`() = runBlocking {
|
||||
val separator = JavaShellCommandUtils.getClassPathSeparator()
|
||||
val fixture = createFixture()
|
||||
val argument = "file1.jar$separator"
|
||||
val completion = fixture.getCompletions("javac -cp $argument")
|
||||
assertSameElements(completion.map { it.name }, listOf("file1.jar", "file2.jar", "dir1/"))
|
||||
assertTrue(completion.all { it.prefixReplacementIndex == argument.length })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `boot classpath suggestion generator`() = runBlocking {
|
||||
val separator = JavaShellCommandUtils.getClassPathSeparator()
|
||||
val fixture = createFixture()
|
||||
val argument = "file1.jar$separator"
|
||||
val completion = fixture.getCompletions("javac --boot-class-path $argument")
|
||||
assertSameElements(completion.map { it.name }, listOf("file1.jar", "file2.jar", "dir1/"))
|
||||
assertTrue(completion.all { it.prefixReplacementIndex == argument.length })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.terminal.completion
|
||||
|
||||
import com.intellij.execution.vmOptions.*
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.terminal.completion.spec.ShellCommandResult
|
||||
import com.intellij.terminal.completion.spec.ShellCompletionSuggestion
|
||||
import com.intellij.terminal.completion.spec.ShellFileInfo
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.testFramework.replaceService
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.plugins.terminal.TerminalEngine
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessExecutor
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGeneratorProcessOptions
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellFileSystemSupport
|
||||
import org.jetbrains.plugins.terminal.testFramework.completion.ShellCompletionTestFixture
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
internal abstract class JdkCommandsShellSpecsProviderTestBase(private val engine: TerminalEngine) : BasePlatformTestCase() {
|
||||
protected fun createFixture(javaVersion: Int = 11): ShellCompletionTestFixture {
|
||||
ApplicationManager.getApplication().replaceService(VMOptionsService::class.java, MockVMOptionsService(), testRootDisposable)
|
||||
val fixture = ShellCompletionTestFixture.builder(project)
|
||||
.setIsReworkedTerminal(engine == TerminalEngine.REWORKED)
|
||||
.mockProcessesExecutor(object : ShellDataGeneratorProcessExecutor {
|
||||
override suspend fun executeProcess(options: ShellDataGeneratorProcessOptions): ShellCommandResult {
|
||||
if (options.executable == "java" && options.args == listOf("-XshowSettings:properties", "-version")) {
|
||||
return ShellCommandResult.create("java.home = /jre/home\njava.version = $javaVersion", exitCode = 0)
|
||||
}
|
||||
else if (options.executable == "__jetbrains_intellij_get_directory_files") {
|
||||
return ShellCommandResult.create("file1.jar\nfile2.jar\ndir1/", exitCode = 0)
|
||||
}
|
||||
return ShellCommandResult.create("", exitCode = 1)
|
||||
}
|
||||
})
|
||||
.mockShellCommandResults { command ->
|
||||
if (command.startsWith("__jetbrains_intellij_get_directory_files")) {
|
||||
return@mockShellCommandResults ShellCommandResult.create("file1.jar\nfile2.jar\ndir1/", exitCode = 0)
|
||||
}
|
||||
return@mockShellCommandResults ShellCommandResult.create("", exitCode = 1)
|
||||
}
|
||||
.mockFileSystemSupport(object : ShellFileSystemSupport {
|
||||
override suspend fun listDirectoryFiles(path: String): List<ShellFileInfo> {
|
||||
return listOf(
|
||||
ShellFileInfo.create("file1.jar", ShellFileInfo.Type.FILE),
|
||||
ShellFileInfo.create("file2.jar", ShellFileInfo.Type.FILE),
|
||||
ShellFileInfo.create("dir1", ShellFileInfo.Type.DIRECTORY),
|
||||
)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
return fixture
|
||||
}
|
||||
|
||||
protected suspend fun ShellCompletionTestFixture.getCompletionNames(command: String = "java "): List<String> {
|
||||
val actual: List<ShellCompletionSuggestion> = getCompletions(command)
|
||||
return actual.map { it.name }
|
||||
}
|
||||
|
||||
|
||||
private class MockVMOptionsService : VMOptionsService {
|
||||
override fun getOrComputeOptionsForJdk(javaHome: String): CompletableFuture<JdkOptionsData> {
|
||||
TestCase.assertEquals("/jre/home", javaHome)
|
||||
return CompletableFuture.completedFuture(
|
||||
JdkOptionsData(
|
||||
listOf(
|
||||
VMOption("settings", null, null, VMOptionKind.Product, null, VMOptionVariant.X),
|
||||
VMOption("lint", null, null, VMOptionKind.Standard, null, VMOptionVariant.X),
|
||||
VMOption("experiment", null, null, VMOptionKind.Experimental, null, VMOptionVariant.X),
|
||||
VMOption("diagnose", null, null, VMOptionKind.Diagnostic, null, VMOptionVariant.X),
|
||||
VMOption("advanced", null, null, VMOptionKind.Product, null, VMOptionVariant.XX),
|
||||
VMOption("add-opens", null, null, VMOptionKind.Product, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-exports", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-experiment-exports", null, null, VMOptionKind.Experimental, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("add-diagnostic-exports", null, null, VMOptionKind.Experimental, null, VMOptionVariant.DASH_DASH),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getOrComputeOptionsForJavac(javaHome: String): CompletableFuture<JdkOptionsData> {
|
||||
TestCase.assertEquals("/jre/home", javaHome)
|
||||
return CompletableFuture.completedFuture(
|
||||
JdkOptionsData(
|
||||
listOf(
|
||||
VMOption("boot-class-path", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("enable-preview", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("release", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("source", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("target", null, null, VMOptionKind.Standard, null, VMOptionVariant.DASH_DASH),
|
||||
VMOption("experimental", null, null, VMOptionKind.Experimental, null, VMOptionVariant.DASH_DASH),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user