PY-88015 Disable manual dependency editing for pyproject.toml-based modules

- Remove PYPROJECT_TOML_PENDING_KEY; use settings::usePyprojectToml directly
- Disable dependencies list per-module based on Module.isPyProjectTomlBased
- Fix Apply button staying active after applying changes
- Sort module list alphabetically, apply only changed dependencies


(cherry picked from commit cd759107b7bec10003f0977a1f1f3f0973d8c429)

IJ-MR-193755

GitOrigin-RevId: fc78dc32efedea640a2cf883d6424d48e53042e1
This commit is contained in:
Vitaly Legchilkin
2026-02-28 00:13:33 +00:00
committed by intellij-monorepo-bot
parent e010410829
commit ea8384f976
3 changed files with 29 additions and 56 deletions
@@ -24,10 +24,7 @@ internal class PyContentEntriesModuleConfigurable(private val module: Module) :
private var modifiableModel: ModifiableRootModel? = null
private var editor: PyContentEntriesEditor? = null
private val isPyProjectTomlManaged: Boolean
get() {
val pending = module.project.getUserData(PythonContentEntriesConfigurable.PYPROJECT_TOML_PENDING_KEY)
return pending ?: PyProjectModelSettings.getInstance(module.project).usePyprojectToml
}
get() = PyProjectModelSettings.getInstance(module.project).usePyprojectToml
override fun getDisplayName(): String = PyBundle.message("configurable.PyContentEntriesModuleConfigurable.display.name")
@@ -10,7 +10,7 @@ import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.python.pyproject.model.PyProjectModelSettings;
import com.intellij.python.pyproject.model.api.PyProjectTomlAPIKt;
import com.intellij.ui.AncestorListenerAdapter;
import com.intellij.ui.CheckBoxList;
import com.intellij.ui.ToolbarDecorator;
@@ -30,10 +30,13 @@ import javax.swing.event.AncestorEvent;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
@@ -41,7 +44,7 @@ public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
private List<Module> myInitialDependencies;
private final JPanel myMainPanel;
private final JPanel myListHolderPanel;
private final CheckBoxList<Module> myDependenciesList;
private final CheckBoxList<Module> myDependenciesList = new CheckBoxList<>();
private final JBLabel myTitleLabel;
public PyModuleDependenciesConfigurable(Module module) {
@@ -65,7 +68,6 @@ public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
null, null, null, 0, false));
}
myDependenciesList = new CheckBoxList<>();
resetModel();
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myDependenciesList,
new EditableListModelDecorator(
@@ -81,9 +83,12 @@ public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
});
}
private boolean isPyProjectTomlManaged() {
return PyProjectTomlAPIKt.isPyProjectTomlBased(myModule);
}
private void updateEnabledState() {
Boolean pending = myModule.getProject().getUserData(PythonContentEntriesConfigurable.PYPROJECT_TOML_PENDING_KEY);
boolean managed = pending != null ? pending : PyProjectModelSettings.getInstance(myModule.getProject()).getUsePyprojectToml();
boolean managed = isPyProjectTomlManaged();
myDependenciesList.setEnabled(!managed);
if (managed) {
myTitleLabel.setText(PyBundle.message("python.pyproject.toml.dependencies.managed.hint"));
@@ -143,18 +148,15 @@ public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
public JComponent $$$getRootComponent$$$() { return myMainPanel; }
private void resetModel() {
myInitialDependencies = Arrays.asList(ModuleRootManager.getInstance(myModule).getDependencies());
List<Module> possibleDependencies = new ArrayList<>(myInitialDependencies);
for (Module otherModule : ModuleManager.getInstance(myModule.getProject()).getModules()) {
if (!possibleDependencies.contains(otherModule) && otherModule != myModule) {
possibleDependencies.add(otherModule);
}
}
myDependenciesList.setItems(possibleDependencies, module -> module.getName());
myDependenciesList.setBorder(null);
for (Module dependency : myInitialDependencies) {
myDependenciesList.setItemSelected(dependency, true);
}
List<Module> otherModules = Arrays.stream(ModuleManager.getInstance(myModule.getProject()).getModules())
.filter(m -> m != myModule)
.sorted(Comparator.comparing(Module::getName, String.CASE_INSENSITIVE_ORDER))
.toList();
Set<Module> dependencies = Set.of(ModuleRootManager.getInstance(myModule).getDependencies());
myDependenciesList.clear();
otherModules.forEach(m -> myDependenciesList.addItem(m, m.getName(), dependencies.contains(m)));
myInitialDependencies = myDependenciesList.getCheckedItems();
}
@Override
@@ -164,38 +166,24 @@ public class PyModuleDependenciesConfigurable implements UnnamedConfigurable {
@Override
public boolean isModified() {
return !collectDependencies().equals(myInitialDependencies);
}
private List<Module> collectDependencies() {
List<Module> result = new ArrayList<>();
for (int i = 0; i < myDependenciesList.getItemsCount(); i++) {
Module module = myDependenciesList.getItemAt(i);
if (myDependenciesList.isItemSelected(module)) {
result.add(module);
}
}
return result;
return !isPyProjectTomlManaged() && !myDependenciesList.getCheckedItems().equals(myInitialDependencies);
}
@Override
public void apply() throws ConfigurationException {
List<Module> dependencies = myDependenciesList.getCheckedItems();
Set<Module> desired = new HashSet<>(dependencies);
ApplicationManager.getApplication().runWriteAction(() -> {
ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel();
List<ModuleOrderEntry> entries = new ArrayList<>();
for (OrderEntry entry : model.getOrderEntries()) {
if (entry instanceof ModuleOrderEntry) {
entries.add((ModuleOrderEntry)entry);
if (entry instanceof ModuleOrderEntry moe && !desired.remove(moe.getModule())) {
model.removeOrderEntry(entry);
}
}
for (ModuleOrderEntry entry : entries) {
model.removeOrderEntry(entry);
}
for (Module module : collectDependencies()) {
model.addModuleOrderEntry(module);
}
desired.forEach(model::addModuleOrderEntry);
model.commit();
});
myInitialDependencies = dependencies;
}
@Override
@@ -7,7 +7,6 @@ import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ui.configuration.PlatformContentEntriesConfigurable
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.util.Key
import com.intellij.python.pyproject.model.PyProjectModelSettings
import com.intellij.python.pyproject.model.PyProjectModelSettings.FeatureState.ASK
import com.intellij.python.pyproject.model.PyProjectModelSettings.FeatureState.OFF
@@ -28,11 +27,6 @@ class PythonContentEntriesConfigurable(project: Project) : ModuleAwareProjectCon
private var pyprojectPanel: DialogPanel? = null
companion object {
@JvmField
val PYPROJECT_TOML_PENDING_KEY: Key<Boolean> = Key.create("PythonContentEntriesConfigurable.pendingUsePyprojectToml")
}
override fun createModuleConfigurable(module: Module): Configurable {
if (PlatformUtils.isPyCharmCommunity()) {
return PlatformContentEntriesConfigurable(module, JavaSourceRootType.SOURCE)
@@ -53,11 +47,8 @@ class PythonContentEntriesConfigurable(project: Project) : ModuleAwareProjectCon
row {
checkBox(PyBundle.message("python.pyproject.toml.based.project.model"))
.bindSelected(settings::usePyprojectToml)
.applyToComponent {
addActionListener { project.putUserData(PYPROJECT_TOML_PENDING_KEY, isSelected) }
}
.contextHelp(PyBundle.message("python.pyproject.toml.based.project.model.comment"))
}
}.bottomGap(com.intellij.ui.dsl.builder.BottomGap.SMALL)
super.createComponent()?.let { parentComponent ->
row {
cell(parentComponent).align(Align.FILL)
@@ -74,18 +65,15 @@ class PythonContentEntriesConfigurable(project: Project) : ModuleAwareProjectCon
override fun apply() {
pyprojectPanel?.apply()
project.putUserData(PYPROJECT_TOML_PENDING_KEY, null)
super.apply()
}
override fun reset() {
pyprojectPanel?.reset()
project.putUserData(PYPROJECT_TOML_PENDING_KEY, null)
super.reset()
}
override fun disposeUIResources() {
project.putUserData(PYPROJECT_TOML_PENDING_KEY, null)
pyprojectPanel = null
super.disposeUIResources()
}