mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[terminal] IJPL-148594 Allow using both slash and backslash for file paths completion in PowerShell
This part fixes command spec based completion of file paths. GitOrigin-RevId: 67a170fce03b729cc34cc33bd8bff49e0241924c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
08b258a65d
commit
3849073765
+14
-9
@@ -1,10 +1,7 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.terminal.block.completion.spec
|
||||
|
||||
import com.intellij.terminal.completion.spec.ShellCommandSpec
|
||||
import com.intellij.terminal.completion.spec.ShellCompletionSuggestion
|
||||
import com.intellij.terminal.completion.spec.ShellRuntimeDataGenerator
|
||||
import com.intellij.terminal.completion.spec.ShellSuggestionType
|
||||
import com.intellij.terminal.completion.spec.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.plugins.terminal.TerminalBundle
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellCommandSpecImpl
|
||||
@@ -32,9 +29,9 @@ object ShellDataGenerators {
|
||||
val key = if (onlyDirectories) "directories" else "files"
|
||||
return ShellRuntimeDataGenerator(
|
||||
debugName = key,
|
||||
getCacheKey = { "$key:${getParentPath(it.typedPrefix)}" }
|
||||
getCacheKey = { "$key:${getParentPath(it.typedPrefix, it.shellName)}" }
|
||||
) { context ->
|
||||
val path = getParentPath(context.typedPrefix)
|
||||
val path = getParentPath(context.typedPrefix, context.shellName)
|
||||
val files: List<String> = context.getChildFiles(path, onlyDirectories)
|
||||
val prefixReplacementIndex = path.length + if (context.typedPrefix.startsWith('"')) 1 else 0
|
||||
val suggestions = files.flatMap {
|
||||
@@ -116,14 +113,22 @@ object ShellDataGenerators {
|
||||
* 1. `file.txt` -> `<empty>`
|
||||
* 2. `src/file.txt` -> `src/`
|
||||
* 3. `/usr/b` -> `/usr/`
|
||||
*
|
||||
* If [shellName] is PowerShell, then this function considers both slash `/` and backslash `\` as path separators.
|
||||
*/
|
||||
fun getParentPath(typedPrefix: String): String {
|
||||
fun getParentPath(typedPrefix: String, shellName: ShellName): String {
|
||||
val separator = File.separatorChar
|
||||
val pathSeparators = if (shellName.isPowerShell()) {
|
||||
val oppositeSeparator = if (separator == '/') '\\' else '/'
|
||||
charArrayOf(separator, oppositeSeparator)
|
||||
}
|
||||
else charArrayOf(separator)
|
||||
// Remove possible quotes before and after
|
||||
// TODO: quotes should not be handled there, typed prefix should already contain no quotes.
|
||||
val adjustedPrefix = typedPrefix.removePrefix("\"").removeSuffix("'")
|
||||
return if (adjustedPrefix.contains(separator)) {
|
||||
adjustedPrefix.substringBeforeLast(separator) + separator
|
||||
val lastSeparatorIndex = adjustedPrefix.lastIndexOfAny(pathSeparators)
|
||||
return if (lastSeparatorIndex != -1) {
|
||||
adjustedPrefix.substring(0, lastSeparatorIndex + 1)
|
||||
}
|
||||
else ""
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,9 +18,9 @@ internal fun cdCommandSpec() = ShellCommandSpec("cd") {
|
||||
|
||||
val generator = ShellRuntimeDataGenerator(
|
||||
debugName = "cd suggestions",
|
||||
getCacheKey = { "cd suggestions:${getParentPath(it.typedPrefix)}" }
|
||||
getCacheKey = { "cd suggestions:${getParentPath(it.typedPrefix, it.shellName)}" }
|
||||
) { context ->
|
||||
val path = getParentPath(context.typedPrefix)
|
||||
val path = getParentPath(context.typedPrefix, context.shellName)
|
||||
val directories = context.getChildFiles(path, onlyDirectories = true)
|
||||
val prefixReplacementIndex = path.length + if (context.typedPrefix.startsWith('"')) 1 else 0
|
||||
val suggestions = directories.flatMap {
|
||||
|
||||
+20
-6
@@ -11,12 +11,13 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.terminal.completion.ShellCommandSpecCompletion
|
||||
import com.intellij.terminal.completion.spec.ShellCompletionSuggestion
|
||||
import com.intellij.terminal.completion.spec.ShellSuggestionType
|
||||
import org.jetbrains.plugins.terminal.action.TerminalCommandCompletionAction
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators.availableCommandsGenerator
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators.fileSuggestionsGenerator
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellDataGeneratorsExecutorImpl
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellRuntimeContextProviderImpl
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellEnvBasedGenerators.aliasesGenerator
|
||||
import org.jetbrains.plugins.terminal.block.completion.spec.impl.ShellRuntimeContextProviderImpl
|
||||
import org.jetbrains.plugins.terminal.exp.BlockTerminalSession
|
||||
import org.jetbrains.plugins.terminal.exp.TerminalDataContextUtils.terminalPromptModel
|
||||
import org.jetbrains.plugins.terminal.exp.completion.TerminalCompletionUtil.findIconForSuggestion
|
||||
@@ -66,7 +67,7 @@ internal class TerminalCommandSpecCompletionContributor : CompletionContributor(
|
||||
val prefix = allTokens.last().substring(prefixReplacementIndex)
|
||||
val resultSet = result.withPrefixMatcher(PlainPrefixMatcher(prefix, true))
|
||||
|
||||
val elements = suggestions.map { it.toLookupElement() }
|
||||
val elements = suggestions.map { it.toLookupElement(session.shellIntegration.shellType) }
|
||||
resultSet.addAllElements(elements)
|
||||
|
||||
if (elements.isNotEmpty()) {
|
||||
@@ -131,7 +132,7 @@ internal class TerminalCommandSpecCompletionContributor : CompletionContributor(
|
||||
return (expandedTokens ?: completeTokens) + tokens.last() // add incomplete token to the end
|
||||
}
|
||||
|
||||
private fun ShellCompletionSuggestion.toLookupElement(): LookupElement {
|
||||
private fun ShellCompletionSuggestion.toLookupElement(shellType: ShellType): LookupElement {
|
||||
val actualIcon = icon ?: findIconForSuggestion(name, type)
|
||||
val realInsertValue = insertValue?.replace("{cursor}", "")
|
||||
val nextSuggestions = getNextSuggestionsString(this).takeIf { it.isNotEmpty() }
|
||||
@@ -149,14 +150,27 @@ internal class TerminalCommandSpecCompletionContributor : CompletionContributor(
|
||||
.withPresentableText(displayName ?: name)
|
||||
.withTailText(nextSuggestions, true)
|
||||
.withIcon(actualIcon)
|
||||
.withInsertHandler(MyInsertHandler(this, appendPathSeparator))
|
||||
.withInsertHandler(MyInsertHandler(this, appendPathSeparator, shellType))
|
||||
val adjustedPriority = priority.coerceIn(0, 100)
|
||||
return PrioritizedLookupElement.withPriority(element, adjustedPriority / 100.0)
|
||||
}
|
||||
|
||||
private class MyInsertHandler(private val suggestion: ShellCompletionSuggestion,
|
||||
private val appendPathSeparator: Boolean) : InsertHandler<LookupElement> {
|
||||
private class MyInsertHandler(
|
||||
private val suggestion: ShellCompletionSuggestion,
|
||||
private val appendPathSeparator: Boolean,
|
||||
private val shellType: ShellType
|
||||
) : InsertHandler<LookupElement> {
|
||||
override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||
// PowerShell consider both slash and backslash as valid path separators.
|
||||
// But after suggestion insertion, it is replacing wrong path separators with OS path separators.
|
||||
// Here we are emulating the same behavior.
|
||||
if (shellType == ShellType.POWERSHELL && (suggestion.type == ShellSuggestionType.FOLDER || suggestion.type == ShellSuggestionType.FILE)) {
|
||||
val pathStartOffset = context.startOffset - suggestion.prefixReplacementIndex
|
||||
val pathText = context.document.immutableCharSequence.substring(pathStartOffset, context.tailOffset)
|
||||
val wrongSeparator = if (File.separatorChar == '/') '\\' else '/'
|
||||
val adjustedPathText = pathText.replace(wrongSeparator, File.separatorChar)
|
||||
context.document.replaceString(pathStartOffset, context.tailOffset, adjustedPathText)
|
||||
}
|
||||
if (appendPathSeparator) {
|
||||
val tailOffset = context.tailOffset
|
||||
context.document.insertString(tailOffset, File.separator)
|
||||
|
||||
Reference in New Issue
Block a user