blob: 97b62269db504a1f59c700c19c18e15098c6f86a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2021 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Mathias Kinzler (SAP AG) - initial implementation
* Dariusz Luksza <dariusz@luksza.org> - add synchronization feature
* Daniel Megert <daniel_megert@ch.ibm.com> - Only check out on double-click
* Daniel Megert <daniel_megert@ch.ibm.com> - Don't reveal selection on refresh
* Robin Stocker <robin@nibor.org> - Show In support
* Daniel Megert <daniel_megert@ch.ibm.com> - Show Git Staging view in Show In menu
* Alexander Nittka <alex@nittka.de> - Bug 545123
*******************************************************************************/
package org.eclipse.egit.ui.internal.repository;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.commands.IStateListener;
import org.eclipse.core.commands.State;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.RepositoryCache;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.Utils;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.EgitUiEditorUtils;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.actions.ActionCommands;
import org.eclipse.egit.ui.internal.branch.BranchOperationUI;
import org.eclipse.egit.ui.internal.commands.ToggleCommand;
import org.eclipse.egit.ui.internal.components.MessagePopupTextCellEditor;
import org.eclipse.egit.ui.internal.groups.RepositoryGroup;
import org.eclipse.egit.ui.internal.groups.RepositoryGroups;
import org.eclipse.egit.ui.internal.history.HistoryPageInput;
import org.eclipse.egit.ui.internal.properties.GitPropertySheetPage;
import org.eclipse.egit.ui.internal.reflog.ReflogView;
import org.eclipse.egit.ui.internal.repository.tree.FetchNode;
import org.eclipse.egit.ui.internal.repository.tree.FileNode;
import org.eclipse.egit.ui.internal.repository.tree.FilterableNode;
import org.eclipse.egit.ui.internal.repository.tree.FolderNode;
import org.eclipse.egit.ui.internal.repository.tree.PushNode;
import org.eclipse.egit.ui.internal.repository.tree.RefNode;
import org.eclipse.egit.ui.internal.repository.tree.RemoteNode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryGroupNode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryNode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType;
import org.eclipse.egit.ui.internal.repository.tree.StashedCommitNode;
import org.eclipse.egit.ui.internal.repository.tree.TagNode;
import org.eclipse.egit.ui.internal.repository.tree.WorkingDirNode;
import org.eclipse.egit.ui.internal.selection.RepositoryVirtualNode;
import org.eclipse.egit.ui.internal.selection.SelectionUtils;
import org.eclipse.egit.ui.internal.staging.StagingView;
import org.eclipse.egit.ui.internal.trace.GitTraceLocation;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewerEditor;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.Window;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.events.ConfigChangedListener;
import org.eclipse.jgit.events.IndexChangedListener;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ControlEditor;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.team.ui.history.IHistoryView;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.handlers.RegistryToggleState;
import org.eclipse.ui.navigator.CommonNavigator;
import org.eclipse.ui.navigator.CommonViewer;
import org.eclipse.ui.part.IPage;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.PropertySheet;
import org.eclipse.ui.views.properties.PropertySheetPage;
/**
* The "Git Repositories View".
*/
public class RepositoriesView extends CommonNavigator implements IShowInSource, IShowInTargetList {
/** View id; also doubles as context id. */
public static final String VIEW_ID = "org.eclipse.egit.ui.RepositoriesView"; //$NON-NLS-1$
/** Sub-context active when a single repository is selected. */
private static final String SINGLE_REPO_CONTEXT_ID = VIEW_ID
+ ".SingleRepository"; //$NON-NLS-1$
/** The command id for toggling "link with selection". */
public static final String LINK_WITH_SELECTION_ID = "org.eclipse.egit.ui.RepositoriesLinkWithSelection"; //$NON-NLS-1$
/**
* Delay between refreshes in milliseconds. Used to avoid overwhelming the
* viewer with refreshes when many change events arrive from repositories.
*/
private static final long DEFAULT_REFRESH_DELAY = 300L;
private final Set<Repository> repositories = new HashSet<>();
private final RefsChangedListener myRefsChangedListener = event -> scheduleRefresh();
private final IndexChangedListener myIndexChangedListener = event -> scheduleRefresh();
private final ConfigChangedListener myConfigChangeListener = event -> scheduleRefresh();
private final List<ListenerHandle> myListeners = new LinkedList<>();
private RefreshUiJob refreshUiJob;
private RefCache.Cache refCache = RefCache.get();
private Composite emptyArea;
private StackLayout layout;
private State reactOnSelection;
private IWorkbenchPart lastSelectionPart;
private File lastSelectedRepository;
private boolean filterCacheLoaded;
private final ISelectionListener selectionChangedListener = (part,
selection) -> {
if (part == RepositoriesView.this) {
if (!selection.isEmpty()
&& selection instanceof IStructuredSelection) {
Repository repo = SelectionUtils
.getRepository((IStructuredSelection) selection);
if (repo != null) {
lastSelectedRepository = repo.getDirectory();
} else {
lastSelectedRepository = null;
}
}
return;
}
IWorkbenchPart currentPart = determinePart(part);
if (!((Boolean) reactOnSelection.getValue()).booleanValue()) {
lastSelectionPart = currentPart;
} else {
lastSelectionPart = null;
reactOnSelection(convertSelection(currentPart, selection));
}
};
private final IStateListener reactOnSelectionListener = (state,
oldValue) -> {
if (((Boolean) state.getValue()).booleanValue()
&& lastSelectionPart != null) {
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
if (lastSelectionPart == null) {
return;
}
IWorkbenchPartSite site = lastSelectionPart.getSite();
if (site == null) {
return;
}
ISelectionProvider provider = site.getSelectionProvider();
if (provider == null) {
return;
}
reactOnSelection(convertSelection(lastSelectionPart,
provider.getSelection()));
});
}
};
private State branchHierarchy;
private final IStateListener stateChangeListener = (state,
oldValue) -> refresh();
private final IPreferenceChangeListener configurationListener = event -> {
if (RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
refresh();
}
};
private IContextActivation renameContext;
private IContextService ctxSrv;
private TextCellEditor textCellEditor;
private Text filterField;
/**
* The default constructor
*/
public RepositoriesView() {
refreshUiJob = new RefreshUiJob();
}
/**
* Create area shown when no repositories are present
*
* @param parent
*/
protected void createEmptyArea(Composite parent) {
emptyArea = new Composite(parent, SWT.NONE);
emptyArea.setBackgroundMode(SWT.INHERIT_FORCE);
MenuManager manager = new MenuManager();
manager.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager m) {
getNavigatorActionService().fillContextMenu(m);
}
});
getSite().registerContextMenu(manager, getCommonViewer());
Menu menu = manager.createContextMenu(emptyArea);
emptyArea.setMenu(menu);
GridLayoutFactory.fillDefaults().applyTo(emptyArea);
Composite infoArea = new Composite(emptyArea, SWT.NONE);
infoArea.setMenu(menu);
GridDataFactory.swtDefaults().align(SWT.LEFT, SWT.TOP)
.grab(true, true).applyTo(infoArea);
GridLayoutFactory.swtDefaults().applyTo(infoArea);
Label messageLabel = new Label(infoArea, SWT.WRAP);
messageLabel.setText(UIText.RepositoriesView_messageEmpty);
messageLabel.setMenu(menu);
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL)
.grab(true, false).applyTo(messageLabel);
Composite optionsArea = new Composite(infoArea, SWT.NONE);
optionsArea.setMenu(menu);
GridLayoutFactory.swtDefaults().numColumns(2).applyTo(optionsArea);
GridDataFactory.swtDefaults().indent(5, 0).grab(true, true)
.applyTo(optionsArea);
final FormToolkit toolkit = new FormToolkit(emptyArea.getDisplay());
emptyArea.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
toolkit.dispose();
}
});
final Color linkColor = JFaceColors.getHyperlinkText(emptyArea
.getDisplay());
Label addLabel = new Label(optionsArea, SWT.NONE);
Image addImage = UIIcons.NEW_REPOSITORY.createImage();
UIUtils.hookDisposal(addLabel, addImage);
addLabel.setImage(addImage);
Hyperlink addLink = toolkit.createHyperlink(optionsArea,
UIText.RepositoriesView_linkAdd, SWT.WRAP);
addLink.setForeground(linkColor);
addLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
IHandlerService service = getViewSite()
.getService(IHandlerService.class);
UIUtils.executeCommand(service,
"org.eclipse.egit.ui.RepositoriesViewAddRepository"); //$NON-NLS-1$
}
});
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL)
.grab(true, false).applyTo(addLink);
Label cloneLabel = new Label(optionsArea, SWT.NONE);
Image cloneImage = UIIcons.CLONEGIT.createImage();
UIUtils.hookDisposal(cloneLabel, cloneImage);
cloneLabel.setImage(cloneImage);
Hyperlink cloneLink = toolkit.createHyperlink(optionsArea,
UIText.RepositoriesView_linkClone, SWT.WRAP);
cloneLink.setForeground(linkColor);
cloneLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
IHandlerService service = getViewSite()
.getService(IHandlerService.class);
UIUtils.executeCommand(service,
"org.eclipse.egit.ui.RepositoriesViewClone"); //$NON-NLS-1$
}
});
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL)
.grab(true, false).applyTo(cloneLink);
Label createLabel = new Label(optionsArea, SWT.NONE);
Image createImage = UIIcons.CREATE_REPOSITORY.createImage();
UIUtils.hookDisposal(createLabel, createImage);
createLabel.setImage(createImage);
Hyperlink createLink = toolkit.createHyperlink(optionsArea,
UIText.RepositoriesView_linkCreate, SWT.WRAP);
createLink.setForeground(linkColor);
createLink.setText(UIText.RepositoriesView_linkCreate);
createLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
IHandlerService service = getViewSite()
.getService(IHandlerService.class);
UIUtils.executeCommand(service,
"org.eclipse.egit.ui.RepositoriesViewCreateRepository"); //$NON-NLS-1$
}
});
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL)
.grab(true, false).applyTo(createLink);
}
@Override
public void createPartControl(Composite aParent) {
FilterCache.INSTANCE.load();
filterCacheLoaded = true;
Composite displayArea = new Composite(aParent, SWT.NONE);
layout = new StackLayout();
displayArea.setLayout(layout);
createEmptyArea(displayArea);
super.createPartControl(displayArea);
IWorkbenchWindow w = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
ICommandService csrv = w.getService(ICommandService.class);
reactOnSelection = csrv.getCommand(LINK_WITH_SELECTION_ID)
.getState(RegistryToggleState.STATE_ID);
reactOnSelection.addListener(reactOnSelectionListener);
branchHierarchy = csrv.getCommand(ToggleCommand.BRANCH_HIERARCHY_ID)
.getState(RegistryToggleState.STATE_ID);
branchHierarchy.addListener(stateChangeListener);
IWorkbenchSiteProgressService service = getSite()
.getService(IWorkbenchSiteProgressService.class);
if (service != null) {
service.showBusyForFamily(JobFamilies.REPO_VIEW_REFRESH);
service.showBusyForFamily(JobFamilies.CLONE);
}
}
@Override
protected CommonViewer createCommonViewerObject(Composite aParent) {
return new RepositoriesCommonViewer(getViewSite().getId(), aParent,
SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
}
private void setTopControl(CommonViewer viewer) {
if (!repositories.isEmpty()
|| RepositoryGroups.INSTANCE.hasGroups()) {
layout.topControl = viewer.getControl();
} else {
layout.topControl = emptyArea;
}
}
// After a refresh of the CommonViewer decide what to display
private void afterRefresh(CommonViewer viewer) {
Control currentTop = layout.topControl;
setTopControl(viewer);
if (currentTop != layout.topControl) {
emptyArea.getParent().layout(true, true);
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
// integrate with Properties view
if (adapter == IPropertySheetPage.class) {
PropertySheetPage page = new GitPropertySheetPage();
page.setPropertySourceProvider(
new RepositoryPropertySourceProvider(page));
return adapter.cast(page);
}
return super.getAdapter(adapter);
}
@Override
protected CommonViewer createCommonViewer(Composite aParent) {
CommonViewer viewer = super.createCommonViewer(aParent);
// handle the double-click event for tags and branches
viewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
TreeSelection sel = (TreeSelection) event.getSelection();
RepositoryTreeNode element = (RepositoryTreeNode) sel
.getFirstElement();
// after deletion the selection can be empty
if (element == null) {
return;
}
if (element instanceof RepositoryGroupNode) {
return;
}
// Disable checkout for bare repositories
if (element.getRepository().isBare()) {
return;
}
if (element instanceof RefNode) {
executeOpenCommandWithConfirmation(element,
((RefNode) element).getObject().getName());
} else if (element instanceof TagNode) {
executeOpenCommandWithConfirmation(element,
((TagNode) element).getObject().getName());
} else if (element instanceof FetchNode) {
executeFetchCommand(((FetchNode) element));
}
}
});
// handle open event for the working directory
viewer.addOpenListener(new IOpenListener() {
@Override
public void open(OpenEvent event) {
TreeSelection sel = (TreeSelection) event.getSelection();
RepositoryTreeNode element = (RepositoryTreeNode) sel
.getFirstElement();
if (element instanceof FileNode
|| element instanceof StashedCommitNode)
executeOpenCommand(element);
}
});
ctxSrv = getSite().getService(IContextService.class);
viewer.addSelectionChangedListener(event -> {
handleSingleRepositoryContext(event.getSelection(), viewer);
});
viewer.getTree().addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
handleSingleRepositoryContext(null, viewer);
}
@Override
public void focusGained(FocusEvent e) {
handleSingleRepositoryContext(viewer.getSelection(), viewer);
}
});
setupInlineEditing(viewer);
// react on selection changes
ISelectionService srv = getSite().getService(ISelectionService.class);
srv.addPostSelectionListener(selectionChangedListener);
// react on changes in the configured repositories
RepositoryUtil.INSTANCE.getPreferences().addPreferenceChangeListener(
configurationListener);
initRepositoriesAndListeners();
ctxSrv.activateContext(VIEW_ID);
// link with editor
viewer.addPostSelectionChangedListener(event -> {
if (!((Boolean) reactOnSelection.getValue()).booleanValue()) {
return;
}
ISelection selection = event.getSelection();
if (selection.isEmpty()
|| !(selection instanceof IStructuredSelection)) {
return;
}
IStructuredSelection sel = (IStructuredSelection) selection;
if (sel.size() > 1) {
return;
}
Object selected = sel.getFirstElement();
if (selected instanceof FileNode) {
showEditor((FileNode) selected);
}
});
emptyArea.setBackground(viewer.getControl().getBackground());
setTopControl(viewer);
return viewer;
}
private void handleSingleRepositoryContext(ISelection selection,
CommonViewer viewer) {
boolean activate = false;
if (selection != null && !selection.isEmpty()
&& (selection instanceof StructuredSelection)) {
StructuredSelection sel = (StructuredSelection) selection;
Object item = sel.getFirstElement();
activate = sel.size() == 1 && (item instanceof RepositoryNode);
}
if (!activate) {
if (renameContext != null) {
ctxSrv.deactivateContext(renameContext);
renameContext = null;
}
} else if (viewer.getTree().isFocusControl() && renameContext == null) {
renameContext = ctxSrv.activateContext(SINGLE_REPO_CONTEXT_ID);
}
}
private void setupInlineEditing(CommonViewer viewer) {
ColumnViewerEditorActivationStrategy editorActivation = new ColumnViewerEditorActivationStrategy(
viewer) {
@Override
protected boolean isEditorActivationEvent(
ColumnViewerEditorActivationEvent event) {
// Editing is started only through the
// RenameRepositoryGroupCommand
return event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
}
};
// TABBING_HORIZONTAL with only one column effectively switches off
// tabbing. It may jump otherwise into an open editor (closing the cell
// editor), and when the user is typing he may then inadvertently modify
// that file.
TreeViewerEditor.create(viewer, editorActivation,
ColumnViewerEditor.TABBING_HORIZONTAL);
// Record the initial value so that the validator can avoid producing an
// error if the text is the same again during editing.
String initialValue[] = { null };
textCellEditor = new MessagePopupTextCellEditor(viewer.getTree(), true) {
@Override
protected boolean withBorder() {
return true;
}
};
textCellEditor.setValidator(value -> {
String currentText = value.toString().trim();
if (currentText.isEmpty()) {
return UIText.RepositoriesView_RepoGroup_EmptyNameError;
}
if (!currentText.equals(initialValue[0])
&& RepositoryGroups.INSTANCE.groupExists(currentText)) {
return MessageFormat.format(
UIText.RepositoryGroups_DuplicateGroupNameError,
currentText);
}
return null;
});
// We don't have a ViewerColumn at hand... use the legacy mechanism:
viewer.setColumnProperties(new String[] { "Name" }); //$NON-NLS-1$
viewer.setCellEditors(new CellEditor[] { textCellEditor });
viewer.setCellModifier(new ICellModifier() {
@Override
public boolean canModify(Object element, String property) {
return element instanceof RepositoryGroupNode;
}
private String doGetValue(Object element) {
if (element instanceof RepositoryGroupNode) {
return ((RepositoryGroupNode) element).getObject()
.getName();
}
return null;
}
@Override
public Object getValue(Object element, String property) {
String result = doGetValue(element);
initialValue[0] = result;
return result;
}
@Override
public void modify(Object element, String property, Object value) {
if (element instanceof Item) {
element = ((Item) element).getData();
}
if (element instanceof RepositoryGroupNode
&& value instanceof CharSequence) {
RepositoryGroup group = ((RepositoryGroupNode) element)
.getObject();
String newName = value.toString().trim();
if (!newName.equals(group.getName())) {
RepositoryGroups.INSTANCE.renameGroup(group, newName);
// Refresh all to get re-sorting
viewer.refresh();
// Re-set the selection to get a status bar update
viewer.setSelection(new StructuredSelection(element),
true);
}
}
}
});
}
/**
* If a text editor is open and has the focus, paste into it.
*
* @return {@code true} if there was a text editor that did paste,
* {@code false} otherwise
*/
public boolean pasteInEditor() {
if (filterField != null && !filterField.isDisposed()) {
// We're editing
filterField.paste();
return true;
}
if (textCellEditor != null && textCellEditor.isActivated()) {
// We're editing
textCellEditor.performPaste();
return true;
}
return false;
}
/**
* Opens a text input to filter the node's children.
*
* @param node
* to filter
*/
public void filter(@NonNull FilterableNode node) {
if (filterField != null) {
// Allow only one tree editor
return;
}
CommonViewer rawViewer = getCommonViewer();
if (!(rawViewer instanceof RepositoriesCommonViewer)) {
return; // Cannot happen
}
RepositoriesCommonViewer viewer = (RepositoriesCommonViewer) rawViewer;
IContentProvider rawProvider = viewer.getContentProvider();
if (!(rawProvider instanceof ITreeContentProvider)) {
return; // Doesn't occur
}
ITreeContentProvider provider = (ITreeContentProvider) rawProvider;
if (!provider.hasChildren(node)) {
return;
}
TreeItem treeItem = viewer.getItem(node);
if (treeItem == null) {
return;
}
if (!viewer.getExpandedState(node)) {
try {
viewer.getTree().setRedraw(false);
viewer.setExpandedState(node, true);
} finally {
viewer.getTree().setRedraw(true);
}
}
// Pop up search field with initial pattern set; update pattern and
// request a refresh of the node on each change with a small delay.
AtomicReference<String> currentPattern = new AtomicReference<>();
WorkbenchJob refresher = new WorkbenchJob(
UIText.RepositoriesView_FilterJob) {
@Override
public boolean belongsTo(Object family) {
return JobFamilies.REPO_VIEW_REFRESH.equals(family)
|| super.belongsTo(family);
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!monitor.isCanceled()) {
filter(viewer, node, currentPattern.get());
return Status.OK_STATUS;
}
return Status.CANCEL_STATUS;
}
};
refresher.setUser(false);
Composite container = new Composite(viewer.getTree(), SWT.NONE);
container.setVisible(false);
GridLayoutFactory.fillDefaults().applyTo(container);
// Use a text field without icons. With icons, we'd get a focusLost
// event when the icons are selected at least on Cocoa.
Text field = new Text(container, SWT.SEARCH);
GridData textData = GridDataFactory.fillDefaults().grab(true, false)
.create();
textData.minimumWidth = 150;
field.setLayoutData(textData);
field.setMessage(UIText.RepositoriesView_FilterMessage);
String pattern = node.getFilter();
if (pattern != null) {
field.setText(pattern);
field.selectAll();
}
TreeFilterEditor filterEditor = new TreeFilterEditor(viewer.getTree(),
treeItem, 0, container);
field.addVerifyListener(e -> e.text = Utils.firstLine(e.text));
field.addModifyListener(e -> {
refresher.cancel();
currentPattern.set(field.getText());
refresher.schedule(200L);
});
FocusAdapter closeOnFocusLost = new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
filterField = null;
filterEditor.dispose();
if (!container.isDisposed()) {
container.setVisible(false);
container.dispose();
}
}
};
field.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
if (key == SWT.ESC) {
// Reset
refresher.cancel();
currentPattern.set(pattern);
refresher.schedule();
}
if (key == SWT.ESC || key == SWT.CR || key == SWT.LF
|| e.character == '\r' || e.character == '\n') {
// Character tests catch NUMPAD-ENTER
filterField = null;
filterEditor.dispose();
field.removeFocusListener(closeOnFocusLost);
container.setVisible(false);
container.dispose();
viewer.getTree().setFocus();
}
}
});
container.getDisplay().asyncExec(() -> {
if (!container.isDisposed()) {
container.setVisible(true);
filterField = field;
field.setFocus();
field.addFocusListener(closeOnFocusLost);
}
});
}
private void filter(RepositoriesCommonViewer viewer,
FilterableNode filterNode, String filter) {
FilterCache.INSTANCE.set(filterNode, filter);
Tree tree = viewer.getTree();
if (tree == null || tree.isDisposed()) {
return;
}
try {
tree.setRedraw(false);
TreeItem item = viewer.getItem(filterNode);
Object currentNode = item.getData();
if (filterNode != currentNode && filterNode.equals(currentNode)) {
((FilterableNode) currentNode)
.setFilter(filterNode.getFilter());
viewer.update(currentNode, null);
}
viewer.refresh(filterNode);
} finally {
tree.setRedraw(true);
}
// Force an update of the status bar
viewer.setSelection(viewer.getSelection());
}
private void executeOpenCommandWithConfirmation(RepositoryTreeNode element,
String refName) {
if (targetIsCurrentBranch(element, refName)) {
return;
}
if (!BranchOperationUI.checkoutWillShowQuestionDialog(refName)) {
IPreferenceStore store = Activator.getDefault()
.getPreferenceStore();
if (store.getBoolean(UIPreferences.SHOW_CHECKOUT_CONFIRMATION)) {
MessageDialogWithToggle dialog = new MessageDialogWithToggle(
getViewSite().getShell(),
UIText.RepositoriesView_CheckoutConfirmationTitle, null,
MessageFormat.format(
UIText.RepositoriesView_CheckoutConfirmationMessage,
Repository.shortenRefName(refName)),
MessageDialog.QUESTION,
new String[] {
UIText.RepositoriesView_CheckoutConfirmationDefaultButtonLabel,
IDialogConstants.CANCEL_LABEL },
0,
UIText.RepositoriesView_CheckoutConfirmationToggleMessage,
false);
// Since we use a custom button here, we may get back the first
// internal ID instead of Window.OK.
int result = dialog.open();
if (result != Window.OK
&& result != IDialogConstants.INTERNAL_ID) {
return;
}
// And with custom buttons and internal IDs, the framework
// doesn't save the preference (even if we set the preference
// store and key).
if (dialog.getToggleState()) {
store.setValue(UIPreferences.SHOW_CHECKOUT_CONFIRMATION,
false);
}
}
}
executeOpenCommand(element);
}
private boolean targetIsCurrentBranch(RepositoryTreeNode element,
String refName) {
try {
Repository repository = element.getRepository();
return refName.equals(repository.getFullBranch());
} catch (IOException e) {
// ignore and just execute the checkout operation
return false;
}
}
private void executeOpenCommand(RepositoryTreeNode element) {
CommonUtils.runCommand("org.eclipse.egit.ui.RepositoriesViewOpen", //$NON-NLS-1$
new StructuredSelection(element));
}
private void executeFetchCommand(FetchNode node) {
CommonUtils.runCommand(ActionCommands.SIMPLE_FETCH_ACTION,
new StructuredSelection(node));
}
private void initRepositoriesAndListeners() {
synchronized (repositories) {
refCache.remove(repositories);
repositories.clear();
unregisterRepositoryListeners();
Set<File> dirs = new HashSet<>();
// listen for repository changes
for (String dir : RepositoryUtil.INSTANCE
.getConfiguredRepositories()) {
File repoDir = new File(dir);
try {
Repository repo = RepositoryCache.INSTANCE
.lookupRepository(repoDir);
listenToRepository(repo);
dirs.add(repo.getDirectory());
repositories.add(repo);
} catch (IOException e) {
String message = NLS
.bind(UIText.RepositoriesView_ExceptionLookingUpRepoMessage,
repoDir.getPath());
Activator.handleError(message, e, false);
RepositoryUtil.INSTANCE.removeDir(repoDir);
}
}
// Also listen to submodules and nested git repositories.
for (Repository repo : RepositoryCache.INSTANCE
.getAllRepositories()) {
if (!dirs.contains(repo.getDirectory())) {
listenToRepository(repo);
dirs.add(repo.getDirectory());
}
}
}
}
private void listenToRepository(Repository repo) {
myListeners.add(repo.getListenerList()
.addIndexChangedListener(myIndexChangedListener));
myListeners.add(repo.getListenerList()
.addRefsChangedListener(myRefsChangedListener));
myListeners.add(repo.getListenerList()
.addConfigChangedListener(myConfigChangeListener));
}
@Override
public void dispose() {
if (filterCacheLoaded) {
// Don't save if didn't load for whatever reason. Otherwise we might
// overwrite existing persisted data with an empty cache.
filterCacheLoaded = false;
FilterCache.INSTANCE.save();
}
if (textCellEditor != null) {
textCellEditor.dispose();
textCellEditor = null;
}
if (reactOnSelection != null) {
reactOnSelection.removeListener(reactOnSelectionListener);
}
if (branchHierarchy != null) {
branchHierarchy.removeListener(stateChangeListener);
}
refreshUiJob.cancel();
RepositoryUtil.INSTANCE.getPreferences().removePreferenceChangeListener(
configurationListener);
ISelectionService srv = getSite().getService(ISelectionService.class);
srv.removePostSelectionListener(selectionChangedListener);
unregisterRepositoryListeners();
refCache.remove(repositories);
refCache.dispose();
repositories.clear();
super.dispose();
}
/**
* @see #showPaths(List)
* @param resource
*/
private void showResource(final IResource resource) {
IPath location = resource.getLocation();
if (location != null)
showPaths(Arrays.asList(location));
}
/**
* Opens the tree and marks the working directory files or folders that
* represent the passed paths if possible.
*
* @param paths
* the paths to show
*/
private void showPaths(final List<IPath> paths) {
Map<Repository, Collection<String>> pathsByRepo = ResourceUtil
.splitPathsByRepository(paths);
boolean added = checkNotConfiguredRepositories(pathsByRepo);
if (added) {
scheduleRefresh(0, () -> {
if (UIUtils.isUsable(getCommonViewer())) {
selectAndReveal(pathsByRepo);
}
});
} else {
selectAndReveal(pathsByRepo);
}
}
private boolean checkNotConfiguredRepositories(
Map<Repository, Collection<String>> pathsByRepo) {
boolean added = false;
for (Repository repository : pathsByRepo.keySet()) {
try {
RepositoryTreeNode node = getRepositoryChildNode(repository,
RepositoryTreeNodeType.WORKINGDIR);
if (node == null) {
added |= RepositoryUtil.INSTANCE
.addConfiguredRepository(repository.getDirectory());
}
} catch (IllegalArgumentException iae) {
Activator.handleError(iae.getMessage(), iae, false);
continue;
}
}
return added;
}
private void selectAndReveal(
Map<Repository, Collection<String>> pathsByRepo) {
final List<RepositoryTreeNode> nodesToShow = new ArrayList<>();
Repository repository = null;
for (Map.Entry<Repository, Collection<String>> entry : pathsByRepo
.entrySet()) {
repository = entry.getKey();
for (String repoPath : entry.getValue()) {
final RepositoryTreeNode node = getNodeForPath(repository,
repoPath);
if (node != null) {
nodesToShow.add(node);
}
}
}
if (repository != null && !nodesToShow.isEmpty()
&& pathsByRepo.size() == 1) {
lastSelectedRepository = repository.getDirectory();
} else {
lastSelectedRepository = null;
}
List<?> current = getCommonViewer().getStructuredSelection().toList();
Set<?> currentlySelected = new HashSet<>(current);
if (currentlySelected.containsAll(nodesToShow)) {
getCommonViewer().getTree().showSelection();
} else {
selectReveal(new StructuredSelection(nodesToShow));
}
}
/**
* Expands the tree element for the given group
*
* @param group
*/
public void expandNodeForGroup(RepositoryGroup group) {
if (group != null) {
getCommonViewer().expandToLevel(new RepositoryGroupNode(group), 1);
}
}
/**
* Reveals and shows the given repository in the view.
*
* @param repositoryToShow
*/
public void showRepository(Repository repositoryToShow) {
ITreeContentProvider cp = (ITreeContentProvider) getCommonViewer()
.getContentProvider();
RepositoryTreeNode node = findRepositoryNode(cp,
cp.getElements(getCommonViewer().getInput()), repositoryToShow);
if (node != null) {
lastSelectedRepository = repositoryToShow.getDirectory();
selectReveal(new StructuredSelection(node));
}
}
/**
* Refresh Repositories View
*/
public void refresh() {
initRepositoriesAndListeners();
scheduleRefresh(0, null);
}
private void trace(String message) {
GitTraceLocation.getTrace().trace(
GitTraceLocation.REPOSITORIESVIEW.getLocation(), message);
}
private void scheduleRefresh() {
scheduleRefresh(DEFAULT_REFRESH_DELAY, null);
}
private synchronized void scheduleRefresh(long delay, Runnable uiTask) {
refreshUiJob.uiTask.compareAndSet(null, uiTask);
refreshUiJob.schedule(delay);
}
class RefreshUiJob extends WorkbenchJob {
final AtomicReference<Runnable> uiTask = new AtomicReference<>();
RefreshUiJob() {
super(PlatformUI.getWorkbench().getDisplay(),
"Refreshing Git Repositories View"); //$NON-NLS-1$
setSystem(true);
setUser(false);
}
@Override
public boolean belongsTo(Object family) {
return JobFamilies.REPO_VIEW_REFRESH.equals(family);
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
final boolean trace = GitTraceLocation.REPOSITORIESVIEW.isActive();
long start = 0;
if (trace) {
start = System.currentTimeMillis();
trace("Starting async update job"); //$NON-NLS-1$
}
CommonViewer tv = getCommonViewer();
if (monitor.isCanceled() || !UIUtils.isUsable(tv)) {
return Status.CANCEL_STATUS;
}
tv.refresh(true);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
IWorkbenchWindow ww = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IViewPart part = ww == null ? null
: ww.getActivePage().findView(IPageLayout.ID_PROP_SHEET);
if (part instanceof PropertySheet) {
PropertySheet sheet = (PropertySheet) part;
IPage page = sheet.getCurrentPage();
if (page instanceof PropertySheetPage) {
((PropertySheetPage) page).refresh();
}
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
Runnable task = uiTask.getAndSet(null);
if (task != null) {
task.run();
}
if (trace) {
trace("Ending async update job after " //$NON-NLS-1$
+ (System.currentTimeMillis() - start) + " ms"); //$NON-NLS-1$
}
return monitor.isCanceled() ? Status.CANCEL_STATUS
: Status.OK_STATUS;
}
}
private void unregisterRepositoryListeners() {
myListeners.forEach(ListenerHandle::remove);
myListeners.clear();
}
@Override
public boolean show(ShowInContext context) {
ISelection selection = context.getSelection();
if ((selection instanceof IStructuredSelection)
&& !selection.isEmpty()) {
IStructuredSelection ss = (IStructuredSelection) selection;
List<IPath> paths = new ArrayList<>();
for (Iterator it = ss.iterator(); it.hasNext();) {
Object element = it.next();
IResource resource = AdapterUtils.adaptToAnyResource(element);
if (resource != null) {
IPath location = resource.getLocation();
if (location != null)
paths.add(location);
} else if (element instanceof IPath)
paths.add((IPath) element);
}
if (!paths.isEmpty()) {
showPaths(paths);
return true;
}
Repository repository = SelectionUtils.getRepository(ss);
if (repository != null) {
showRepository(repository);
return true;
}
}
if(context.getInput() instanceof IFileEditorInput) {
IFileEditorInput input = (IFileEditorInput) context.getInput();
showResource(input.getFile());
return true;
}
Repository repository = Adapters.adapt(context.getInput(),
Repository.class);
if (repository != null) {
showRepository(repository);
return true;
}
return false;
}
@Override
public ShowInContext getShowInContext() {
IStructuredSelection selection = (IStructuredSelection) getCommonViewer()
.getSelection();
List<Object> elements = getShowInElements(selection);
// GenericHistoryView only shows a selection of a single resource (see
// bug 392949), so prepare our own history page input which can contain
// multiple files to support showing more than one file in history.
// It's also necessary for a single file that is outside of the
// workspace (and as such is not an IResource).
HistoryPageInput historyPageInput = getHistoryPageInput(selection);
return new ShowInContext(historyPageInput, new StructuredSelection(elements));
}
@Override
public String[] getShowInTargetIds() {
IStructuredSelection selection = (IStructuredSelection) getCommonViewer()
.getSelection();
for (Object element : selection.toList())
if (element instanceof RepositoryNode) {
return new String[] { IHistoryView.VIEW_ID, ReflogView.VIEW_ID,
StagingView.VIEW_ID };
} else if (element instanceof RefNode) {
return new String[] { IHistoryView.VIEW_ID,
ReflogView.VIEW_ID };
}
// Make sure History view is always listed, regardless of perspective
return new String[] { IHistoryView.VIEW_ID };
}
private static List<Object> getShowInElements(IStructuredSelection selection) {
List<Object> elements = new ArrayList<>();
for (Object element : selection.toList()) {
if (element instanceof FileNode || element instanceof FolderNode
|| element instanceof WorkingDirNode) {
RepositoryTreeNode treeNode = (RepositoryTreeNode) element;
IPath path = treeNode.getPath();
IResource resource = ResourceUtil.getResourceForLocation(path,
false);
if (resource != null)
elements.add(resource);
} else if (element instanceof RepositoryNode) {
// Can be shown in History, Reflog and Properties views
elements.add(element);
} else if (element instanceof RepositoryNode
|| element instanceof RemoteNode
|| element instanceof FetchNode
|| element instanceof PushNode
|| element instanceof TagNode
|| element instanceof RefNode) {
// These can be shown in Properties view directly
elements.add(element);
}
}
return elements;
}
/**
* @param selection
* @return the HistoryPageInput corresponding to the selection, or null
*/
private static HistoryPageInput getHistoryPageInput(IStructuredSelection selection) {
List<File> files = new ArrayList<>();
Repository repo = null;
for (Object element : selection.toList()) {
Repository nodeRepository;
if (element instanceof FileNode) {
FileNode fileNode = (FileNode) element;
files.add(fileNode.getObject());
nodeRepository = fileNode.getRepository();
} else if (element instanceof FolderNode) {
FolderNode folderNode = (FolderNode) element;
files.add(folderNode.getObject());
nodeRepository = folderNode.getRepository();
} else {
// Don't return input if selection is not file/folder
return null;
}
if (repo == null)
repo = nodeRepository;
// Don't return input if nodes from different repositories are selected
if (repo != nodeRepository)
return null;
}
if (repo != null)
return new HistoryPageInput(repo, files.toArray(new File[0]));
else
return null;
}
private IWorkbenchPart determinePart(IWorkbenchPart part) {
IWorkbenchPart currentPart = part;
// this may happen if we switch between editors
if (currentPart instanceof IEditorPart) {
if (currentPart instanceof MultiPageEditorPart) {
Object nestedEditor = ((MultiPageEditorPart) part)
.getSelectedPage();
if (nestedEditor instanceof IEditorPart) {
currentPart = ((IEditorPart) nestedEditor);
}
}
}
return currentPart;
}
private ISelection convertSelection(IWorkbenchPart part,
ISelection selection) {
if (part instanceof IEditorPart) {
IEditorInput input = ((IEditorPart) part).getEditorInput();
if (input instanceof IFileEditorInput) {
return new StructuredSelection(
((IFileEditorInput) input).getFile());
} else if (input instanceof IURIEditorInput) {
return new StructuredSelection(input);
}
}
return selection;
}
private void reactOnSelection(ISelection selection) {
if (layout.topControl != emptyArea
&& selection instanceof StructuredSelection) {
StructuredSelection ssel = (StructuredSelection) selection;
if (ssel.size() != 1) {
return;
}
Object selected = ssel.getFirstElement();
IResource adapted = AdapterUtils.adaptToAnyResource(selected);
if (adapted != null) {
showResource(adapted);
return;
}
if ((!(selected instanceof RepositoryNode)
&& !(selected instanceof RepositoryVirtualNode))) {
File file = Adapters.adapt(selected, File.class);
if (file != null) {
IPath path = new Path(file.getAbsolutePath());
showPaths(Arrays.asList(path));
return;
}
}
Repository repository = Adapters.adapt(selected,
Repository.class);
if (repository != null && !repository.getDirectory()
.equals(lastSelectedRepository)) {
showRepository(repository);
return;
}
}
}
private void showEditor(FileNode node) {
File file = node.getObject();
IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
IEditorPart editor = EgitUiEditorUtils.findEditor(file, page);
IEditorPart active = page.getActiveEditor();
if (editor != null && editor != active) {
window.getWorkbench().getDisplay()
.asyncExec(() -> page.bringToTop(editor));
}
}
}
private RepositoryTreeNode getRepositoryChildNode(Repository repository,
RepositoryTreeNodeType type) {
ITreeContentProvider cp = (ITreeContentProvider) getCommonViewer()
.getContentProvider();
RepositoryTreeNode repoNode = findRepositoryNode(cp,
cp.getElements(getCommonViewer().getInput()), repository);
return repoNode == null ? null : findChild(cp, repoNode, type);
}
private RepositoryTreeNode findChild(ITreeContentProvider cp,
RepositoryTreeNode root, RepositoryTreeNodeType type) {
for (Object child : cp.getChildren(root)) {
RepositoryTreeNode childNode = (RepositoryTreeNode) child;
if (childNode.getType() == type) {
return childNode;
}
}
return null;
}
private RepositoryTreeNode findRepositoryNode(
ITreeContentProvider cp, Object[] roots,
Repository repository) {
for (Object repo : roots) {
RepositoryTreeNode node = (RepositoryTreeNode) repo;
if (node instanceof RepositoryGroupNode) {
RepositoryTreeNode candidate = findRepositoryNode(cp,
cp.getChildren(node), repository);
if (candidate != null) {
return candidate;
}
} else {
// TODO equals implementation of Repository?
if (repository.getDirectory()
.equals(((Repository) node.getObject())
.getDirectory())) {
return node;
}
}
}
// Might be a submodule
for (Object repo : roots) {
RepositoryTreeNode node = (RepositoryTreeNode) repo;
RepositoryTreeNode submodules = findChild(cp, node,
RepositoryTreeNodeType.SUBMODULES);
if (submodules != null) {
RepositoryTreeNode submoduleNode = findRepositoryNode(cp,
cp.getChildren(submodules), repository);
if (submoduleNode != null) {
return submoduleNode;
}
}
}
return null;
}
private RepositoryTreeNode getNodeForPath(Repository repository,
String repoRelativePath) {
RepositoryTreeNode currentNode = getRepositoryChildNode(repository,
RepositoryTreeNodeType.WORKINGDIR);
CommonViewer viewer = getCommonViewer();
if (currentNode == null) {
return null;
} else {
// reveal repository in case working dir filter is applied
final RepositoryTreeNode workingDir = currentNode;
if (Arrays.stream(viewer.getFilters()).anyMatch(filter -> !filter
.select(viewer, workingDir.getParent(), workingDir))) {
return currentNode.getParent();
}
}
ITreeContentProvider cp = (ITreeContentProvider) viewer
.getContentProvider();
IPath relPath = new Path(repoRelativePath);
for (String segment : relPath.segments()) {
for (Object child : cp.getChildren(currentNode)) {
@SuppressWarnings("unchecked")
RepositoryTreeNode<File> childNode = (RepositoryTreeNode<File>) child;
if (childNode.getObject().getName().equals(segment)) {
currentNode = childNode;
break;
}
}
}
return currentNode;
}
/**
* Customized {@link CommonViewer} that
* <ul>
* <li>switches back to the empty area if the tree view becomes empty,
* and</li>
* <li>adds additional information at the end of labels, and</li>
* <li>provides access to the {@link TreeItem} of an element.</li>
* </ul>
*/
private class RepositoriesCommonViewer extends CommonViewer {
/**
* Creates a new {@link RepositoriesCommonViewer}.
*
* @param viewId
* of the view containing the viewer
* @param parent
* for the new viewer
* @param style
* of the new viewer
*/
public RepositoriesCommonViewer(String viewId, Composite parent,
int style) {
super(viewId, parent, style);
}
@Override
protected void init() {
super.init();
setLabelProvider(new PathAddingLabelProvider(
getNavigatorContentService().createCommonLabelProvider()));
}
@Override
public void refresh() {
super.refresh();
afterRefresh(this);
}
@Override
public void refresh(boolean updateLabels) {
super.refresh(updateLabels);
afterRefresh(this);
}
public TreeItem getItem(Object element) {
Widget item = findItem(element);
if (item instanceof TreeItem) {
return (TreeItem) item;
}
return null;
}
}
@SuppressWarnings("restriction")
private static class PathAddingLabelProvider extends
org.eclipse.ui.internal.navigator.NavigatorDecoratingLabelProvider {
public PathAddingLabelProvider(ILabelProvider commonLabelProvider) {
super(commonLabelProvider);
}
@Override
public void update(ViewerCell cell) {
RepositoryTreeNodeLabelProvider.update(cell, super::update);
}
}
/**
* A customized {@link ControlEditor} that keeps the editor control just
* behind the label of the node.
* <p>
* Note that the super class handles scolling and invokes {@link #layout()}
* when the parent is scrolled.
* </p>
*/
private static class TreeFilterEditor extends ControlEditor {
private Composite parent;
private TreeItem item;
private int columnIndex;
private Point editorSize;
public TreeFilterEditor(Composite parent, TreeItem item,
int columnIndex, Control editor) {
super(parent);
this.parent = parent;
this.item = item;
this.columnIndex = columnIndex;
editorSize = editor.computeSize(SWT.DEFAULT, SWT.DEFAULT);
setEditor(editor);
}
@Override
public void layout() {
Point textSize = null;
GC gc = null;
try {
gc = new GC(parent.getDisplay());
gc.setFont(item.getFont(columnIndex));
textSize = gc.stringExtent(item.getText(columnIndex));
} finally {
if (gc != null) {
gc.dispose();
}
}
Rectangle cell = item.getBounds(columnIndex);
Rectangle text = item.getTextBounds(columnIndex);
Rectangle area = parent.getClientArea();
area.y = cell.y;
area.x = Math.max(area.x, Math.min(text.x + textSize.x + 5,
area.x + area.width - editorSize.x));
area.width = editorSize.x;
area.height = editorSize.y;
getEditor().setBounds(area);
}
@Override
public void dispose() {
super.dispose();
parent = null;
item = null;
editorSize = null;
}
}
}