diff --git a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseResourceAction.java b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseResourceAction.java index fd6aee00f48..a11209a453b 100644 --- a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseResourceAction.java +++ b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseResourceAction.java @@ -16,7 +16,6 @@ package org.eclipse.ui.actions; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IFile; @@ -229,14 +228,14 @@ protected boolean updateSelection(IStructuredSelection s) { // don't call super since we want to enable if open project is selected. setText(defaultText); setToolTipText(defaultToolTip); - if (!selectionIsOfType(IResource.PROJECT)) { + List projects = getSelectedResources().stream() + .filter(IProject.class::isInstance).map(IProject.class::cast).toList(); + if (projects.isEmpty()) { return false; } boolean hasOpenProjects = false; - Iterator resources = getSelectedResources().iterator(); - while (resources.hasNext()) { - IProject currentResource = (IProject) resources.next(); + for (IProject currentResource : projects) { if (currentResource.isOpen()) { if (hasOpenProjects) { setText(pluralText); @@ -258,7 +257,7 @@ public synchronized void resourceChanged(IResourceChangeEvent event) { // Warning: code duplicated in OpenResourceAction List sel = getSelectedResources(); // don't bother looking at delta if selection not applicable - if (selectionIsOfType(IResource.PROJECT)) { + if (sel.stream().anyMatch(IProject.class::isInstance)) { IResourceDelta delta = event.getDelta(); if (delta != null) { IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); diff --git a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseUnrelatedProjectsAction.java b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseUnrelatedProjectsAction.java index 54eebcb244b..711066642d9 100644 --- a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseUnrelatedProjectsAction.java +++ b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/CloseUnrelatedProjectsAction.java @@ -261,7 +261,8 @@ protected List getSelectedResources() { @Override public void resourceChanged(IResourceChangeEvent event) { // don't bother looking at delta if selection not applicable - if (selectionIsOfType(IResource.PROJECT)) { + List selectedResources = super.getSelectedResources(); + if (selectedResources.stream().anyMatch(IProject.class::isInstance)) { IResourceDelta delta = event.getDelta(); if (delta != null) { IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); diff --git a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/OpenResourceAction.java b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/OpenResourceAction.java index cbe3618076f..635fcbe7830 100644 --- a/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/OpenResourceAction.java +++ b/bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/OpenResourceAction.java @@ -190,7 +190,7 @@ public void resourceChanged(IResourceChangeEvent event) { // Warning: code duplicated in CloseResourceAction List sel = getSelectedResources(); // don't bother looking at delta if selection not applicable - if (selectionIsOfType(IResource.PROJECT)) { + if (sel.stream().anyMatch(IProject.class::isInstance)) { IResourceDelta delta = event.getDelta(); if (delta != null) { IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); @@ -304,13 +304,15 @@ protected boolean updateSelection(IStructuredSelection s) { // selected. setText(IDEWorkbenchMessages.OpenResourceAction_text); setToolTipText(IDEWorkbenchMessages.OpenResourceAction_toolTip); - if (!selectionIsOfType(IResource.PROJECT)) { + List projects = getSelectedResources().stream() + .filter(IProject.class::isInstance).map(IProject.class::cast).toList(); + if (projects.isEmpty()) { return false; } boolean hasClosedProjects = false; - for (IResource currentResource : getSelectedResources()) { - if (!((IProject) currentResource).isOpen()) { + for (IProject currentResource : projects) { + if (!currentResource.isOpen()) { if (hasClosedProjects) { setText(IDEWorkbenchMessages.OpenResourceAction_text_plural); setToolTipText(IDEWorkbenchMessages.OpenResourceAction_toolTip_plural); diff --git a/bundles/org.eclipse.ui.navigator.resources/plugin.xml b/bundles/org.eclipse.ui.navigator.resources/plugin.xml index 7eec1464661..d6007f86353 100644 --- a/bundles/org.eclipse.ui.navigator.resources/plugin.xml +++ b/bundles/org.eclipse.ui.navigator.resources/plugin.xml @@ -237,13 +237,8 @@ class="org.eclipse.ui.internal.navigator.resources.actions.ResourceMgmtActionProvider" id="org.eclipse.ui.navigator.resources.ResourceMgmtActions"> - - - - - - - + + diff --git a/tests/org.eclipse.ui.tests.navigator/src/org/eclipse/ui/tests/navigator/resources/ResourceMgmtActionProviderTests.java b/tests/org.eclipse.ui.tests.navigator/src/org/eclipse/ui/tests/navigator/resources/ResourceMgmtActionProviderTests.java index 128451a678b..9954e56552d 100644 --- a/tests/org.eclipse.ui.tests.navigator/src/org/eclipse/ui/tests/navigator/resources/ResourceMgmtActionProviderTests.java +++ b/tests/org.eclipse.ui.tests.navigator/src/org/eclipse/ui/tests/navigator/resources/ResourceMgmtActionProviderTests.java @@ -118,6 +118,67 @@ public void testFillContextMenu_openProjectNoBuilderSelection() throws CoreExcep } } + /** + * Test for a file selected together with an open project: Close Project must + * be both present and enabled. Regression test for the bug where + * selectionIsOfType(PROJECT) disabled the action for any mixed selection. + * + * @throws CoreException + */ + @Test + public void testFillContextMenu_fileAndOpenProjectSelection_closeProjectEnabled() throws CoreException { + // _p1 is already open; _project has a known 'src' folder + files + IProject openProj = ResourcesPlugin.getWorkspace().getRoot().getProject("Test"); + openProj.open(null); + // Select a file alongside a project (the typical Ctrl+A expanded scenario) + ResourceMgmtActionProvider provider = providerForObjects(_p1, openProj.getFile(".project")); + provider.fillContextMenu(manager); + assertTrue(menuHasContribution("org.eclipse.ui.CloseResourceAction"), + "Close Project should be in the menu"); + assertTrue(isMenuContributionEnabled("org.eclipse.ui.CloseResourceAction"), + "Close Project should be enabled when open projects are in the selection"); + assertTrue(menuHasContribution("org.eclipse.ui.CloseUnrelatedProjectsAction"), + "Close Unrelated Projects should be in the menu"); + assertTrue(isMenuContributionEnabled("org.eclipse.ui.CloseUnrelatedProjectsAction"), + "Close Unrelated Projects should be enabled when open projects are in the selection"); + } + + /** + * Test for mixed selection: an open project alongside a non-adaptable element + * (e.g. a working set header from Ctrl+A in Project Explorer). Close Project + * and Refresh must still appear — regression test for issue #3790. + * + * @throws CoreException + */ + @Test + public void testFillContextMenu_mixedSelectionOpenProjectAndNonAdaptableElement() throws CoreException { + IProject openProj = ResourcesPlugin.getWorkspace().getRoot().getProject("Test"); + openProj.open(null); + // Plain Object does not implement IAdaptable, so it is never resolved to a + // project — it counts as a non-project element in the selection. + Object nonResource = new Object(); + ResourceMgmtActionProvider provider = providerForObjects(openProj, nonResource); + provider.fillContextMenu(manager); + checkMenuHasCorrectContributions(false, true, false, true, true); + } + + /** + * Test for a fully expanded selection: two open projects plus child resources + * from both (simulating Ctrl+A when both projects are expanded). Close Project + * must still appear for the open projects in the selection. + * + * @throws CoreException + */ + @Test + public void testFillContextMenu_twoOpenProjectsWithChildResourcesSelection() throws CoreException { + // _p1 and _p2 are already opened in setUp() + IFolder srcFolder = _project.getFolder("src"); + IFolder binFolder = _project.getFolder("bin"); + ResourceMgmtActionProvider provider = providerForObjects(_p1, _p2, srcFolder, binFolder); + provider.fillContextMenu(manager); + checkMenuHasCorrectContributions(false, true, false, true, true); + } + /** * Test for 'open project' that doesn't have a builder attached - only 'open * project' should be disabled @@ -158,6 +219,19 @@ public void testFillContextMenu_openProjectWithBuilderSelection() throws CoreExc } } + /* + * Return a provider for a mixed/arbitrary selection (Object[]) + */ + private ResourceMgmtActionProvider providerForObjects(Object... selectedElements) { + ICommonActionExtensionSite cfg = new CommonActionExtensionSite("NA", "NA", + CommonViewerSiteFactory.createCommonViewerSite(_commonNavigator.getViewSite()), + (NavigatorContentService) _contentService, _viewer); + ResourceMgmtActionProvider provider = new ResourceMgmtActionProvider(); + provider.setContext(new ActionContext(new StructuredSelection(selectedElements))); + provider.init(cfg); + return provider; + } + /* * Return a provider, given the selected navigator items */ @@ -206,4 +280,16 @@ private boolean menuHasContribution(String contribution) { return false; } + /* + * Check whether the named menu entry is enabled + */ + private boolean isMenuContributionEnabled(String contribution) { + for (IContributionItem thisItem : manager.getItems()) { + if (thisItem.getId() != null && thisItem.getId().equals(contribution)) { + return thisItem.isEnabled(); + } + } + return false; + } + }