| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Andrey Loskutov <loskutov@gmx.de> - Bug 41431, 462760, 461786 |
| * Lucas Bullen (Red Hat Inc.) - Bug 522096 - "Close Projects" on working set |
| *******************************************************************************/ |
| package org.eclipse.ui.actions; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceRuleFactory; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; |
| import org.eclipse.core.resources.mapping.ResourceChangeValidator; |
| import org.eclipse.core.runtime.Adapters; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.MultiRule; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.window.IShellProvider; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorReference; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.ide.IDE; |
| import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; |
| import org.eclipse.ui.internal.ide.IIDEHelpContextIds; |
| |
| /** |
| * Standard action for closing the currently selected project(s). |
| * <p> |
| * This class may be instantiated; it is not intended to be subclassed. |
| * </p> |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class CloseResourceAction extends WorkspaceAction implements IResourceChangeListener { |
| /** |
| * The id of this action. |
| */ |
| public static final String ID = PlatformUI.PLUGIN_ID + ".CloseResourceAction"; //$NON-NLS-1$ |
| |
| private final String defaultText; |
| private final String defaultToolTip; |
| private final String pluralText; |
| private final String pluralTooltip; |
| |
| private String[] modelProviderIds; |
| |
| /** |
| * Creates a new action. |
| * |
| * @param shell the shell for any dialogs |
| * @deprecated See {@link #CloseResourceAction(IShellProvider)} |
| */ |
| @Deprecated |
| public CloseResourceAction(Shell shell) { |
| this(() -> shell); |
| } |
| |
| /** |
| * Override super constructor to allow subclass to |
| * override with unique text. |
| * @deprecated See {@link #CloseResourceAction(IShellProvider, String)} |
| */ |
| @Deprecated |
| protected CloseResourceAction(Shell shell, String text) { |
| this(() -> shell, text); |
| } |
| |
| /** |
| * Create the new action. |
| * |
| * @param provider |
| * the shell provider for any dialogs |
| * @since 3.4 |
| */ |
| public CloseResourceAction(IShellProvider provider) { |
| this(provider, IDEWorkbenchMessages.CloseResourceAction_text); |
| initAction(); |
| } |
| |
| /** |
| * Provide text to the action. |
| * |
| * @param provider |
| * the shell provider for any dialogs |
| * @param text |
| * label for action when only a singular selection is made |
| * @since 3.4 |
| */ |
| protected CloseResourceAction(IShellProvider provider, String text) { |
| this(provider, text, IDEWorkbenchMessages.CloseResourceAction_toolTip, |
| IDEWorkbenchMessages.CloseResourceAction_text_plural, |
| IDEWorkbenchMessages.CloseResourceAction_toolTip_plural); |
| } |
| |
| /** |
| * Provide text to the action. |
| * |
| * @param provider |
| * the shell provider for any dialogs |
| * @param text |
| * label for action when only a singular selection is made |
| * @param tooltip |
| * tooltip text for action when only a singular selection is made |
| * @param textPlural |
| * label for action when selection contains multiple elements |
| * @param tooltipPlural |
| * tooltip text for action when selection contains multiple elements |
| * @since 3.14 |
| */ |
| protected CloseResourceAction(IShellProvider provider, String text, String tooltip, String textPlural, |
| String tooltipPlural) { |
| super(provider, text); |
| defaultText = text; |
| defaultToolTip = tooltip; |
| pluralText = textPlural; |
| pluralTooltip = tooltipPlural; |
| } |
| |
| private void initAction() { |
| setId(ID); |
| setToolTipText(IDEWorkbenchMessages.CloseResourceAction_toolTip); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IIDEHelpContextIds.CLOSE_RESOURCE_ACTION); |
| } |
| |
| @Override |
| protected String getOperationMessage() { |
| if (getActionResources().size() > 1) |
| return IDEWorkbenchMessages.CloseResourceAction_operationMessage_plural; |
| return IDEWorkbenchMessages.CloseResourceAction_operationMessage; |
| } |
| |
| @Override |
| protected String getProblemsMessage() { |
| return IDEWorkbenchMessages.CloseResourceAction_problemMessage; |
| } |
| |
| @Override |
| protected String getProblemsTitle() { |
| return IDEWorkbenchMessages.CloseResourceAction_title; |
| } |
| |
| @Override |
| protected void invokeOperation(IResource resource, IProgressMonitor monitor) throws CoreException { |
| ((IProject) resource).close(monitor); |
| } |
| |
| /** |
| * The implementation of this <code>WorkspaceAction</code> method |
| * method saves and closes the resource's dirty editors before closing |
| * it. |
| */ |
| @Override |
| public void run() { |
| // Get the items to close. |
| List<? extends IResource> projects = getSelectedResources(); |
| if (projects == null || projects.isEmpty()) { |
| // no action needs to be taken since no projects are selected |
| return; |
| } |
| |
| final IResource[] projectArray = projects.toArray(new IResource[projects.size()]); |
| |
| if (!IDE.saveAllEditors(projectArray, true)) { |
| return; |
| } |
| if (!validateClose()) { |
| return; |
| } |
| |
| closeMatchingEditors(projects, false); |
| |
| //be conservative and include all projects in the selection - projects |
| //can change state between now and when the job starts |
| ISchedulingRule rule = null; |
| IResourceRuleFactory factory = ResourcesPlugin.getWorkspace().getRuleFactory(); |
| for (IResource element : projectArray) { |
| IProject project = (IProject) element; |
| rule = MultiRule.combine(rule, factory.modifyRule(project)); |
| } |
| runInBackground(rule); |
| } |
| |
| @Override |
| protected boolean shouldPerformResourcePruning() { |
| return false; |
| } |
| |
| /** |
| * The <code>CloseResourceAction</code> implementation of this |
| * <code>SelectionListenerAction</code> method ensures that this action is |
| * enabled only if one of the selections is an open project. |
| */ |
| @Override |
| 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)) { |
| return false; |
| } |
| |
| boolean hasOpenProjects = false; |
| Iterator<? extends IResource> resources = getSelectedResources().iterator(); |
| while (resources.hasNext()) { |
| IProject currentResource = (IProject) resources.next(); |
| if (currentResource.isOpen()) { |
| if (hasOpenProjects) { |
| setText(pluralText); |
| setToolTipText(pluralTooltip); |
| break; |
| } |
| hasOpenProjects = true; |
| } |
| } |
| return hasOpenProjects; |
| } |
| |
| /** |
| * Handles a resource changed event by updating the enablement |
| * if one of the selected projects is opened or closed. |
| */ |
| @Override |
| public synchronized void resourceChanged(IResourceChangeEvent event) { |
| // Warning: code duplicated in OpenResourceAction |
| List<? extends IResource> sel = getSelectedResources(); |
| // don't bother looking at delta if selection not applicable |
| if (selectionIsOfType(IResource.PROJECT)) { |
| IResourceDelta delta = event.getDelta(); |
| if (delta != null) { |
| IResourceDelta[] projDeltas = delta.getAffectedChildren(IResourceDelta.CHANGED); |
| for (IResourceDelta projDelta : projDeltas) { |
| if ((projDelta.getFlags() & IResourceDelta.OPEN) != 0) { |
| if (sel.contains(projDelta.getResource())) { |
| selectionChanged(getStructuredSelection()); |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| @Override |
| protected synchronized List<? extends IResource> getSelectedResources() { |
| return super.getSelectedResources(); |
| } |
| |
| @Override |
| protected synchronized List<?> getSelectedNonResources() { |
| return super.getSelectedNonResources(); |
| } |
| |
| /** |
| * Returns the model provider ids that are known to the client |
| * that instantiated this operation. |
| * |
| * @return the model provider ids that are known to the client |
| * that instantiated this operation. |
| * @since 3.2 |
| */ |
| public String[] getModelProviderIds() { |
| return modelProviderIds; |
| } |
| |
| /** |
| * Sets the model provider ids that are known to the client |
| * that instantiated this operation. Any potential side effects |
| * reported by these models during validation will be ignored. |
| * |
| * @param modelProviderIds the model providers known to the client |
| * who is using this operation. |
| * @since 3.2 |
| */ |
| public void setModelProviderIds(String[] modelProviderIds) { |
| this.modelProviderIds = modelProviderIds; |
| } |
| |
| /** |
| * Validates the operation against the model providers. |
| * |
| * @return whether the operation should proceed |
| */ |
| private boolean validateClose() { |
| IResourceChangeDescriptionFactory factory = ResourceChangeValidator.getValidator().createDeltaFactory(); |
| List<? extends IResource> resources = getActionResources(); |
| for (IResource resource : resources) { |
| if (resource instanceof IProject) { |
| IProject project = (IProject) resource; |
| factory.close(project); |
| } |
| } |
| String message; |
| if (resources.size() == 1) { |
| message = NLS.bind(IDEWorkbenchMessages.CloseResourceAction_warningForOne, resources.get(0).getName()); |
| } else { |
| message = IDEWorkbenchMessages.CloseResourceAction_warningForMultiple; |
| } |
| return IDE.promptToConfirm(getShell(), IDEWorkbenchMessages.CloseResourceAction_confirm, message, factory.getDelta(), getModelProviderIds(), false /* no need to syncExec */); |
| } |
| |
| /** |
| * Tries to find opened editors matching given resource roots. The editors |
| * will be closed without confirmation and only if the editor resource does |
| * not exists anymore. |
| * |
| * @param resourceRoots |
| * non null array with deleted resource tree roots |
| * @param deletedOnly |
| * true to close only editors on resources which do not exist |
| */ |
| static void closeMatchingEditors(final List<? extends IResource> resourceRoots, final boolean deletedOnly) { |
| if (resourceRoots.isEmpty()) { |
| return; |
| } |
| Runnable runnable = () -> SafeRunner.run(new SafeRunnable(IDEWorkbenchMessages.ErrorOnCloseEditors) { |
| @Override |
| public void run() { |
| IWorkbenchWindow w = getActiveWindow(); |
| if (w != null) { |
| List<IEditorReference> toClose = getMatchingEditors(resourceRoots, w, deletedOnly); |
| if (toClose.isEmpty()) { |
| return; |
| } |
| closeEditors(toClose, w); |
| } |
| } |
| }); |
| BusyIndicator.showWhile(PlatformUI.getWorkbench().getDisplay(), runnable); |
| } |
| |
| private static IWorkbenchWindow getActiveWindow() { |
| IWorkbenchWindow w = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| if (w == null) { |
| IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows(); |
| if (windows.length > 0) { |
| w = windows[0]; |
| } |
| } |
| return w; |
| } |
| |
| private static List<IEditorReference> getMatchingEditors(final List<? extends IResource> resourceRoots, |
| IWorkbenchWindow w, boolean deletedOnly) { |
| List<IEditorReference> toClose = new ArrayList<>(); |
| IEditorReference[] editors = getEditors(w); |
| for (IEditorReference ref : editors) { |
| IResource resource = getAdapter(ref); |
| // only collect editors for non existing resources |
| if (resource != null && belongsTo(resourceRoots, resource)) { |
| if (deletedOnly && resource.exists()) { |
| continue; |
| } |
| toClose.add(ref); |
| } |
| } |
| return toClose; |
| } |
| |
| private static IEditorReference[] getEditors(IWorkbenchWindow w) { |
| if (w != null) { |
| IWorkbenchPage page = w.getActivePage(); |
| if (page != null) { |
| return page.getEditorReferences(); |
| } |
| } |
| return new IEditorReference[0]; |
| } |
| |
| private static IResource getAdapter(IEditorReference ref) { |
| IEditorInput input; |
| try { |
| input = ref.getEditorInput(); |
| } catch (PartInitException e) { |
| // ignore if factory can't restore input, see bug 461786 |
| return null; |
| } |
| // here we can only guess how the input might be related to a resource |
| IFile adapter = Adapters.adapt(input, IFile.class); |
| if (adapter != null) { |
| return adapter; |
| } |
| return Adapters.adapt(input, IResource.class); |
| } |
| |
| private static boolean belongsTo(List<? extends IResource> roots, IResource leaf) { |
| for (IResource resource : roots) { |
| if (resource.contains(leaf)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void closeEditors(List<IEditorReference> toClose, IWorkbenchWindow w) { |
| IWorkbenchPage page = w.getActivePage(); |
| if (page == null) { |
| return; |
| } |
| page.closeEditors(toClose.toArray(new IEditorReference[toClose.size()]), false); |
| } |
| } |