1 /*******************************************************************************
2 * Copyright (C) 2014, 2016 Robin Stocker <robin@nibor.org> and others.
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 486857
13 *******************************************************************************/
14 package org
.eclipse
.egit
.ui
.internal
.selection
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Arrays
;
18 import java
.util
.Collections
;
19 import java
.util
.HashSet
;
20 import java
.util
.Iterator
;
21 import java
.util
.LinkedHashSet
;
22 import java
.util
.List
;
25 import org
.eclipse
.core
.expressions
.IEvaluationContext
;
26 import org
.eclipse
.core
.resources
.IProject
;
27 import org
.eclipse
.core
.resources
.IResource
;
28 import org
.eclipse
.core
.resources
.mapping
.ResourceMapping
;
29 import org
.eclipse
.core
.runtime
.IPath
;
30 import org
.eclipse
.egit
.core
.AdapterUtils
;
31 import org
.eclipse
.egit
.core
.internal
.util
.ResourceUtil
;
32 import org
.eclipse
.egit
.core
.project
.RepositoryMapping
;
33 import org
.eclipse
.egit
.ui
.internal
.CommonUtils
;
34 import org
.eclipse
.egit
.ui
.internal
.UIText
;
35 import org
.eclipse
.egit
.ui
.internal
.history
.HistoryPageInput
;
36 import org
.eclipse
.egit
.ui
.internal
.revision
.FileRevisionEditorInput
;
37 import org
.eclipse
.egit
.ui
.internal
.trace
.GitTraceLocation
;
38 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
39 import org
.eclipse
.jface
.text
.ITextSelection
;
40 import org
.eclipse
.jface
.viewers
.ISelection
;
41 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
42 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
43 import org
.eclipse
.jgit
.annotations
.NonNull
;
44 import org
.eclipse
.jgit
.annotations
.Nullable
;
45 import org
.eclipse
.jgit
.lib
.Repository
;
46 import org
.eclipse
.swt
.widgets
.Shell
;
47 import org
.eclipse
.team
.core
.history
.IFileRevision
;
48 import org
.eclipse
.ui
.IEditorInput
;
49 import org
.eclipse
.ui
.IEditorPart
;
50 import org
.eclipse
.ui
.ISources
;
51 import org
.eclipse
.ui
.IWorkbenchWindow
;
52 import org
.eclipse
.ui
.PlatformUI
;
53 import org
.eclipse
.ui
.handlers
.IHandlerService
;
54 import org
.eclipse
.ui
.part
.MultiPageEditorPart
;
57 * Utilities for working with selections.
59 public class SelectionUtils
{
63 * @return the single selected repository, or <code>null</code>
66 public static Repository
getRepository(
67 @NonNull IStructuredSelection selection
) {
68 return getRepository(false, selection
, null);
72 * @param evaluationContext
73 * @return array of selected repositories
76 public static Repository
[] getRepositories(
77 @Nullable IEvaluationContext evaluationContext
) {
78 return getRepositories(getSelection(evaluationContext
));
82 * Retrieves all the repositories associated with the current selection. It
83 * attempts to first identify the selections as projects and if that yields
84 * an empty result, it then changes to adapt the selections to the
88 * @return array of repositories
91 public static Repository
[] getRepositories(
92 @NonNull IStructuredSelection selection
) {
94 IProject
[] selectedProjects
= getSelectedProjects(selection
);
96 if (selectedProjects
.length
> 0)
97 return getRepositoriesFor(selectedProjects
);
99 if (selection
.isEmpty()) {
100 return new Repository
[0];
103 Set
<Repository
> repos
= new LinkedHashSet
<>();
104 for (Object o
: selection
.toArray()) {
105 Repository repo
= AdapterUtils
.adapt(o
, Repository
.class);
109 // no repository found for one of the objects!
110 return new Repository
[0];
113 return repos
.toArray(new Repository
[0]);
117 * @param evaluationContext
118 * @return the single selected repository, or <code>null</code>
121 public static Repository
getRepository(
122 @Nullable IEvaluationContext evaluationContext
) {
123 return getRepository(false, getSelection(evaluationContext
), null);
127 * Get the single selected repository or warn if no repository or multiple
128 * different repositories could be found.
132 * the shell for showing the warning
133 * @return the single selected repository, or <code>null</code>
136 public static Repository
getRepositoryOrWarn(
137 @NonNull IStructuredSelection selection
, @NonNull Shell shell
) {
138 return getRepository(true, selection
, shell
);
143 * @return the structured selection of the evaluation context
146 public static IStructuredSelection
getSelection(
147 @Nullable IEvaluationContext context
) {
149 return StructuredSelection
.EMPTY
;
151 Object selection
= context
152 .getVariable(ISources
.ACTIVE_MENU_SELECTION_NAME
);
153 if (!(selection
instanceof ISelection
))
155 .getVariable(ISources
.ACTIVE_CURRENT_SELECTION_NAME
);
157 if (selection
instanceof ITextSelection
)
158 return getSelectionFromEditorInput(context
);
159 else if (selection
instanceof IStructuredSelection
)
160 return (IStructuredSelection
) selection
;
161 return StructuredSelection
.EMPTY
;
165 * Tries to convert the passed selection to a structured selection.
167 * E.g. in case of a text selection, it is converted to be a selection of
168 * the resource that is in the editor.
171 * @return the structured selection, or an empty selection
174 public static IStructuredSelection
getStructuredSelection(
175 @NonNull ISelection selection
) {
176 if (selection
instanceof ITextSelection
) {
177 IEvaluationContext evaluationContext
= getEvaluationContext();
178 if (evaluationContext
== null) {
179 return StructuredSelection
.EMPTY
;
181 return getSelectionFromEditorInput(evaluationContext
);
182 } else if (selection
instanceof IStructuredSelection
) {
183 return (IStructuredSelection
) selection
;
185 return StructuredSelection
.EMPTY
;
190 * @return the selected locations
193 public static IPath
[] getSelectedLocations(
194 @NonNull IStructuredSelection selection
) {
195 Set
<IPath
> result
= new LinkedHashSet
<>();
196 for (Object o
: selection
.toList()) {
197 IResource resource
= AdapterUtils
.adaptToAnyResource(o
);
198 if (resource
!= null) {
199 IPath location
= resource
.getLocation();
200 if (location
!= null)
201 result
.add(location
);
203 IPath location
= AdapterUtils
.adapt(o
, IPath
.class);
204 if (location
!= null)
205 result
.add(location
);
207 for (IResource r
: extractResourcesFromMapping(o
)) {
208 IPath l
= r
.getLocation();
214 return result
.toArray(new IPath
[0]);
218 * Returns the resources contained in the given selection.
221 * @return the resources in the selection
224 public static IResource
[] getSelectedResources(
225 @NonNull IStructuredSelection selection
) {
226 Set
<IResource
> result
= getSelectedResourcesSet(selection
);
227 return result
.toArray(new IResource
[0]);
231 * Returns the resources contained in the given selection.
234 * @return the resources in the selection
237 private static Set
<IResource
> getSelectedResourcesSet(
238 @NonNull IStructuredSelection selection
) {
239 Set
<IResource
> result
= new LinkedHashSet
<>();
240 for (Object o
: selection
.toList()) {
241 IResource resource
= AdapterUtils
.adaptToAnyResource(o
);
242 if (resource
!= null)
243 result
.add(resource
);
245 result
.addAll(extractResourcesFromMapping(o
));
250 private static List
<IResource
> extractResourcesFromMapping(Object o
) {
251 ResourceMapping mapping
= AdapterUtils
.adapt(o
, ResourceMapping
.class);
252 return ResourceUtil
.extractResourcesFromMapping(mapping
);
256 * Determines the most fitting {@link HistoryPageInput} for the given
257 * {@link IStructuredSelection}. The {@code mandatoryObject} must be
258 * contained in the selection and in a repository.
260 * Most fitting means that the input will contain all selected resources
261 * which are contained in the same repository as the given
262 * {@code mandatoryObject}.
266 * The selection for which the most fitting HistoryPageInput is
268 * @param mandatoryObject
269 * The object to which the HistoryPageInput is tailored. Must be
270 * contained in the given selection and in a repository.
271 * @return The most fitting HistoryPageInput. Will return {@code null} when
272 * the {@code mandatoryObject} is not contained in the given
273 * selection or in a repository.
276 public static HistoryPageInput
getMostFittingInput(
277 @NonNull IStructuredSelection selection
, Object mandatoryObject
) {
278 Set
<IResource
> resources
= getSelectedResourcesSet(selection
);
279 if (!resources
.contains(mandatoryObject
)) {
283 Repository repository
= ResourceUtil
284 .getRepository((IResource
) mandatoryObject
);
285 if (repository
== null) {
289 for (Iterator
<IResource
> it
= resources
.iterator(); it
.hasNext();) {
290 IResource resource
= it
.next();
291 if (ResourceUtil
.getRepository(resource
) != repository
) {
296 IResource
[] resourceArray
= resources
.toArray(new IResource
[0]);
297 return new HistoryPageInput(repository
, resourceArray
);
301 * Determines a set of either {@link Repository}, {@link IResource}s or
302 * {@link IPath}s from a selection. For selection contents that adapt to
303 * {@link Repository}, {@link IResource} or {@link ResourceMapping}, the
304 * containing {@link Repository}s or {@link IResource}s are included in the
305 * result set; otherwise for selection contents that adapt to {@link IPath}
306 * these paths are included.
310 * @return the set of {@link Repository}, {@link IResource} and
311 * {@link IPath} objects from the selection; not containing
312 * {@code null} values
315 private static Set
<Object
> getSelectionContents(
316 @NonNull IStructuredSelection selection
) {
317 Set
<Object
> result
= new HashSet
<>();
318 for (Object o
: selection
.toList()) {
319 Repository r
= AdapterUtils
.adapt(o
, Repository
.class);
324 IResource resource
= AdapterUtils
.adaptToAnyResource(o
);
325 if (resource
!= null) {
326 result
.add(resource
);
329 ResourceMapping mapping
= AdapterUtils
.adapt(o
,
330 ResourceMapping
.class);
331 if (mapping
!= null) {
332 result
.addAll(extractResourcesFromMapping(mapping
));
334 IPath location
= AdapterUtils
.adapt(o
, IPath
.class);
335 if (location
!= null) {
336 result
.add(location
);
344 * Figure out which repository to use. All selected resources must map to
345 * the same Git repository.
348 * Put up a message dialog to warn why a resource was not
352 * must be provided if warn = true
353 * @return repository for current project, or null
356 private static Repository
getRepository(boolean warn
,
357 @NonNull IStructuredSelection selection
, Shell shell
) {
358 if (selection
.isEmpty()) {
361 Set
<Object
> elements
= getSelectionContents(selection
);
362 if (GitTraceLocation
.SELECTION
.isActive())
363 GitTraceLocation
.getTrace().trace(
364 GitTraceLocation
.SELECTION
.getLocation(), "selection=" //$NON-NLS-1$
365 + selection
+ ", elements=" + elements
.toString()); //$NON-NLS-1$
367 boolean hadNull
= false;
368 Repository result
= null;
369 for (Object location
: elements
) {
370 Repository repo
= null;
371 if (location
instanceof Repository
) {
372 repo
= (Repository
) location
;
373 } else if (location
instanceof IResource
) {
374 repo
= ResourceUtil
.getRepository((IResource
) location
);
375 } else if (location
instanceof IPath
) {
376 repo
= ResourceUtil
.getRepository((IPath
) location
);
381 if (result
== null) {
384 boolean mismatch
= hadNull
&& result
!= null;
385 if (mismatch
|| result
!= repo
) {
387 MessageDialog
.openError(shell
,
388 UIText
.RepositoryAction_multiRepoSelectionTitle
,
389 UIText
.RepositoryAction_multiRepoSelection
);
395 if (result
== null) {
396 for (Object o
: selection
.toArray()) {
397 Repository nextRepo
= AdapterUtils
.adapt(o
, Repository
.class);
398 if (nextRepo
!= null && result
!= null && result
!= nextRepo
) {
403 UIText
.RepositoryAction_multiRepoSelectionTitle
,
404 UIText
.RepositoryAction_multiRepoSelection
);
411 if (result
== null) {
413 MessageDialog
.openError(shell
,
414 UIText
.RepositoryAction_errorFindingRepoTitle
,
415 UIText
.RepositoryAction_errorFindingRepo
);
422 private static IStructuredSelection
getSelectionFromEditorInput(
423 IEvaluationContext context
) {
424 Object part
= context
.getVariable(ISources
.ACTIVE_PART_NAME
);
425 if (!(part
instanceof IEditorPart
)) {
426 return StructuredSelection
.EMPTY
;
428 Object object
= context
.getVariable(ISources
.ACTIVE_EDITOR_INPUT_NAME
);
429 Object editor
= context
.getVariable(ISources
.ACTIVE_EDITOR_NAME
);
430 if (editor
instanceof MultiPageEditorPart
) {
431 Object nestedEditor
= ((MultiPageEditorPart
) editor
)
433 if (nestedEditor
instanceof IEditorPart
) {
434 object
= ((IEditorPart
) nestedEditor
).getEditorInput();
437 if (!(object
instanceof IEditorInput
)
438 && (editor
instanceof IEditorPart
)) {
439 object
= ((IEditorPart
) editor
).getEditorInput();
441 if (object
instanceof IEditorInput
) {
442 IEditorInput editorInput
= (IEditorInput
) object
;
443 // Note that there is both a getResource(IEditorInput) as well as a
444 // getResource(Object), which don't do the same thing. We explicitly
445 // want the first here.
446 IResource resource
= org
.eclipse
.ui
.ide
.ResourceUtil
447 .getResource(editorInput
);
448 if (resource
!= null)
449 return new StructuredSelection(resource
);
450 if (editorInput
instanceof FileRevisionEditorInput
) {
451 FileRevisionEditorInput fileRevisionEditorInput
= (FileRevisionEditorInput
) editorInput
;
452 IFileRevision fileRevision
= fileRevisionEditorInput
454 if (fileRevision
!= null)
455 return new StructuredSelection(fileRevision
);
459 return StructuredSelection
.EMPTY
;
462 private static IEvaluationContext
getEvaluationContext() {
463 IEvaluationContext ctx
;
464 IWorkbenchWindow activeWorkbenchWindow
= PlatformUI
.getWorkbench()
465 .getActiveWorkbenchWindow();
466 // no active window during Eclipse shutdown
467 if (activeWorkbenchWindow
== null)
469 IHandlerService hsr
= CommonUtils
.getService(activeWorkbenchWindow
, IHandlerService
.class);
470 ctx
= hsr
.getCurrentState();
475 * Retrieve the list of projects that contains the given resources. All
476 * resources must actually map to a project shared with egit, otherwise an
477 * empty array is returned. In case of a linked resource, the project
478 * returned is the one that contains the link target and is shared with
479 * egit, if any, otherwise an empty array is also returned.
482 * @return the projects hosting the selected resources
484 public static IProject
[] getSelectedProjects(
485 IStructuredSelection selection
) {
486 Set
<IProject
> ret
= new LinkedHashSet
<>();
487 for (IResource resource
: getSelectedAdaptables(selection
,
489 RepositoryMapping mapping
= RepositoryMapping
.getMapping(resource
);
490 if (mapping
!= null && (mapping
.getContainer() instanceof IProject
))
491 ret
.add((IProject
) mapping
.getContainer());
493 return new IProject
[0];
495 ret
.addAll(extractProjectsFromMappings(selection
));
497 return ret
.toArray(new IProject
[0]);
500 private static Set
<IProject
> extractProjectsFromMappings(
501 IStructuredSelection selection
) {
502 Set
<IProject
> ret
= new LinkedHashSet
<>();
503 for (ResourceMapping mapping
: getSelectedAdaptables(selection
,
504 ResourceMapping
.class)) {
505 IProject
[] mappedProjects
= mapping
.getProjects();
506 if (mappedProjects
!= null && mappedProjects
.length
!= 0) {
507 // Some mappings (WorkingSetResourceMapping) return the projects
508 // in unpredictable order. Sort them like the navigator to
509 // correspond to the order the user usually sees.
510 List
<IProject
> projects
= new ArrayList
<>(
511 Arrays
.asList(mappedProjects
));
512 Collections
.sort(projects
,
513 CommonUtils
.RESOURCE_NAME_COMPARATOR
);
514 ret
.addAll(projects
);
521 * Creates an array of the given class type containing all the objects in
522 * the selection that adapt to the given class.
526 * @return the selected adaptables
529 private static <T
> List
<T
> getSelectedAdaptables(ISelection selection
,
532 if (selection
!= null && !selection
.isEmpty()) {
533 result
= new ArrayList
<>();
534 Iterator elements
= ((IStructuredSelection
) selection
).iterator();
535 while (elements
.hasNext()) {
536 T adapter
= AdapterUtils
.adapt(elements
.next(), c
);
537 if (adapter
!= null) {
542 result
= Collections
.emptyList();
550 * @return the repositories that projects map to if all projects are mapped
553 private static Repository
[] getRepositoriesFor(final IProject
[] projects
) {
554 Set
<Repository
> ret
= new LinkedHashSet
<>();
555 for (IProject project
: projects
) {
556 RepositoryMapping repositoryMapping
= RepositoryMapping
557 .getMapping(project
);
558 if (repositoryMapping
== null) {
559 return new Repository
[0];
561 ret
.add(repositoryMapping
.getRepository());
563 return ret
.toArray(new Repository
[0]);