From d327bf69b2ab4735fa07b0f7583672aa3e2b85de Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Fri, 13 Feb 2026 15:43:33 +0100 Subject: [PATCH] IJPL-207762 [command completion] extract nested classes from JavaCommandCompletionFactory to upper level GitOrigin-RevId: 52e85daa8b41c476772259fb6c8fc861130b367d --- .../resources/META-INF/JavaPlugin.xml | 4 +- .../JavaCommandCompletionFactory.java | 109 +----------------- .../JavaIntentionCommandOffsetProvider.java | 94 +++++++++++++++ .../commands/JavaIntentionCommandSkipper.java | 26 +++++ 4 files changed, 123 insertions(+), 110 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandOffsetProvider.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandSkipper.java diff --git a/java/java-backend/resources/META-INF/JavaPlugin.xml b/java/java-backend/resources/META-INF/JavaPlugin.xml index 0dc71f1c3b61..ed36b41f916e 100644 --- a/java/java-backend/resources/META-INF/JavaPlugin.xml +++ b/java/java-backend/resources/META-INF/JavaPlugin.xml @@ -1189,11 +1189,11 @@ + implementationClass="com.intellij.codeInsight.completion.commands.JavaIntentionCommandSkipper"/> + implementationClass="com.intellij.codeInsight.completion.commands.JavaIntentionCommandOffsetProvider"/> diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaCommandCompletionFactory.java b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaCommandCompletionFactory.java index 3d5a3a1a3873..8b696c6da8d8 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaCommandCompletionFactory.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaCommandCompletionFactory.java @@ -1,41 +1,19 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.codeInsight.completion.commands; import com.intellij.codeInsight.completion.command.CommandCompletionFactory; -import com.intellij.codeInsight.completion.command.commands.IntentionCommandOffsetProvider; -import com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper; -import com.intellij.codeInsight.daemon.impl.quickfix.CreateGetterOrSetterFix; -import com.intellij.codeInsight.daemon.impl.quickfix.ExpensivePsiIntentionAction; -import com.intellij.codeInsight.intention.CommonIntentionAction; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ex.QuickFixWrapper; -import com.intellij.modcommand.ModCommandAction; -import com.intellij.modcommand.ModCommandService; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.JavaTokenType; -import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJShellFile; import com.intellij.psi.PsiJavaFile; import com.intellij.psi.PsiJavaToken; -import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiTypeElement; -import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - class JavaCommandCompletionFactory implements CommandCompletionFactory, DumbAware { @Override @@ -69,89 +47,4 @@ class JavaCommandCompletionFactory implements CommandCompletionFactory, DumbAwar 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) { - if (action instanceof ExpensivePsiIntentionAction) return true; - LocalQuickFix fix = QuickFixWrapper.unwrap(action); - if (fix != null) { - ModCommandAction unwrappedAction = ModCommandService.getInstance().unwrap(fix); - if (unwrappedAction instanceof CreateGetterOrSetterFix) return true; - } - return IntentionCommandSkipper.super.skip(action, psiFile, offset); - } - } - - static class JavaIntentionCommandOffsetProvider implements IntentionCommandOffsetProvider { - @Override - public @NotNull List<@NotNull Integer> findOffsets(@NotNull PsiFile psiFile, int offset) { - Set results = new HashSet<>(); - Document document = psiFile.getFileDocument(); - Queue queue = new ArrayDeque<>(); - queue.add(offset); - while (!queue.isEmpty()) { - Integer currentOffset = queue.poll(); - if (currentOffset == 0) continue; - PsiElement element = psiFile.findElementAt(currentOffset - 1); - int currentLine = document.getLineNumber(currentOffset - 1); - while (element instanceof PsiWhiteSpace || - element != null && - StringUtil.isEmptyOrSpaces(element.getText())) { - element = element.getPrevSibling(); - if (element == null) { - break; - } - element = PsiTreeUtil.getDeepestLast(element); - if (currentLine != document.getLineNumber(element.getTextRange().getEndOffset())) { - element = null; - break; - } - currentOffset = element.getTextRange().getEndOffset(); - } - results.add(currentOffset); - if (element == null) continue; - if (element.getParent() instanceof PsiLiteralExpression literalExpression && literalExpression.getValue() instanceof String) { - results.add(literalExpression.getTextRange().getEndOffset() - (literalExpression.isTextBlock() ? 3 : 1)); - } - PsiElement parent = element.getParent(); - if (element instanceof PsiJavaToken) { - Character open = braces.get(element.getText().charAt(0)); - //collect first from last - PsiElement curParent = parent; - if (open != null && curParent.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) { - while (curParent.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) { - int nextOffset = curParent.getTextRange().getStartOffset(); - curParent = curParent.getParent(); - boolean stopParent = curParent == null || curParent instanceof PsiClass || curParent instanceof PsiFile; - if (results.add(nextOffset) && !stopParent) { - queue.add(nextOffset); - } - if (stopParent) { - break; - } - } - } - //collect open from closed - if (open != null) { - for (PsiElement child : parent.getChildren()) { - if (child instanceof PsiJavaToken && child.getText().charAt(0) == open) { - int nextOffset = child.getTextRange().getStartOffset(); - if (!results.contains(nextOffset)) { - queue.add(nextOffset); - } - break; - } - } - } - } - } - return new ArrayList<>(results); - } - - private static final Map braces = - Map.of(']', '[', - '}', '{', - ')', '('); - } } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandOffsetProvider.java b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandOffsetProvider.java new file mode 100644 index 000000000000..293f2636f46c --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandOffsetProvider.java @@ -0,0 +1,94 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.completion.commands; + +import com.intellij.codeInsight.completion.command.commands.IntentionCommandOffsetProvider; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaToken; +import com.intellij.psi.PsiLiteralExpression; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +class JavaIntentionCommandOffsetProvider implements IntentionCommandOffsetProvider { + @Override + public @NotNull List<@NotNull Integer> findOffsets(@NotNull PsiFile psiFile, int offset) { + Set results = new HashSet<>(); + Document document = psiFile.getFileDocument(); + Queue queue = new ArrayDeque<>(); + queue.add(offset); + while (!queue.isEmpty()) { + Integer currentOffset = queue.poll(); + if (currentOffset == 0) continue; + PsiElement element = psiFile.findElementAt(currentOffset - 1); + int currentLine = document.getLineNumber(currentOffset - 1); + while (element instanceof PsiWhiteSpace || + element != null && + StringUtil.isEmptyOrSpaces(element.getText())) { + element = element.getPrevSibling(); + if (element == null) { + break; + } + element = PsiTreeUtil.getDeepestLast(element); + if (currentLine != document.getLineNumber(element.getTextRange().getEndOffset())) { + element = null; + break; + } + currentOffset = element.getTextRange().getEndOffset(); + } + results.add(currentOffset); + if (element == null) continue; + if (element.getParent() instanceof PsiLiteralExpression literalExpression && literalExpression.getValue() instanceof String) { + results.add(literalExpression.getTextRange().getEndOffset() - (literalExpression.isTextBlock() ? 3 : 1)); + } + PsiElement parent = element.getParent(); + if (element instanceof PsiJavaToken) { + Character open = braces.get(element.getText().charAt(0)); + //collect first from last + PsiElement curParent = parent; + if (open != null && curParent.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) { + while (curParent.getTextRange().getEndOffset() == element.getTextRange().getEndOffset()) { + int nextOffset = curParent.getTextRange().getStartOffset(); + curParent = curParent.getParent(); + boolean stopParent = curParent == null || curParent instanceof PsiClass || curParent instanceof PsiFile; + if (results.add(nextOffset) && !stopParent) { + queue.add(nextOffset); + } + if (stopParent) { + break; + } + } + } + //collect open from closed + if (open != null) { + for (PsiElement child : parent.getChildren()) { + if (child instanceof PsiJavaToken && child.getText().charAt(0) == open) { + int nextOffset = child.getTextRange().getStartOffset(); + if (!results.contains(nextOffset)) { + queue.add(nextOffset); + } + break; + } + } + } + } + } + return new ArrayList<>(results); + } + + private static final Map braces = + Map.of(']', '[', + '}', '{', + ')', '('); +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandSkipper.java b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandSkipper.java new file mode 100644 index 000000000000..da1735f2c436 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/JavaIntentionCommandSkipper.java @@ -0,0 +1,26 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.completion.commands; + +import com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper; +import com.intellij.codeInsight.daemon.impl.quickfix.CreateGetterOrSetterFix; +import com.intellij.codeInsight.daemon.impl.quickfix.ExpensivePsiIntentionAction; +import com.intellij.codeInsight.intention.CommonIntentionAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ex.QuickFixWrapper; +import com.intellij.modcommand.ModCommandAction; +import com.intellij.modcommand.ModCommandService; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +class JavaIntentionCommandSkipper implements IntentionCommandSkipper { + @Override + public boolean skip(@NotNull CommonIntentionAction action, @NotNull PsiFile psiFile, int offset) { + if (action instanceof ExpensivePsiIntentionAction) return true; + LocalQuickFix fix = QuickFixWrapper.unwrap(action); + if (fix != null) { + ModCommandAction unwrappedAction = ModCommandService.getInstance().unwrap(fix); + if (unwrappedAction instanceof CreateGetterOrSetterFix) return true; + } + return IntentionCommandSkipper.super.skip(action, psiFile, offset); + } +}