[Java] IDEA-385244 Support javac standard options completion in terminal

GitOrigin-RevId: b167cb6dbf75bb5431533356620b9e1d2061f938
This commit is contained in:
Georgii Ustinov
2026-01-29 17:59:14 +00:00
committed by intellij-monorepo-bot
parent f2a9c94d66
commit f8df55e707
14 changed files with 733 additions and 246 deletions
@@ -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 {
@@ -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()),
)
@@ -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),
)
)
)
}
}
}
@@ -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 })
}
}
@@ -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),
)
)
)
}
}
}