From c44c7f65d7160710dcc5cc71e89f62383b55ea87 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 12 Dec 2025 11:12:47 +0100 Subject: [PATCH] IJPL-214795 [java-completion] BringVariableIntoScope locals completion as ModCommands GitOrigin-RevId: d6750b86a53467d23c7f862bb1ef538ef33677cb --- .../resources/META-INF/JavaPlugin.xml | 1 + .../completion/JavaCompletionContributor.java | 60 ++--------------- .../InnerScopeVariableItemProvider.java | 25 +++++++ .../modcompletion/ReferenceItemProvider.java | 4 -- .../modcompletion/VariableCompletionItem.java | 2 +- .../quickfix/BringVariableIntoScopeFix.java | 65 ++++++++++++++++++- 6 files changed, 97 insertions(+), 60 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/InnerScopeVariableItemProvider.java diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 282828791470..30ebda9da881 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1196,6 +1196,7 @@ + diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 65a07053ce0c..f0a341995580 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -75,8 +75,6 @@ import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.JavaDeprecationUtils; import com.siyeh.ig.psiutils.TypeUtils; import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; -import one.util.streamex.EntryStream; -import one.util.streamex.StreamEx; import org.jetbrains.annotations.*; import java.util.*; @@ -897,7 +895,7 @@ public final class JavaCompletionContributor extends CompletionContributor imple } if (parameters.getInvocationCount() > 0) { - items.addAll(getInnerScopeVariables(parameters, position)); + items.addAll(getInnerScopeVariables(position)); } if (ref.getQualifier() instanceof PsiExpression qualifierExpression && @@ -945,56 +943,12 @@ public final class JavaCompletionContributor extends CompletionContributor imple return null; } - private static @Unmodifiable Collection getInnerScopeVariables(CompletionParameters parameters, PsiElement position) { - PsiElement container = BringVariableIntoScopeFix.getContainer(position); - if (container == null) return Collections.emptyList(); - Map> variableMap = - EntryStream.ofTree(container, (depth, element) -> depth > 2 ? null : StreamEx.of(element.getChildren())) - .values() - .select(PsiCodeBlock.class) - .flatArray(PsiCodeBlock::getStatements) - .select(PsiDeclarationStatement.class) - .flatArray(PsiDeclarationStatement::getDeclaredElements) - .select(PsiLocalVariable.class) - .remove(var -> PsiTreeUtil.isAncestor(var, position, true)) - .toMap(PsiLocalVariable::getName, Optional::of, (v1, v2) -> Optional.empty()); - PsiResolveHelper helper = JavaPsiFacade.getInstance(parameters.getOriginalFile().getProject()).getResolveHelper(); - variableMap.values().removeAll(Collections.singleton(Optional.empty())); - variableMap.keySet().removeIf(name -> helper.resolveReferencedVariable(name, position) != null); - int offset = position.getTextRange().getStartOffset(); - variableMap.values().removeIf(v -> v.orElseThrow().getTextRange().getStartOffset() > offset); - if (variableMap.isEmpty()) return Collections.emptyList(); - return ContainerUtil.map(variableMap.values(), optVar -> { - assert optVar.isPresent(); - PsiLocalVariable variable = optVar.get(); - String place = getPlace(variable); - return new VariableLookupItem(variable, JavaBundle.message("completion.inner.scope.tail.text", place)).setPriority(-1); - }); - } - - private static @Nls @NotNull String getPlace(PsiLocalVariable variable) { - String place = JavaBundle.message("completion.inner.scope"); - PsiCodeBlock block = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); - PsiElement statement = block == null ? null : block.getParent(); - if (statement instanceof PsiTryStatement) { - place = ((PsiTryStatement)statement).getFinallyBlock() == block ? JavaKeywords.TRY + "-" + JavaKeywords.FINALLY : JavaKeywords.TRY; - } - else if (statement instanceof PsiCatchSection) { - place = JavaKeywords.CATCH; - } - else if (statement instanceof PsiSynchronizedStatement) { - place = JavaKeywords.SYNCHRONIZED; - } - else if (statement instanceof PsiBlockStatement) { - PsiElement parent = statement.getParent(); - if (parent instanceof PsiWhileStatement) { - place = JavaKeywords.WHILE; - } - else if (parent instanceof PsiIfStatement) { - place = ((PsiIfStatement)parent).getThenBranch() == statement ? JavaKeywords.IF + "-then" : JavaKeywords.IF + "-" + JavaKeywords.ELSE; - } - } - return place; + private static @Unmodifiable Collection getInnerScopeVariables(PsiElement position) { + List list = BringVariableIntoScopeFix.findInnerScopeVariables(position); + return ContainerUtil.map(list, variable -> + new VariableLookupItem( + variable, JavaBundle.message("completion.inner.scope.tail.text", BringVariableIntoScopeFix.getVariableDeclarationPlace(variable))) + .setPriority(-1)); } private static @NotNull List completePermitsListReference(@NotNull CompletionParameters parameters, diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/InnerScopeVariableItemProvider.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/InnerScopeVariableItemProvider.java new file mode 100644 index 000000000000..e1a4a7caf37f --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/InnerScopeVariableItemProvider.java @@ -0,0 +1,25 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.completion.modcompletion; + +import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix; +import com.intellij.java.JavaBundle; +import com.intellij.modcompletion.ModCompletionItem; +import com.intellij.modcompletion.ModCompletionItemProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLocalVariable; +import org.jetbrains.annotations.NotNullByDefault; + +import java.util.function.Consumer; + +@NotNullByDefault +final class InnerScopeVariableItemProvider implements ModCompletionItemProvider { + @Override + public void provideItems(CompletionContext context, Consumer sink) { + if (context.invocationCount() < 1) return; + PsiElement position = context.getPosition(); + for (PsiLocalVariable variable : BringVariableIntoScopeFix.findInnerScopeVariables(position)) { + sink.accept(new VariableCompletionItem( + variable, JavaBundle.message("completion.inner.scope.tail.text", BringVariableIntoScopeFix.getVariableDeclarationPlace(variable)))); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java index 5c5a4337a835..65bf77be5486 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java @@ -150,10 +150,6 @@ final class ReferenceItemProvider implements ModCompletionItemProvider { sink.accept(firstArrayElement); } }); - - //if (context.invocationCount() > 0) { - // items.addAll(getInnerScopeVariables(parameters, position)); - //} } private static @Nullable CommonCompletionItem accessFirstElement(ModCompletionItem item) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java index 373ab1406c86..b616d218942b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java @@ -142,12 +142,12 @@ final class VariableCompletionItem extends PsiUpdateCompletionItem if (toDelete != null && toDelete.isValid()) { document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset()); } - PsiDocumentManager.getInstance(project).commitDocument(document); } else if (VariableLookupItem.shouldQualify(field, file.findReferenceAt(updater.getCaretOffset() - 1))) { qualifyFieldReference(actionContext.offset(), updater, field); } } + PsiDocumentManager.getInstance(project).commitDocument(document); PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, updater.getCaretOffset() - 1, PsiReferenceExpression.class, false); if (ref != null) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java index d4c2c8023fb4..48f9ddbe486f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/BringVariableIntoScopeFix.java @@ -5,6 +5,8 @@ import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.intention.PriorityAction; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInspection.util.IntentionName; +import com.intellij.java.JavaBundle; +import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.modcommand.ActionContext; import com.intellij.modcommand.ModCommand; import com.intellij.modcommand.ModCommandAction; @@ -16,11 +18,14 @@ import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.controlFlow.ControlFlowUtil; import com.intellij.psi.util.*; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.EntryStream; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Objects; +import java.util.*; public final class BringVariableIntoScopeFix implements ModCommandAction { private static final Logger LOG = Logger.getInstance(BringVariableIntoScopeFix.class); @@ -182,4 +187,60 @@ public final class BringVariableIntoScopeFix implements ModCommandAction { PsiExpression initializer = factory.createExpressionFromText(init, variable); variable.setInitializer(initializer); } + + /** + * @param position PSI position to find the inner scope variables + * @return list of variables declared in the scopes nested to the position scope + */ + public static @NotNull List findInnerScopeVariables(PsiElement position) { + PsiElement container = getContainer(position); + Map> variableMap = Map.of(); + if (container != null) { + variableMap = EntryStream.ofTree(container, (depth, element) -> depth > 2 ? null : StreamEx.of(element.getChildren())) + .values() + .select(PsiCodeBlock.class) + .flatArray(PsiCodeBlock::getStatements) + .select(PsiDeclarationStatement.class) + .flatArray(PsiDeclarationStatement::getDeclaredElements) + .select(PsiLocalVariable.class) + .remove(var -> PsiTreeUtil.isAncestor(var, position, true)) + .toMap(PsiLocalVariable::getName, Optional::of, (v1, v2) -> Optional.empty()); + PsiResolveHelper helper = JavaPsiFacade.getInstance(container.getProject()).getResolveHelper(); + variableMap.values().removeAll(Collections.singleton(Optional.empty())); + variableMap.keySet().removeIf(name -> helper.resolveReferencedVariable(name, position) != null); + int offset = position.getTextRange().getStartOffset(); + variableMap.values().removeIf(v -> v.orElseThrow().getTextRange().getStartOffset() > offset); + } + List list = ContainerUtil.map(variableMap.values(), Optional::get); + return list; + } + + /** + * @param variable variable to describe its location + * @return the human-readable description of variable's declaration place, useful for variable brought into the scope. + */ + public static @Nls @NotNull String getVariableDeclarationPlace(PsiLocalVariable variable) { + String place = JavaBundle.message("completion.inner.scope"); + PsiCodeBlock block = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class); + PsiElement statement = block == null ? null : block.getParent(); + if (statement instanceof PsiTryStatement) { + place = ((PsiTryStatement)statement).getFinallyBlock() == block ? JavaKeywords.TRY + "-" + JavaKeywords.FINALLY : JavaKeywords.TRY; + } + else if (statement instanceof PsiCatchSection) { + place = JavaKeywords.CATCH; + } + else if (statement instanceof PsiSynchronizedStatement) { + place = JavaKeywords.SYNCHRONIZED; + } + else if (statement instanceof PsiBlockStatement) { + PsiElement parent = statement.getParent(); + if (parent instanceof PsiWhileStatement) { + place = JavaKeywords.WHILE; + } + else if (parent instanceof PsiIfStatement ifStatement) { + place = ifStatement.getThenBranch() == statement ? JavaKeywords.IF + "-then" : JavaKeywords.IF + "-" + JavaKeywords.ELSE; + } + } + return place; + } }