mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[command-completion] IDEA-380020 Command Completion shouldn't be proposed inside string literals if they are not injection points
GitOrigin-RevId: ccf69ade720cd70ec878e36c386aeb21fe90b41e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
73369fee2a
commit
fde7f496b4
@@ -27,16 +27,33 @@ class JavaCommandCompletionFactory implements CommandCompletionFactory, DumbAwar
|
||||
if (!(psiFile instanceof PsiJavaFile)) return false;
|
||||
//Doesn't work well. Disable for now
|
||||
if (psiFile instanceof PsiJShellFile) return false;
|
||||
PsiElement elementAt = psiFile.findElementAt(offset);
|
||||
if (elementAt == null) return true;
|
||||
if (!(elementAt.getParent() instanceof PsiParameterList)) return true;
|
||||
PsiElement prevLeaf = PsiTreeUtil.prevLeaf(elementAt, true);
|
||||
if (!(prevLeaf instanceof PsiJavaToken javaToken && javaToken.textMatches("."))) return true;
|
||||
PsiElement prevPrevLeaf = PsiTreeUtil.prevLeaf(prevLeaf, true);
|
||||
if (PsiTreeUtil.getParentOfType(prevPrevLeaf, PsiTypeElement.class) != null) return false;
|
||||
if (isInsideParameterList(psiFile, offset)) return false;
|
||||
if (isInsideStringLiteral(psiFile, offset)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isInsideStringLiteral(@NotNull PsiFile file, int offset) {
|
||||
PsiElement elementAt = file.findElementAt(offset);
|
||||
if (!(elementAt instanceof PsiJavaToken psiJavaToken &&
|
||||
(psiJavaToken.getTokenType() == JavaTokenType.STRING_LITERAL ||
|
||||
psiJavaToken.getTokenType() == JavaTokenType.TEXT_BLOCK_LITERAL
|
||||
))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return psiJavaToken.getTextRange().containsOffset(offset);
|
||||
}
|
||||
|
||||
private static boolean isInsideParameterList(@NotNull PsiFile psiFile, int offset) {
|
||||
PsiElement elementAt = psiFile.findElementAt(offset);
|
||||
if (elementAt == null) return false;
|
||||
if (!(elementAt.getParent() instanceof PsiParameterList)) return false;
|
||||
PsiElement prevLeaf = PsiTreeUtil.prevLeaf(elementAt, true);
|
||||
if (!(prevLeaf instanceof PsiJavaToken javaToken && javaToken.textMatches("."))) return false;
|
||||
PsiElement prevPrevLeaf = PsiTreeUtil.prevLeaf(prevLeaf, true);
|
||||
return PsiTreeUtil.getParentOfType(prevPrevLeaf, PsiTypeElement.class) != null;
|
||||
}
|
||||
|
||||
static class JavaIntentionCommandSkipper implements IntentionCommandSkipper {
|
||||
@Override
|
||||
public boolean skip(@NotNull CommonIntentionAction action, @NotNull PsiFile psiFile, int offset) {
|
||||
|
||||
@@ -1560,6 +1560,20 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
|
||||
myFixture.completeBasic()
|
||||
}
|
||||
|
||||
fun testCompletionInsideLiteral() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
public class B<T> {
|
||||
static void main() {
|
||||
call("class A{}.<caret>");
|
||||
}
|
||||
|
||||
private static void call(String number) {}
|
||||
}""".trimIndent())
|
||||
val basic = myFixture.completeBasic()
|
||||
assertTrue(basic.none { element -> element.`as`(CommandCompletionLookupElement::class.java) != null })
|
||||
}
|
||||
|
||||
|
||||
fun testHighlightingFormat() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.lang.injection.InjectedLanguageManager
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiLanguageInjectionHost
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
|
||||
import org.jetbrains.kotlin.analysis.api.KaImplementationDetail
|
||||
@@ -24,16 +25,33 @@ internal class KotlinCommandCompletionFactory : CommandCompletionFactory, DumbAw
|
||||
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return false
|
||||
if (offset < 1) return false
|
||||
if (psiFile !is KtFile) return false
|
||||
if (isInsideFor(psiFile, offset)) return false
|
||||
if (isInsideStringLiteral(psiFile, offset)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isInsideStringLiteral(psiFile: KtFile, offset: Int): Boolean {
|
||||
val element = psiFile.findElementAt(offset)
|
||||
if (element == null) return false
|
||||
val templateExpression = PsiTreeUtil.getParentOfType(
|
||||
element,
|
||||
KtStringTemplateExpression::class.java,
|
||||
KtStringTemplateEntryWithExpression::class.java
|
||||
)
|
||||
return templateExpression is KtStringTemplateExpression
|
||||
}
|
||||
|
||||
private fun isInsideFor(psiFile: KtFile, offset: Int): Boolean {
|
||||
var element = psiFile.findElementAt(offset - 1)
|
||||
val parent = element?.parent
|
||||
if (parent !is KtForExpression && parent !is KtNamedFunction) return true
|
||||
if (parent !is KtForExpression && parent !is KtNamedFunction) return false
|
||||
if (parent is KtForExpression) {
|
||||
element = element.prevSibling?.let {
|
||||
if (it is KtContainerNode) it.firstChild else it
|
||||
} ?: return true
|
||||
return !parent.loopRange.isAncestor(element)
|
||||
} ?: return false
|
||||
return parent.loopRange.isAncestor(element)
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
override fun supportFiltersWithDoublePrefix(): Boolean = false
|
||||
|
||||
@@ -677,6 +677,65 @@ class K2CommandCompletionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
assertTrue(elements[0].`as`(CommandCompletionLookupElement::class.java) != null)
|
||||
}
|
||||
|
||||
fun testNoCompletionInsideStringBlock() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
myFixture.configureByText(
|
||||
"x.kt", """
|
||||
class A {
|
||||
fun test() {
|
||||
call(
|
||||
""${'"'}some.a.<caret>
|
||||
""${'"'}.trimMargin(), "1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun call(key: String, key2: String) {
|
||||
println(key + key2)
|
||||
}""".trimIndent()
|
||||
)
|
||||
val elements = myFixture.complete(CompletionType.BASIC, 0)
|
||||
assertTrue(elements.none { it.`as`(CommandCompletionLookupElement::class.java) != null })
|
||||
}
|
||||
|
||||
fun testNoCompletionInsideStringLiteral() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
myFixture.configureByText(
|
||||
"x.kt", """
|
||||
class A {
|
||||
fun test3() {
|
||||
call("some.a.<caret>", "1")
|
||||
}
|
||||
}
|
||||
|
||||
fun call(key: String, key2: String) {
|
||||
println(key + key2)
|
||||
}""".trimIndent()
|
||||
)
|
||||
val elements = myFixture.complete(CompletionType.BASIC, 0)
|
||||
assertTrue(elements.none { it.`as`(CommandCompletionLookupElement::class.java) != null })
|
||||
}
|
||||
|
||||
fun testNoCompletionInsideStringInsideInterpolation() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
myFixture.configureByText(
|
||||
"x.kt", """
|
||||
class A {
|
||||
fun test2(a: String) {
|
||||
call(
|
||||
"some.a.${'$'}{a.<caret>}".trimMargin(), "1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun call(key: String, key2: String) {
|
||||
println(key + key2)
|
||||
}""".trimIndent()
|
||||
)
|
||||
val elements = myFixture.complete(CompletionType.BASIC, 0)
|
||||
assertFalse(elements.none { it.`as`(CommandCompletionLookupElement::class.java) != null })
|
||||
}
|
||||
|
||||
fun testNotFirstCompletion() {
|
||||
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
|
||||
myFixture.configureByText(
|
||||
|
||||
Reference in New Issue
Block a user