/******************************************************************************* * Copyright (C) 2014, 2016 Robin Stocker <robin@nibor.org> 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: * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 486857 *******************************************************************************/ package org.eclipse.egit.ui.internal.selection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.core.resources.mapping.ResourceTraversal; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.egit.core.AdapterUtils; import org.eclipse.egit.core.internal.util.ResourceUtil; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.CommonUtils; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.history.HistoryPageInput; import org.eclipse.egit.ui.internal.revision.FileRevisionEditorInput; import org.eclipse.egit.ui.internal.trace.GitTraceLocation; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Repository; import org.eclipse.swt.widgets.Shell; import org.eclipse.team.core.history.IFileRevision; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.ISources; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.part.MultiPageEditorPart; /** * Utilities for working with selections. */ public class SelectionUtils { /** * @param selection * @return the single selected repository, or <code>null</code> */ @Nullable public static Repository getRepository( @NonNull IStructuredSelection selection) { return getRepository(false, selection, null); } /** * @param evaluationContext * @return the single selected repository, or <code>null</code> */ @Nullable public static Repository getRepository( @Nullable IEvaluationContext evaluationContext) { return getRepository(false, getSelection(evaluationContext), null); } /** * Get the single selected repository or warn if no repository or multiple * different repositories could be found. * * @param selection * @param shell * the shell for showing the warning * @return the single selected repository, or <code>null</code> */ @Nullable public static Repository getRepositoryOrWarn( @NonNull IStructuredSelection selection, @NonNull Shell shell) { return getRepository(true, selection, shell); } /** * @param context * @return the structured selection of the evaluation context */ @NonNull public static IStructuredSelection getSelection( @Nullable IEvaluationContext context) { if (context == null) return StructuredSelection.EMPTY; Object selection = context .getVariable(ISources.ACTIVE_MENU_SELECTION_NAME); if (!(selection instanceof ISelection)) selection = context .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME); if (selection instanceof ITextSelection) return getSelectionFromEditorInput(context); else if (selection instanceof IStructuredSelection) return (IStructuredSelection) selection; return StructuredSelection.EMPTY; } /** * Tries to convert the passed selection to a structured selection. * <p> * E.g. in case of a text selection, it is converted to be a selection of * the resource that is in the editor. * * @param selection * @return the structured selection, or an empty selection */ @NonNull public static IStructuredSelection getStructuredSelection( @NonNull ISelection selection) { if (selection instanceof ITextSelection) { IEvaluationContext evaluationContext = getEvaluationContext(); if (evaluationContext == null) { return StructuredSelection.EMPTY; } return getSelectionFromEditorInput(evaluationContext); } else if (selection instanceof IStructuredSelection) { return (IStructuredSelection) selection; } return StructuredSelection.EMPTY; } /** * @param selection * @return the selected locations */ @NonNull public static IPath[] getSelectedLocations( @NonNull IStructuredSelection selection) { Set<IPath> result = new LinkedHashSet<>(); for (Object o : selection.toList()) { IResource resource = AdapterUtils.adaptToAnyResource(o); if (resource != null) { IPath location = resource.getLocation(); if (location != null) result.add(location); } else { IPath location = AdapterUtils.adapt(o, IPath.class); if (location != null) result.add(location); else for (IResource r : extractResourcesFromMapping(o)) { IPath l = r.getLocation(); if (l != null) result.add(l); } } } return result.toArray(new IPath[result.size()]); } /** * Returns the resources contained in the given selection. * * @param selection * @return the resources in the selection */ @NonNull public static IResource[] getSelectedResources( @NonNull IStructuredSelection selection) { Set<IResource> result = getSelectedResourcesSet(selection); return result.toArray(new IResource[result.size()]); } /** * Returns the resources contained in the given selection. * * @param selection * @return the resources in the selection */ @NonNull private static Set<IResource> getSelectedResourcesSet( @NonNull IStructuredSelection selection) { Set<IResource> result = new LinkedHashSet<>(); for (Object o : selection.toList()) { IResource resource = AdapterUtils.adaptToAnyResource(o); if (resource != null) result.add(resource); else result.addAll(extractResourcesFromMapping(o)); } return result; } private static List<IResource> extractResourcesFromMapping(Object o) { ResourceMapping mapping = AdapterUtils.adapt(o, ResourceMapping.class); if (mapping == null) return Collections.emptyList(); ResourceTraversal[] traversals; try { traversals = mapping.getTraversals(null, null); } catch (CoreException e) { Activator.logError(e.getMessage(), e); return Collections.emptyList(); } if (traversals.length == 0) return Collections.emptyList(); List<IResource> result = new ArrayList<>(); for (ResourceTraversal traversal : traversals) { IResource[] resources = traversal.getResources(); result.addAll(Arrays.asList(resources)); } return result; } /** * Determines the most fitting {@link HistoryPageInput} for the given * {@link IStructuredSelection}. The {@code mandatoryObject} must be * contained in the selection and in a repository. * <p> * Most fitting means that the input will contain all selected resources * which are contained in the same repository as the given * {@code mandatoryObject}. * </p> * * @param selection * The selection for which the most fitting HistoryPageInput is * to be determined. * @param mandatoryObject * The object to which the HistoryPageInput is tailored. Must be * contained in the given selection and in a repository. * @return The most fitting HistoryPageInput. Will return {@code null} when * the {@code mandatoryObject} is not contained in the given * selection or in a repository. */ @Nullable public static HistoryPageInput getMostFittingInput( @NonNull IStructuredSelection selection, Object mandatoryObject) { Set<IResource> resources = getSelectedResourcesSet(selection); if (!resources.contains(mandatoryObject)) { return null; } Repository repository = ResourceUtil .getRepository((IResource) mandatoryObject); if (repository == null) { return null; } for (Iterator<IResource> it = resources.iterator(); it.hasNext();) { IResource resource = it.next(); if (ResourceUtil.getRepository(resource) != repository) { it.remove(); } } IResource[] resourceArray = resources.toArray(new IResource[resources .size()]); return new HistoryPageInput(repository, resourceArray); } /** * Determines a set of either {@link Repository}, {@link IResource}s or * {@link IPath}s from a selection. For selection contents that adapt to * {@link Repository}, {@link IResource} or {@link ResourceMapping}, the * containing {@link Repository}s or {@link IResource}s are included in the * result set; otherwise for selection contents that adapt to {@link IPath} * these paths are included. * * @param selection * to process * @return the set of {@link Repository}, {@link IResource} and * {@link IPath} objects from the selection; not containing * {@code null} values */ @NonNull private static Set<Object> getSelectionContents( @NonNull IStructuredSelection selection) { Set<Object> result = new HashSet<>(); for (Object o : selection.toList()) { Repository r = AdapterUtils.adapt(o, Repository.class); if (r != null) { result.add(r); continue; } IResource resource = AdapterUtils.adaptToAnyResource(o); if (resource != null) { result.add(resource); continue; } ResourceMapping mapping = AdapterUtils.adapt(o, ResourceMapping.class); if (mapping != null) { result.addAll(extractResourcesFromMapping(mapping)); } else { IPath location = AdapterUtils.adapt(o, IPath.class); if (location != null) { result.add(location); } } } return result; } /** * Figure out which repository to use. All selected resources must map to * the same Git repository. * * @param warn * Put up a message dialog to warn why a resource was not * selected * @param selection * @param shell * must be provided if warn = true * @return repository for current project, or null */ @Nullable private static Repository getRepository(boolean warn, @NonNull IStructuredSelection selection, Shell shell) { if (selection.isEmpty()) { return null; } Set<Object> elements = getSelectionContents(selection); if (GitTraceLocation.SELECTION.isActive()) GitTraceLocation.getTrace().trace( GitTraceLocation.SELECTION.getLocation(), "selection=" //$NON-NLS-1$ + selection + ", elements=" + elements.toString()); //$NON-NLS-1$ boolean hadNull = false; Repository result = null; for (Object location : elements) { Repository repo = null; if (location instanceof Repository) { repo = (Repository) location; } else if (location instanceof IResource) { repo = ResourceUtil.getRepository((IResource) location); } else if (location instanceof IPath) { repo = ResourceUtil.getRepository((IPath) location); } if (repo == null) { hadNull = true; } if (result == null) { result = repo; } boolean mismatch = hadNull && result != null; if (mismatch || result != repo) { if (warn) { MessageDialog.openError(shell, UIText.RepositoryAction_multiRepoSelectionTitle, UIText.RepositoryAction_multiRepoSelection); } return null; } } if (result == null) { for (Object o : selection.toArray()) { Repository nextRepo = AdapterUtils.adapt(o, Repository.class); if (nextRepo != null && result != null && result != nextRepo) { if (warn) MessageDialog .openError( shell, UIText.RepositoryAction_multiRepoSelectionTitle, UIText.RepositoryAction_multiRepoSelection); return null; } result = nextRepo; } } if (result == null) { if (warn) MessageDialog.openError(shell, UIText.RepositoryAction_errorFindingRepoTitle, UIText.RepositoryAction_errorFindingRepo); return null; } return result; } private static IStructuredSelection getSelectionFromEditorInput( IEvaluationContext context) { Object part = context.getVariable(ISources.ACTIVE_PART_NAME); if (!(part instanceof IEditorPart)) { return StructuredSelection.EMPTY; } Object object = context.getVariable(ISources.ACTIVE_EDITOR_INPUT_NAME); Object editor = context.getVariable(ISources.ACTIVE_EDITOR_NAME); if (editor instanceof MultiPageEditorPart) { Object nestedEditor = ((MultiPageEditorPart) editor) .getSelectedPage(); if (nestedEditor instanceof IEditorPart) { object = ((IEditorPart) nestedEditor).getEditorInput(); } } if (!(object instanceof IEditorInput) && (editor instanceof IEditorPart)) { object = ((IEditorPart) editor).getEditorInput(); } if (object instanceof IEditorInput) { IEditorInput editorInput = (IEditorInput) object; // Note that there is both a getResource(IEditorInput) as well as a // getResource(Object), which don't do the same thing. We explicitly // want the first here. IResource resource = org.eclipse.ui.ide.ResourceUtil .getResource(editorInput); if (resource != null) return new StructuredSelection(resource); if (editorInput instanceof FileRevisionEditorInput) { FileRevisionEditorInput fileRevisionEditorInput = (FileRevisionEditorInput) editorInput; IFileRevision fileRevision = fileRevisionEditorInput .getFileRevision(); if (fileRevision != null) return new StructuredSelection(fileRevision); } } return StructuredSelection.EMPTY; } private static IEvaluationContext getEvaluationContext() { IEvaluationContext ctx; IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); // no active window during Eclipse shutdown if (activeWorkbenchWindow == null) return null; IHandlerService hsr = CommonUtils.getService(activeWorkbenchWindow, IHandlerService.class); ctx = hsr.getCurrentState(); return ctx; } }