diff --git a/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt index 42d44ee407d5..158296bcba0e 100644 --- a/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt +++ b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandSpecsProvider.kt @@ -54,7 +54,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { description(JavaTerminalBundle.message("java.command.terminal.classpath.option.description")) argument { displayName(CLASSPATH_ARGUMENT_NAME) - suggestions(ShellDataGenerators.fileSuggestionsGenerator()) + suggestions(JavaShellCommandUtils.classpathSuggestionsGenerator()) } } option("-showversion") { @@ -116,7 +116,7 @@ class JavaShellCommandSpecsProvider : ShellCommandSpecsProvider { description(JavaTerminalBundle.message("java.command.terminal.classpath.option.description")) argument { displayName(CLASSPATH_ARGUMENT_NAME) - suggestions(ShellDataGenerators.fileSuggestionsGenerator()) + suggestions(JavaShellCommandUtils.classpathSuggestionsGenerator()) } } option("--help") { @@ -161,7 +161,7 @@ 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|" -private val CLASSPATH_ARGUMENT_NAME: @NlsSafe String = "filepath[${ShellCommandUtils.getClassPathSeparator()}filepath]" +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), diff --git a/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandUtils.kt b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandUtils.kt new file mode 100644 index 000000000000..0935f3ac97a8 --- /dev/null +++ b/java/java-terminal/src/com/intellij/java/terminal/JavaShellCommandUtils.kt @@ -0,0 +1,40 @@ +// 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.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 + +internal object JavaShellCommandUtils { + internal fun getClassPathSeparator() = when { + SystemInfo.isWindows -> ";" + else -> ":" + } + + internal fun classpathSuggestionsGenerator(): ShellRuntimeDataGenerator> { + val key = "classpath generator" + return ShellRuntimeDataGenerator( + debugName = key, + getCacheKey = { + val pathInfo = getPathInfo(it) + "$key:${pathInfo.lastSeparatorIndex}:${pathInfo.typedPrefix}" + } + ) { context -> + val pathInfo = getPathInfo(context) + ShellDataGenerators.getFileSuggestions(context, pathInfo.typedPrefix, false, if (pathInfo.lastSeparatorIndex == -1) 0 else pathInfo.lastSeparatorIndex + 1) + } + } + + private fun getPathInfo(context: ShellRuntimeContext): PathInfo { + val separator = getClassPathSeparator() + val currentPrefix = context.typedPrefix + val index = currentPrefix.lastIndexOf(separator) + val newTypedPrefix = if (index == -1) currentPrefix else currentPrefix.substring(index + 1) + return PathInfo(newTypedPrefix, index) + } + + private data class PathInfo(val typedPrefix: String, val lastSeparatorIndex: Int) +} \ No newline at end of file diff --git a/java/java-terminal/src/com/intellij/java/terminal/ShellCommandUtils.kt b/java/java-terminal/src/com/intellij/java/terminal/ShellCommandUtils.kt deleted file mode 100644 index 378455d20d8c..000000000000 --- a/java/java-terminal/src/com/intellij/java/terminal/ShellCommandUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -// 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.openapi.util.SystemInfo - -internal object ShellCommandUtils { - internal fun getClassPathSeparator() = when { - SystemInfo.isWindows -> ";" - else -> ":" - } -} \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt index 0140c8a1a2a4..79f5cc2783cd 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellDataGenerators.kt @@ -4,9 +4,10 @@ package org.jetbrains.plugins.terminal.block.completion.spec import com.intellij.terminal.completion.spec.* import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.TerminalBundle +import org.jetbrains.plugins.terminal.block.completion.ShellCommandSpecsManagerImpl +import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators.getFileSuggestions import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellCommandSpecImpl import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellEnvBasedGenerators -import org.jetbrains.plugins.terminal.block.completion.ShellCommandSpecsManagerImpl import java.io.File @ApiStatus.Experimental @@ -24,6 +25,7 @@ object ShellDataGenerators { * Note that the suggestions are not filtered by the file prefix extracted from the [typed prefix][com.intellij.terminal.completion.spec.ShellRuntimeContext.typedPrefix]. * All the files from the base directory extracted from the typed prefix are returned as [ShellCompletionSuggestion]'s. * All prefix matching logic is performed by the core completion logic. + * @see getFileSuggestions */ fun fileSuggestionsGenerator(onlyDirectories: Boolean = false): ShellRuntimeDataGenerator> { val key = if (onlyDirectories) "directories" else "files" @@ -31,39 +33,56 @@ object ShellDataGenerators { debugName = key, getCacheKey = { "$key:${getParentPath(it.typedPrefix, it.shellName)}" } ) { context -> - val path = getParentPath(context.typedPrefix, context.shellName) - val files: List = context.getChildFiles(path, onlyDirectories) - val prefixReplacementIndex = path.length + if (context.typedPrefix.startsWith('"')) 1 else 0 - val suggestions = files.flatMap { - val type = if (it.endsWith(File.separatorChar)) ShellSuggestionType.FOLDER else ShellSuggestionType.FILE - val suggestion = ShellCompletionSuggestion(name = it, type = type, prefixReplacementIndex = prefixReplacementIndex) - if (type == ShellSuggestionType.FILE) { - listOf(suggestion) - } - else { - // Directory suggestion has a trailing file separator, but suggestion without it is also valid. - // It is needed for the parser to consider it as a valid suggestion and not mark it as something unknown. - val hiddenSuggestion = ShellCompletionSuggestion( - name = it.removeSuffix(File.separator), - type = ShellSuggestionType.FOLDER, - prefixReplacementIndex = prefixReplacementIndex, - isHidden = true - ) - listOf(suggestion, hiddenSuggestion) - } - } - val adjustedPrefix = context.typedPrefix.removePrefix("\"").removeSuffix("'") - // If the base path is the same as the typed prefix, then add an empty suggestion. - // Because the current typed prefix is already a valid value of the file argument. - // It is needed for the parser to consider current typed prefix as a valid file suggestion. - if (path.isNotEmpty() && path == adjustedPrefix) { - val emptySuggestion = ShellCompletionSuggestion(name = "", prefixReplacementIndex = prefixReplacementIndex, isHidden = true) - suggestions + emptySuggestion - } - else suggestions + getFileSuggestions(context, context.typedPrefix, onlyDirectories) } } + /** + * Generates file suggestions for a given typed prefix in the specified shell context. + * + * @param context the runtime context of the shell containing information about the current environment + * @param typedPrefix the typed prefix for which suggestions are generated + * @param onlyDirectories if only directories should be suggested + * @param replacementIndexDelta + */ + suspend fun getFileSuggestions( + context: ShellRuntimeContext, + typedPrefix: String, + onlyDirectories: Boolean, + replacementIndexDelta: Int = 0, + ): List { + val path = getParentPath(typedPrefix, context.shellName) + val files: List = context.getChildFiles(path, onlyDirectories) + val prefixReplacementIndex = path.length + if (context.typedPrefix.startsWith('"')) 1 else 0 + replacementIndexDelta + val suggestions = files.flatMap { + val type = if (it.endsWith(File.separatorChar)) ShellSuggestionType.FOLDER else ShellSuggestionType.FILE + val suggestion = ShellCompletionSuggestion(name = it, type = type, prefixReplacementIndex = prefixReplacementIndex) + if (type == ShellSuggestionType.FILE) { + listOf(suggestion) + } + else { + // Directory suggestion has a trailing file separator, but suggestion without it is also valid. + // It is needed for the parser to consider it as a valid suggestion and not mark it as something unknown. + val hiddenSuggestion = ShellCompletionSuggestion( + name = it.removeSuffix(File.separator), + type = ShellSuggestionType.FOLDER, + prefixReplacementIndex = prefixReplacementIndex, + isHidden = true + ) + listOf(suggestion, hiddenSuggestion) + } + } + val adjustedPrefix = typedPrefix.removePrefix("\"").removeSuffix("'") + // If the base path is the same as the typed prefix, then add an empty suggestion. + // Because the current typed prefix is already a valid value of the file argument. + // It is needed for the parser to consider current typed prefix as a valid file suggestion. + return if (path.isNotEmpty() && path == adjustedPrefix) { + val emptySuggestion = ShellCompletionSuggestion(name = "", prefixReplacementIndex = prefixReplacementIndex, isHidden = true) + suggestions + emptySuggestion + } + else suggestions + } + /** * Provides the list of all available commands, functions, keywords and aliases available in the Shell. * Useful for the commands that accept the other shell command as an argument.