IJPL-214795 [java-completion] BringVariableIntoScope locals completion as ModCommands

GitOrigin-RevId: d6750b86a53467d23c7f862bb1ef538ef33677cb
This commit is contained in:
Tagir Valeev
2025-12-15 10:16:33 +00:00
committed by intellij-monorepo-bot
parent 9786f35b95
commit c44c7f65d7
6 changed files with 97 additions and 60 deletions
@@ -1196,6 +1196,7 @@
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.AnnotationAttributeItemProvider"/>
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.GenerateMemberItemProvider"/>
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.ReferenceItemProvider"/>
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.InnerScopeVariableItemProvider"/>
<completion.skip implementation="com.intellij.codeInsight.completion.AbstractExpectedTypeSkipper" id="skipAbstract"/>
<completion.skip implementation="com.intellij.codeInsight.completion.DeprecatedSkipper" id="skipDeprecated"/>
@@ -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<LookupElement> getInnerScopeVariables(CompletionParameters parameters, PsiElement position) {
PsiElement container = BringVariableIntoScopeFix.getContainer(position);
if (container == null) return Collections.emptyList();
Map<String, Optional<PsiLocalVariable>> 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.<PsiLocalVariable>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<LookupElement> getInnerScopeVariables(PsiElement position) {
List<PsiLocalVariable> 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<LookupElement> completePermitsListReference(@NotNull CompletionParameters parameters,
@@ -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<ModCompletionItem> 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))));
}
}
}
@@ -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) {
@@ -142,12 +142,12 @@ final class VariableCompletionItem extends PsiUpdateCompletionItem<PsiVariable>
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) {
@@ -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<PsiLocalVariable> findInnerScopeVariables(PsiElement position) {
PsiElement container = getContainer(position);
Map<String, Optional<PsiLocalVariable>> 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.<PsiLocalVariable>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<PsiLocalVariable> 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;
}
}