[java-imports] IDEA-13817 Create "favorites" for static import and completion

GitOrigin-RevId: 786ff0d07a68ecb53f3c929600353bc28f483bf0
This commit is contained in:
Mikhail Pyltsin
2025-01-16 14:21:49 +00:00
committed by intellij-monorepo-bot
parent 2afb61710c
commit b1a1f903ba
30 changed files with 742 additions and 247 deletions
@@ -415,6 +415,7 @@
<projectService serviceImplementation="com.intellij.jarRepository.RemoteRepositoriesConfiguration"/>
<projectService serviceImplementation="com.intellij.jarRepository.services.MavenRepositoryServicesManager"/>
<projectService serviceImplementation="com.intellij.codeInsight.JavaProjectCodeInsightSettings"/>
<applicationService serviceImplementation="com.intellij.codeInsight.JavaIdeCodeInsightSettings"/>
<projectService serviceInterface="com.intellij.testIntegration.TestFailedLineManager"
serviceImplementation="com.intellij.execution.codeInspection.TestFailedLineManagerImpl"/>
<projectService serviceInterface="com.intellij.codeInspection.ex.EntryPointsManager"
@@ -1,232 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.application.options.editor;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.execution.util.ListTableWithButtons;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBoxTableRenderer;
import com.intellij.openapi.ui.ComponentValidator;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.cellvalidators.CellComponentProvider;
import com.intellij.openapi.ui.cellvalidators.CellTooltipManager;
import com.intellij.openapi.ui.cellvalidators.ValidatingTableCellRendererWrapper;
import com.intellij.openapi.ui.cellvalidators.ValidationUtils;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.ScrollingUtil;
import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.util.ui.ListTableModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.util.*;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
class ExcludeTable extends ListTableWithButtons<ExcludeTable.Item> {
private static final Pattern ourPackagePattern = Pattern.compile("([\\w*]+\\.)*[\\w*]+");
private static final BiFunction<Object, JComponent, ValidationInfo> validationInfoProducer = (value, component) ->
(value == null || StringUtil.isEmpty(value.toString()) || ourPackagePattern.matcher(value.toString()).matches()) ?
null : new ValidationInfo(JavaBundle.message("illegal.name.validation.info", value.toString()), component);
private static final Disposable validatorsDisposable = Disposer.newDisposable();
private static final ColumnInfo<Item, String> NAME_COLUMN = new ColumnInfo<>(JavaBundle.message("exclude.table.mask")) {
@Override
public @Nullable String valueOf(Item pair) {
return pair.exclude;
}
@Override
public TableCellEditor getEditor(Item pair) {
ExtendableTextField cellEditor = new ExtendableTextField();
cellEditor.putClientProperty(DarculaUIUtil.COMPACT_PROPERTY, Boolean.TRUE);
new ComponentValidator(validatorsDisposable).withValidator(() -> {
String text = cellEditor.getText();
boolean hasError = !StringUtil.isEmpty(text) && !ourPackagePattern.matcher(text).matches();
ValidationUtils.setExtension(cellEditor, ValidationUtils.ERROR_EXTENSION, hasError);
return validationInfoProducer.apply(text, cellEditor);
}).andRegisterOnDocumentListener(cellEditor).installOn(cellEditor);
return new DefaultCellEditor(cellEditor);
}
@Override
public TableCellRenderer getRenderer(Item pair) {
JTextField cellEditor = new JTextField();
cellEditor.putClientProperty(DarculaUIUtil.COMPACT_PROPERTY, Boolean.TRUE);
return new ValidatingTableCellRendererWrapper(new DefaultTableCellRenderer()).
withCellValidator((value, row, column) -> validationInfoProducer.apply(value, null)).
bindToEditorSize(cellEditor::getPreferredSize);
}
@Override
public boolean isCellEditable(Item pair) {
return true;
}
@Override
public void setValue(Item item, String value) {
item.exclude = value;
}
};
private static final ColumnInfo<Item, ExclusionScope> SCOPE_COLUMN = new ColumnInfo<>(JavaBundle.message(
"exclude.table.scope.column")) {
@Override
public @Nullable ExclusionScope valueOf(Item pair) {
return pair.scope;
}
@Override
public TableCellRenderer getRenderer(Item pair) {
return new ComboBoxTableRenderer<>(ExclusionScope.values());
}
@Override
public TableCellEditor getEditor(Item pair) {
return new ComboBoxTableRenderer<>(ExclusionScope.values());
}
@Override
public boolean isCellEditable(Item pair) {
return true;
}
@Override
public void setValue(Item pair, ExclusionScope value) {
pair.scope = value;
}
@Override
public String getMaxStringValue() {
return "Project";
}
@Override
public int getAdditionalWidth() {
return JBUIScale.scale(12) + AllIcons.General.ArrowDown.getIconWidth();
}
};
private final Project myProject;
ExcludeTable(@NotNull Project project) {
myProject = project;
JBTable table = getTableView();
table.getEmptyText().clear();
table.getEmptyText().appendLine(JavaBundle.message("exclude.from.imports.no.exclusions"));
table.getEmptyText().appendLine(JavaBundle.message("exclude.from.imports.no.exclusions.2"));
table.setStriped(false);
new CellTooltipManager(myProject).withCellComponentProvider(CellComponentProvider.forTable(table)).installOn(table);
Disposer.register(project, validatorsDisposable);
}
@Override
protected ListTableModel<Item> createListModel() {
return new ListTableModel<>(NAME_COLUMN, SCOPE_COLUMN);
}
@Override
protected Item createElement() {
return new Item("", ExclusionScope.IDE);
}
@Override
protected boolean isEmpty(Item element) {
return element.exclude.isEmpty();
}
@Override
protected Item cloneElement(Item variable) {
return new Item(variable.exclude, variable.scope);
}
@Override
protected boolean canDeleteElement(Item selection) {
return true;
}
void addExcludePackage(@NotNull String packageName) {
int index = 0;
while (index < getTableView().getListTableModel().getRowCount()) {
if (getTableView().getListTableModel().getItem(index).exclude.compareTo(packageName) > 0) {
break;
}
index++;
}
getTableView().getListTableModel().insertRow(index, new Item(packageName, ExclusionScope.IDE));
getTableView().clearSelection();
getTableView().addRowSelectionInterval(index, index);
ScrollingUtil.ensureIndexIsVisible(getTableView(), index, 0);
IdeFocusManager.getGlobalInstance().requestFocus(getTableView(), false);
}
void reset() {
List<Item> rows = new ArrayList<>();
for (String s : CodeInsightSettings.getInstance().EXCLUDED_PACKAGES) {
rows.add(new Item(s, ExclusionScope.IDE));
}
for (String s : JavaProjectCodeInsightSettings.getSettings(myProject).excludedNames) {
rows.add(new Item(s, ExclusionScope.Project));
}
rows.sort(Comparator.comparing(o -> o.exclude));
setValues(rows);
}
void apply() {
CodeInsightSettings.getInstance().EXCLUDED_PACKAGES = ArrayUtilRt.toStringArray(getExcludedPackages(ExclusionScope.IDE));
JavaProjectCodeInsightSettings.getSettings(myProject).excludedNames = getExcludedPackages(ExclusionScope.Project);
}
private List<String> getExcludedPackages(ExclusionScope scope) {
List<String> result = new ArrayList<>();
for (Item pair : getTableView().getListTableModel().getItems()) {
if (scope == pair.scope) {
result.add(pair.exclude);
}
}
Collections.sort(result);
return result;
}
boolean isModified() {
return !getExcludedPackages(ExclusionScope.IDE).equals(Arrays.asList(CodeInsightSettings.getInstance().EXCLUDED_PACKAGES))
||
!getExcludedPackages(ExclusionScope.Project).equals(JavaProjectCodeInsightSettings.getSettings(myProject).excludedNames);
}
private enum ExclusionScope {Project, IDE}
static class Item {
String exclude;
ExclusionScope scope;
Item(@NotNull String exclude, ExclusionScope scope) {
this.exclude = exclude;
this.scope = scope;
}
}
}
@@ -0,0 +1,248 @@
// 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.application.options.editor;
import com.intellij.execution.util.ListTableWithButtons;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ui.ComboBoxTableRenderer;
import com.intellij.openapi.ui.ComponentValidator;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.cellvalidators.CellComponentProvider;
import com.intellij.openapi.ui.cellvalidators.CellTooltipManager;
import com.intellij.openapi.ui.cellvalidators.ValidatingTableCellRendererWrapper;
import com.intellij.openapi.ui.cellvalidators.ValidationUtils;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.ScrollingUtil;
import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.util.ui.ListTableModel;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.util.*;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
abstract class ImportTable extends ListTableWithButtons<ImportTable.Item> implements Disposable.Default {
private static final Pattern ourPackagePattern = Pattern.compile("([\\w*]+\\.)*[\\w*]+");
private static final BiFunction<Object, JComponent, ValidationInfo> validationInfoProducer = (value, component) ->
(value == null || StringUtil.isEmpty(value.toString()) || ourPackagePattern.matcher(value.toString()).matches()) ?
null : new ValidationInfo(JavaBundle.message("illegal.name.validation.info", value.toString()), component);
private static final Disposable validatorsDisposable = Disposer.newDisposable();
private final @NotNull @Nls String myNameColumn;
private final @NotNull @Nls String myScopeColumn;
ImportTable(@NotNull Disposable parentDisposable,
@NlsSafe @NotNull String firstMessageLine,
@NlsSafe @NotNull String secondMessageLine,
@NotNull @Nls String nameColumn,
@NotNull @Nls String scopeColumn) {
myNameColumn = nameColumn;
myScopeColumn = scopeColumn;
JBTable table = getTableView();
table.getEmptyText().clear();
table.getEmptyText().appendLine(firstMessageLine);
table.getEmptyText().appendLine(secondMessageLine);
table.setStriped(false);
new CellTooltipManager(parentDisposable).withCellComponentProvider(CellComponentProvider.forTable(table)).installOn(table);
Disposer.register(parentDisposable, validatorsDisposable);
}
@Override
protected ListTableModel<Item> createListModel() {
return new ListTableModel<>(createNameColumn(), createScopeColumn());
}
private ColumnInfo<Item, Scope> createScopeColumn() {
return new ColumnInfo<>(myScopeColumn) {
@Override
public @Nullable ImportTable.Scope valueOf(Item pair) {
return pair.scope;
}
@Override
public TableCellRenderer getRenderer(Item pair) {
return new ComboBoxTableRenderer<>(Scope.values());
}
@Override
public TableCellEditor getEditor(Item pair) {
return new ComboBoxTableRenderer<>(Scope.values());
}
@Override
public boolean isCellEditable(Item pair) {
return true;
}
@Override
public void setValue(Item pair, Scope value) {
pair.scope = value;
}
@Override
public String getMaxStringValue() {
return "Project";
}
@Override
public int getAdditionalWidth() {
return JBUIScale.scale(12) + AllIcons.General.ArrowDown.getIconWidth();
}
};
}
private ColumnInfo<Item, String> createNameColumn() {
return new ColumnInfo<>(myNameColumn) {
@Override
public @Nullable String valueOf(Item pair) {
return pair.row;
}
@Override
public TableCellEditor getEditor(Item pair) {
ExtendableTextField cellEditor = new ExtendableTextField();
cellEditor.putClientProperty(DarculaUIUtil.COMPACT_PROPERTY, Boolean.TRUE);
new ComponentValidator(validatorsDisposable).withValidator(() -> {
String text = cellEditor.getText();
boolean hasError = !StringUtil.isEmpty(text) && !ourPackagePattern.matcher(text).matches();
ValidationUtils.setExtension(cellEditor, ValidationUtils.ERROR_EXTENSION, hasError);
return validationInfoProducer.apply(text, cellEditor);
}).andRegisterOnDocumentListener(cellEditor).installOn(cellEditor);
return new DefaultCellEditor(cellEditor);
}
@Override
public TableCellRenderer getRenderer(Item pair) {
JTextField cellEditor = new JTextField();
cellEditor.putClientProperty(DarculaUIUtil.COMPACT_PROPERTY, Boolean.TRUE);
return new ValidatingTableCellRendererWrapper(new DefaultTableCellRenderer()).
withCellValidator((value, row, column) -> validationInfoProducer.apply(value, null)).
bindToEditorSize(cellEditor::getPreferredSize);
}
@Override
public boolean isCellEditable(Item pair) {
return true;
}
@Override
public void setValue(Item item, String value) {
item.row = value;
}
};
}
@Override
protected Item createElement() {
return new Item("", Scope.IDE);
}
@Override
protected boolean isEmpty(Item element) {
return element.row.isEmpty();
}
@Override
protected Item cloneElement(Item variable) {
return new Item(variable.row, variable.scope);
}
@Override
protected boolean canDeleteElement(Item selection) {
return true;
}
void addRow(@NotNull String row) {
int index = 0;
while (index < getTableView().getListTableModel().getRowCount()) {
if (getTableView().getListTableModel().getItem(index).row.compareTo(row) > 0) {
break;
}
index++;
}
getTableView().getListTableModel().insertRow(index, new Item(row, Scope.IDE));
getTableView().clearSelection();
getTableView().addRowSelectionInterval(index, index);
ScrollingUtil.ensureIndexIsVisible(getTableView(), index, 0);
IdeFocusManager.getGlobalInstance().requestFocus(getTableView(), false);
}
void reset() {
List<Item> rows = new ArrayList<>();
for (String s : getIdeRows()) {
rows.add(new Item(s, Scope.IDE));
}
for (String s : getProjectRows()) {
rows.add(new Item(s, Scope.Project));
}
rows.sort(Comparator.comparing(o -> o.row));
setValues(rows);
}
void apply() {
setIdeRows(ArrayUtilRt.toStringArray(getRows(Scope.IDE)));
setProjectRows(getRows(Scope.Project).toArray(ArrayUtilRt.EMPTY_STRING_ARRAY));
}
protected abstract @NotNull String @NotNull [] getIdeRows();
protected abstract @NotNull String @NotNull [] getProjectRows();
protected abstract void setIdeRows(@NotNull String @NotNull [] rows);
protected abstract void setProjectRows(@NotNull String @NotNull [] rows);
private List<String> getRows(Scope scope) {
List<String> result = new ArrayList<>();
for (Item pair : getTableView().getListTableModel().getItems()) {
if (scope == pair.scope) {
result.add(pair.row);
}
}
Collections.sort(result);
return result;
}
boolean isModified() {
return !Arrays.equals(ArrayUtilRt.toStringArray(getRows(Scope.IDE)), getIdeRows())
||
!Arrays.equals(ArrayUtilRt.toStringArray(getRows(Scope.Project)), getProjectRows());
}
private enum Scope {Project, IDE}
static class Item {
String row;
Scope scope;
Item(@NotNull String row, Scope scope) {
this.row = row;
this.scope = scope;
}
}
}
@@ -4,17 +4,24 @@ package com.intellij.application.options.editor
import com.intellij.application.options.CodeStyleConfigurableWrapper
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.CodeInsightWorkspaceSettings
import com.intellij.codeInsight.JavaIdeCodeInsightSettings
import com.intellij.codeInsight.JavaProjectCodeInsightSettings
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings
import com.intellij.icons.AllIcons
import com.intellij.ide.DataManager
import com.intellij.ide.util.TreeClassChooserFactory
import com.intellij.java.JavaBundle
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.options.UiDslUnnamedConfigurable
import com.intellij.openapi.options.ex.Settings
import com.intellij.openapi.project.DumbService.Companion.isDumb
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.ui.AnActionButtonRunnable
import com.intellij.ui.ContextHelpLabel
import com.intellij.ui.IdeUICustomization
import com.intellij.ui.dsl.builder.*
@@ -22,10 +29,70 @@ import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import javax.swing.JComponent
class JavaAutoImportOptions(val project: Project) : UiDslUnnamedConfigurable.Simple(), AutoImportOptionsProvider {
private val excludeTable = ExcludeTable(project)
private val excludeTable = object : ImportTable(project,
JavaBundle.message("exclude.from.imports.no.exclusions"),
JavaBundle.message("exclude.from.imports.no.exclusions.2"),
JavaBundle.message("exclude.table.mask"),
JavaBundle.message("exclude.table.scope.column")) {
override fun getIdeRows(): Array<out String> {
return CodeInsightSettings.getInstance().EXCLUDED_PACKAGES
}
override fun getProjectRows(): Array<out String> {
return JavaProjectCodeInsightSettings.getSettings(project).excludedNames.toTypedArray()
}
override fun setIdeRows(rows: Array<out String>) {
CodeInsightSettings.getInstance().EXCLUDED_PACKAGES = rows
}
override fun setProjectRows(rows: Array<out String>) {
JavaProjectCodeInsightSettings.getSettings(project).excludedNames = rows.toList()
}
}
private val autoStaticImportTable = object : ImportTable(project,
JavaBundle.message("auto.static.import.comment"),
JavaBundle.message("auto.static.import.comment.2"),
JavaBundle.message("auto.static.import.class"),
JavaBundle.message("auto.static.import.scope")) {
override fun createAddAction(): AnActionButtonRunnable? {
return AnActionButtonRunnable { button ->
val element = createElement()
if (!project.isDefault && !isDumb(project)) {
val chooser = TreeClassChooserFactory.getInstance(project)
.createWithInnerClassesScopeChooser(JavaBundle.message("dialog.title.choose.class"), GlobalSearchScope.allScope(project), null, null)
chooser.showDialog()
val selected = chooser.getSelected()
element.row = selected?.qualifiedName ?: ""
}
addNewElement(element)
}
}
override fun getIdeRows(): Array<out String> {
return JavaIdeCodeInsightSettings.getInstance().includedAutoStaticNames.toTypedArray()
}
override fun getProjectRows(): Array<out String> {
return JavaProjectCodeInsightSettings.getSettings(project).includedAutoStaticNames.toTypedArray()
}
override fun setIdeRows(rows: Array<out String>) {
JavaIdeCodeInsightSettings.getInstance().includedAutoStaticNames = rows.toList()
}
override fun setProjectRows(rows: Array<out String>) {
JavaProjectCodeInsightSettings.getSettings(project).includedAutoStaticNames = rows.toList()
}
}
override fun Panel.createContent() {
excludeTable.tableView.setShowGrid(false)
autoStaticImportTable.tableView.setShowGrid(false)
val dcaSettings = DaemonCodeAnalyzerSettings.getInstance()
val ciSettings = CodeInsightSettings.getInstance()
val ciWorkspaceSettings = CodeInsightWorkspaceSettings.getInstance(project)
@@ -73,6 +140,14 @@ class JavaAutoImportOptions(val project: Project) : UiDslUnnamedConfigurable.Sim
toolTipText = IdeUICustomization.getInstance().projectMessage("configurable.current.project.tooltip")
}
}
row {
cell(autoStaticImportTable.component)
.align(AlignX.FILL)
.label(JavaBundle.message("auto.static.import.completion.group"), LabelPosition.TOP)
.onApply { autoStaticImportTable.apply() }
.onReset { autoStaticImportTable.reset() }
.onIsModified { autoStaticImportTable.isModified }
}
row {
cell(excludeTable.component)
.align(AlignX.FILL)
@@ -81,6 +156,9 @@ class JavaAutoImportOptions(val project: Project) : UiDslUnnamedConfigurable.Sim
.onApply { excludeTable.apply() }
.onReset { excludeTable.reset() }
.onIsModified { excludeTable.isModified }
.applyToComponent {
disposable?.let { Disposer.register(it, excludeTable) }
}
}
}
onApply {
@@ -101,6 +179,6 @@ class JavaAutoImportOptions(val project: Project) : UiDslUnnamedConfigurable.Sim
}
fun addExcludePackage(packageName: String) {
excludeTable.addExcludePackage(packageName)
excludeTable.addRow(packageName)
}
}
@@ -0,0 +1,36 @@
// 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;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.SettingsCategory;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.XCollection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@State(name = "JavaIdeCodeInsightSettings", storages = @Storage("codeInsightSettings.xml"), category = SettingsCategory.CODE, perClient = true)
public class JavaIdeCodeInsightSettings implements PersistentStateComponent<JavaIdeCodeInsightSettings> {
@XCollection(propertyElementName = "included-static-names", elementName = "name", valueAttributeName = "")
public List<String> includedAutoStaticNames = new ArrayList<>();
public static JavaIdeCodeInsightSettings getInstance() {
return ApplicationManager.getApplication().getService(JavaIdeCodeInsightSettings.class);
}
@Override
public @Nullable JavaIdeCodeInsightSettings getState() {
return this;
}
@Override
public void loadState(@NotNull JavaIdeCodeInsightSettings state) {
XmlSerializerUtil.copyBean(state, this);
}
}
@@ -28,11 +28,25 @@ public class JavaProjectCodeInsightSettings implements PersistentStateComponent<
@XCollection(propertyElementName = "excluded-names", elementName = "name", valueAttributeName = "")
public List<String> excludedNames = new ArrayList<>();
@XCollection(propertyElementName = "included-static-names", elementName = "name", valueAttributeName = "")
public List<String> includedAutoStaticNames = new ArrayList<>();
public static JavaProjectCodeInsightSettings getSettings(@NotNull Project project) {
return project.getService(JavaProjectCodeInsightSettings.class);
}
public List<String> getAllIncludedAutoStaticNames() {
List<String> names = new ArrayList<>(includedAutoStaticNames);
names.addAll(JavaIdeCodeInsightSettings.getInstance().includedAutoStaticNames);
return names;
}
public boolean isStaticAutoImportClass(@Nullable String name) {
if (name == null) return false;
return includedAutoStaticNames.contains(name) ||
JavaIdeCodeInsightSettings.getInstance().includedAutoStaticNames.contains(name);
}
public boolean isExcluded(@NotNull String name) {
for (String excluded : excludedNames) {
if (nameMatches(name, excluded)) {
@@ -1,11 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
@@ -32,6 +35,16 @@ public class JavaStaticMemberProcessor extends StaticMemberProcessor {
}
}
}
Project project = parameters.getPosition().getProject();
JavaProjectCodeInsightSettings codeInsightSettings = JavaProjectCodeInsightSettings.getSettings(project);
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
GlobalSearchScope resolveScope = parameters.getOriginalFile().getResolveScope();
for (String classNames : codeInsightSettings.getAllIncludedAutoStaticNames()) {
PsiClass aClass = javaPsiFacade.findClass(classNames, resolveScope);
if (aClass != null) {
importMembersOf(aClass);
}
}
}
@Override
@@ -28,12 +28,13 @@ public abstract class StaticMemberProcessor {
private final PsiResolveHelper myResolveHelper;
private boolean myHintShown;
private final boolean myPackagedContext;
private final JavaProjectCodeInsightSettings codeInsightSettings;
protected StaticMemberProcessor(@NotNull PsiElement position) {
myPosition = position;
myProject = myPosition.getProject();
myResolveHelper = JavaPsiFacade.getInstance(myProject).getResolveHelper();
myPackagedContext = JavaCompletionUtil.inSomePackage(position);
codeInsightSettings = JavaProjectCodeInsightSettings.getSettings(position.getProject());
}
public void importMembersOf(@NotNull PsiClass psiClass) {
@@ -60,7 +61,12 @@ public abstract class StaticMemberProcessor {
if (member instanceof PsiMethod && !classesToSkip.add(containingClass)) return;
if(!additionalFilter(member)) return;
boolean shouldImport = myStaticImportedClasses.contains(containingClass);
showHint(shouldImport);
if (!shouldImport && codeInsightSettings.isStaticAutoImportClass(containingClass.getQualifiedName())) {
shouldImport = true;
}
else {
showHint(shouldImport);
}
LookupElement item = member instanceof PsiMethod ? createItemWithOverloads((PsiMethod)member, containingClass, shouldImport) :
member instanceof PsiField ? createLookupElement(member, containingClass, shouldImport) :
null;
@@ -3,6 +3,7 @@ package com.intellij.psi.impl.source.codeStyle;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.ImportFilter;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightVisitorImpl;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.jsp.JspSpiUtil;
@@ -103,7 +104,8 @@ public final class ImportHelper {
Map<String, Boolean> classesOrPackagesToImportOnDemand = new HashMap<>();
List<PsiImportModuleStatement> previousModuleStatements = collectModuleImports(file, mySettings);
Map<String, PsiImportModuleStatement> moduleStatementMap = collectNamesImportedByModules(file, previousModuleStatements, resultList);
collectOnDemandImports(resultList, mySettings, classesOrPackagesToImportOnDemand, moduleStatementMap);
JavaProjectCodeInsightSettings javaProjectCodeInsightSettings = JavaProjectCodeInsightSettings.getSettings(file.getProject());
collectOnDemandImports(resultList, mySettings, javaProjectCodeInsightSettings, classesOrPackagesToImportOnDemand, moduleStatementMap);
MultiMap<String, String> conflictingMemberNames = new MultiMap<>();
for (Import anImport : resultList) {
@@ -211,7 +213,8 @@ public final class ImportHelper {
}
public static void collectOnDemandImports(@NotNull List<Import> resultList,
@NotNull JavaCodeStyleSettings settings,
@NotNull JavaCodeStyleSettings javaCodeStyleSettings,
@NotNull JavaProjectCodeInsightSettings javaProjectCodeInsightSettings,
@NotNull Map<String, Boolean> outClassesOrPackagesToImportOnDemand,
@NotNull Map<String, PsiImportModuleStatement> moduleStatementMap) {
Object2IntMap<String> packageToCountMap = new Object2IntOpenHashMap<>();
@@ -225,12 +228,13 @@ public final class ImportHelper {
}
classToCountMap.forEach((className, count) -> {
if (isToUseImportOnDemand(className, count, true, settings)) {
if (isToUseImportOnDemand(className, count, true, javaCodeStyleSettings) ||
javaProjectCodeInsightSettings.isStaticAutoImportClass(className)) {
outClassesOrPackagesToImportOnDemand.put(className, true);
}
});
packageToCountMap.forEach((packageName, count) -> {
if (isToUseImportOnDemand(packageName, count, false, settings)) {
if (isToUseImportOnDemand(packageName, count, false, javaCodeStyleSettings)) {
outClassesOrPackagesToImportOnDemand.put(packageName, false);
}
});
@@ -930,7 +934,7 @@ public final class ImportHelper {
return maxSpace;
}
private static boolean isToUseImportOnDemand(@NotNull String packageName,
private static boolean isToUseImportOnDemand(@NotNull String packageOrClassName,
int classCount,
boolean isStaticImportNeeded,
@NotNull JavaCodeStyleSettings settings) {
@@ -938,9 +942,9 @@ public final class ImportHelper {
int limitCount = isStaticImportNeeded ? settings.NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND :
settings.CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND;
if (classCount >= limitCount) return true;
if (packageName.isEmpty()) return false;
if (packageOrClassName.isEmpty()) return false;
PackageEntryTable table = settings.PACKAGES_TO_USE_IMPORT_ON_DEMAND;
return table.contains(packageName);
return table.contains(packageOrClassName);
}
private static int findEntryIndex(@NotNull String packageName, boolean isStatic, boolean isModule, PackageEntry @NotNull [] entries) {
@@ -2,6 +2,7 @@
package com.intellij.psi.impl.source.codeStyle;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.codeInspection.dataFlow.MethodContract;
@@ -133,6 +134,13 @@ public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager {
return ImportHelper.hasConflictingOnStaticDemandImport(file, psiClass, referenceName);
}
@Override
public boolean isStaticAutoImportClass(@Nullable String fqn) {
if (fqn == null) return false;
JavaProjectCodeInsightSettings settings = JavaProjectCodeInsightSettings.getSettings(myProject);
return settings.isStaticAutoImportClass(fqn);
}
@Override
public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass) {
return new ImportHelper(JavaCodeStyleSettings.getInstance(file)).addImport(file, refClass);
@@ -4,10 +4,12 @@ package com.intellij.psi.impl.source.codeStyle;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.codeInsight.intention.impl.AddOnDemandStaticImportAction;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.ReferenceAdjuster;
import com.intellij.psi.impl.PsiImplUtil;
@@ -15,10 +17,12 @@ import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.SourceJavaCodeReference;
import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ImportUtils;
import one.util.streamex.StreamEx;
@@ -40,6 +44,10 @@ public final class JavaReferenceAdjuster implements ReferenceAdjuster {
deQualifyImpl((PsiQualifiedReferenceElement)element);
return element;
}
if (tryAutoStaticallyImport(ref)) {
deQualifyImpl((PsiQualifiedReferenceElement)element);
return element;
}
}
if (elementType == JavaElementType.JAVA_CODE_REFERENCE || incompleteCode ||
parentType == JavaElementType.REFERENCE_EXPRESSION || parentType == JavaElementType.METHOD_REF_EXPRESSION) {
@@ -130,6 +138,31 @@ public final class JavaReferenceAdjuster implements ReferenceAdjuster {
return element;
}
private static boolean tryAutoStaticallyImport(@NotNull PsiReferenceExpression reference) {
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(reference.getProject());
PsiFile file = reference.getContainingFile();
if (!(file instanceof PsiJavaFile javaFile)) return false;
if (reference instanceof PsiMethodReferenceExpression) return false;
String memberName = reference.getReferenceName();
if (memberName == null) return false;
PsiJavaCodeReferenceElement qualifier = ObjectUtils.tryCast(reference.getQualifier(), PsiJavaCodeReferenceElement.class);
if (qualifier == null) return false;
if (GenericsUtil.isGenericReference(reference, qualifier)) return false;
if (PsiTreeUtil.getParentOfType(reference, PsiImportStatementBase.class) != null) return false;
if (!(qualifier.resolve() instanceof PsiClass psiClass)) return false;
String classQualifiedName = psiClass.getQualifiedName();
if (!javaCodeStyleManager.isStaticAutoImportClass(classQualifiedName)) return false;
PsiElement referenceNameElement = qualifier.getReferenceNameElement();
if (referenceNameElement == null) return false;
PsiClass anImport = AddOnDemandStaticImportAction.getClassToPerformStaticImport(referenceNameElement);
if (anImport == null) return false;
if (javaCodeStyleManager.hasConflictingOnDemandImport(javaFile, anImport, memberName)) return false;
PsiImportList importList = javaFile.getImportList();
if (importList == null) return false;
PsiReferenceExpressionImpl.bindToElementViaStaticImport(anImport, memberName, importList);
return true;
}
@Override
public ASTNode process(@NotNull ASTNode element, boolean addImports, boolean incompleteCode, Project project) {
final CodeStyleSettings settings = CodeStyle.getSettings(element.getPsi().getContainingFile());
@@ -24,6 +24,15 @@ public abstract class JavaCodeStyleManager {
public static final int DO_NOT_ADD_IMPORTS = 0x1000;
public static final int INCOMPLETE_CODE = 0x2000;
/**
* Determines whether the specified fully qualified name (FQN) represents
* a class that should be statically auto-imported in the context.
*
* @param fqn the fully qualified name of the target class, must not be null
* @return true if the class is configured to be statically auto-imported, false otherwise
*/
public abstract boolean isStaticAutoImportClass(@Nullable String fqn);
public abstract boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass);
public abstract @NotNull PsiElement shortenClassReferences(@NotNull PsiElement element,
@MagicConstant(flags = {DO_NOT_ADD_IMPORTS, INCOMPLETE_CODE}) int flags) throws IncorrectOperationException;
@@ -22,6 +22,11 @@ import java.util.Set;
import java.util.function.Predicate;
public class CoreJavaCodeStyleManager extends JavaCodeStyleManager {
@Override
public boolean isStaticAutoImportClass(@Nullable String fqn) {
return false;
}
@Override
public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass) {
return false;
@@ -105,8 +105,11 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
String qualifiedName = qualifierClass.getQualifiedName();
List<PsiJavaCodeReferenceElement> refs = getImportsFromClass(importList, qualifiedName);
JavaFileCodeStyleFacade javaCodeStyleSettingsFacade = JavaFileCodeStyleFacade.forContext(importList.getContainingFile());
if (!javaCodeStyleSettingsFacade.isToImportOnDemand(qualifiedName) && refs.size() + 1 < javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand() ||
JavaCodeStyleManager.getInstance(qualifierClass.getProject()).hasConflictingOnDemandImport((PsiJavaFile)importList.getContainingFile(), qualifierClass, staticName)) {
JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(qualifierClass.getProject());
if ((!javaCodeStyleSettingsFacade.isToImportOnDemand(qualifiedName) &&
!javaCodeStyleManager.isStaticAutoImportClass(qualifiedName) &&
refs.size() + 1 < javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand()) ||
javaCodeStyleManager.hasConflictingOnDemandImport((PsiJavaFile)importList.getContainingFile(), qualifierClass, staticName)) {
importList.add(JavaPsiFacade.getElementFactory(qualifierClass.getProject()).createImportStaticStatement(qualifierClass, staticName));
} else {
for (PsiJavaCodeReferenceElement ref : refs) {
@@ -0,0 +1,7 @@
import java.util.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().toli<caret>
}
}
@@ -0,0 +1,9 @@
import java.util.*;
import static org.example.AAA.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().toli<caret>
}
}
@@ -0,0 +1,10 @@
import java.util.*;
import java.util.stream.Collectors;
import static org.example.AAA.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().collect(Collectors.toList());<caret>
}
}
@@ -0,0 +1,9 @@
import java.util.*;
import static java.util.stream.Collectors.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().collect(toList());<caret>
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,9 @@
import java.util.Arrays;
import java.util.List;
class Foo {
void test(List<String> baz) {
Arrays.sort(<caret>);
}
}
@@ -0,0 +1,8 @@
import java.util.List;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,9 @@
import java.util.List;
import static java.util.Arrays.*;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,9 @@
import java.util.List;
import static java.util.Arrays.*;
class Foo {
void test(List<String> baz) {
sort(<caret>);
}
}
@@ -0,0 +1,9 @@
import java.util.List;
import static org.example.AAA.*;
class Foo {
void test(List<String> baz) {
so<caret>
}
}
@@ -0,0 +1,10 @@
import java.util.Arrays;
import java.util.List;
import static org.example.AAA.*;
class Foo {
void test(List<String> baz) {
Arrays.sort(<caret>);
}
}
@@ -0,0 +1,10 @@
import java.util.List;
import static java.util.Arrays.*;
class Foo {
void test(List<String> baz) {
sort(<caret>);
}
}
@@ -0,0 +1,147 @@
// 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.java.codeInsight.completion;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiMethod;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@NeedsIndex.ForStandardLibrary
public class AutoStaticImportCompletionTest extends NormalCompletionTestCase {
@Override
public void tearDown() throws Exception {
try {
JavaProjectCodeInsightSettings.getSettings(getProject()).includedAutoStaticNames.clear();
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
super.tearDown();
}
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_21;
}
@Override
public final String getBasePath() {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/autoStaticImport/";
}
public void testSimpleStaticAutoImport() {
addStaticAutoImport("java.util.Arrays");
configure();
type("r");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
@NeedsIndex.Full
public void testSimpleStaticAutoImportWithConflict() {
addStaticAutoImport("java.util.Arrays");
myFixture.addClass("""
package org.example;
public final class AAA {
public static <T> List<T> sort(T[] args){
return null;
}
}
""");
configure();
type("r");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
public void testSimpleStaticAutoImportWithAlreadyImported() {
addStaticAutoImport("java.util.Arrays");
configure();
type("r");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
public void testNoAutoImport() {
configure();
type("r");
LookupElement[] elements = myFixture.getLookupElements();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNull(element);
}
@NeedsIndex.Full
public void testChainedAutoStaticImport() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_1_8, () -> {
addStaticAutoImport("java.util.stream.Collectors");
configureByTestName();
selectItem(ContainerUtil.find(myItems, it -> it.getLookupString().contains("toList")));
checkResult();
});
}
@NeedsIndex.Full
public void testChainedAutoStaticImportWithConflict() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_1_8, () -> {
addStaticAutoImport("java.util.stream.Collectors");
myFixture.addClass("""
package org.example;
public final class AAA {
public static <T> List<T> toList(){
return null;
}
}
""");
configureByTestName();
selectItem(ContainerUtil.find(myItems, it -> it.getLookupString().contains("toList")));
checkResult();
});
}
public void testNoSimpleStaticAutoImport() {
configure();
LookupElement[] elements = myFixture.completeBasic();
LookupElement element = ContainerUtil.find(elements, e ->
e.getLookupString().equals("sort") &&
e.getPsiElement() instanceof PsiMethod method &&
method.getContainingClass().getQualifiedName().equals("java.util.Arrays"));
assertNotNull(element);
selectItem(element);
checkResult();
}
private void addStaticAutoImport(@NotNull String name) {
JavaProjectCodeInsightSettings.getSettings(getProject()).includedAutoStaticNames.add(name);
}
}
@@ -236,9 +236,14 @@ error.text.this.is.not.a.valid.java.class.name=This is not a valid Java class na
exclude.0.from.completion=Exclude ''{0}'' from completion
exclude.accessors=&Exclude Accessors
exclude.from.completion.group=Exclude from auto-import and completion:
auto.static.import.completion.group=Include auto-import of static members in completion:
auto.static.import.comment=Add a class to include its static members
auto.static.import.comment.2=in auto-import and completion
auto.static.import.class=Class
auto.static.import.scope=Scope
exclude.from.imports.no.exclusions=Add a class, package, or member
exclude.from.imports.no.exclusions.2=to exclude from auto-import and completion
exclude.table.mask=Class, package, or member
exclude.table.mask=Class, Package, or Member
exclude.table.scope.column=Scope
export.to.html.generate.hyperlinks.checkbox=Generate &hyperlinks to classes
external.annotation.prompt=External Annotation Prompt
@@ -1,6 +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.javaFX.fxml.codeInsight;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.lang.ASTNode;
@@ -47,9 +48,10 @@ public final class JavaFxImportsOptimizer implements ImportOptimizer {
collectNamesToImport(names, demandedForNested, (XmlFile)file);
names.sort((o1, o2) -> StringUtil.compare(o1.name(), o2.name(), true));
final JavaCodeStyleSettings settings = JavaCodeStyleSettings.getInstance(file);
JavaProjectCodeInsightSettings javaProjectCodeInsightSettings = JavaProjectCodeInsightSettings.getSettings(file.getProject());
final @NotNull List<ImportHelper.Import> sortedNames = ImportHelper.sortItemsAccordingToSettings(names, settings);
final Map<String, Boolean> onDemand = new HashMap<>();
ImportHelper.collectOnDemandImports(sortedNames, settings, onDemand, new HashMap<>());
ImportHelper.collectOnDemandImports(sortedNames, settings, javaProjectCodeInsightSettings, onDemand, new HashMap<>());
for (String s : demandedForNested) {
onDemand.put(s, false);
}