[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
@@ -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),
)
)
)
}
}
}