Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wolf2016-12-06 18:05:43 +0000
committerMatthias Sohn2016-12-12 22:29:07 +0000
commitd7a674e071bd8dfdcfa7110ea54d520ef43ac8be (patch)
tree7c5bad0914ce2409a2de2772c3075a46b136df24
parent63485929cf9affdf82df44addd31212f4854c6cb (diff)
downloadegit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.tar.gz
egit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.tar.xz
egit-d7a674e071bd8dfdcfa7110ea54d520ef43ac8be.zip
Add an outline page for DiffEditorPage
Introduce a MultiPageEditorContentOutlinePage for the CommitEditor that manages outline pages for nested editors. Give the nested DiffEditorPage its own fully functional outline page. The outline page is linked to the editor page: its selection updates when the caret's position in the editor moves between file diffs. It also has a simple context menu giving access to actions for opening the files in an editor, or for showing a file diff in a compare editor. Change-Id: Iddd72105b86c3a8c5d31e6fa874bb470ad1f964c Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
-rw-r--r--org.eclipse.egit.ui/META-INF/MANIFEST.MF3
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java3
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java15
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java355
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java109
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java250
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java30
-rw-r--r--org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties2
8 files changed, 762 insertions, 5 deletions
diff --git a/org.eclipse.egit.ui/META-INF/MANIFEST.MF b/org.eclipse.egit.ui/META-INF/MANIFEST.MF
index 07ad37a5cb..6b9207fd1a 100644
--- a/org.eclipse.egit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.egit.ui/META-INF/MANIFEST.MF
@@ -28,7 +28,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.7.0,4.0.0)",
org.eclipse.debug.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional,
org.eclipse.e4.ui.css.swt.theme;resolution:=optional,
org.eclipse.jdt.core;bundle-version="[3.4.0,4.0.0)";resolution:=optional,
- org.eclipse.jdt.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional
+ org.eclipse.jdt.ui;bundle-version="[3.4.0,4.0.0)";resolution:=optional,
+ org.eclipse.ui.views;bundle-version="[3.4.0,4.0.0)"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.eclipse.egit.core;version="[4.6.0,4.7.0)",
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java
index 6b3d59d757..8b1cd16fe4 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/UIText.java
@@ -4325,6 +4325,9 @@ public class UIText extends NLS {
public static String StashEditorPage_LabelParent2;
/** */
+ public static String MultiPageEditorContentOutlinePage_NoOutline;
+
+ /** */
public static String Header_contextMenu_copy;
/** */
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java
index 8970773b0e..540dfabba0 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/CommitEditor.java
@@ -73,6 +73,7 @@ import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.MultiPageEditorSite;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.progress.UIJob;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
* Editor class to view a commit in a form editor.
@@ -142,6 +143,8 @@ public class CommitEditor extends SharedHeaderFormEditor implements
return openQuiet(commit, true);
}
+ private IContentOutlinePage outlinePage;
+
private CommitEditorPage commitPage;
private DiffEditorPage diffPage;
@@ -364,9 +367,10 @@ public class CommitEditor extends SharedHeaderFormEditor implements
@Override
public Object getAdapter(Class adapter) {
if (RepositoryCommit.class == adapter) {
- return AdapterUtils.adapt(getEditorInput(), adapter);
+ return AdapterUtils.adapt(getEditorInput(), RepositoryCommit.class);
+ } else if (IContentOutlinePage.class == adapter) {
+ return getOutlinePage();
}
-
return super.getAdapter(adapter);
}
@@ -432,6 +436,13 @@ public class CommitEditor extends SharedHeaderFormEditor implements
}
}
+ private IContentOutlinePage getOutlinePage() {
+ if (outlinePage == null) {
+ outlinePage = new MultiPageEditorContentOutlinePage(this);
+ }
+ return outlinePage;
+ }
+
@Override
public ShowInContext getShowInContext() {
IFormPage currentPage = getActivePageInstance();
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java
new file mode 100644
index 0000000000..e27a125e26
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorOutlinePage.java
@@ -0,0 +1,355 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
+ *
+ * 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
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.commit;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.egit.ui.internal.UIText;
+import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter.FileDiffRegion;
+import org.eclipse.egit.ui.internal.history.FileDiff;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.util.SafeRunnable;
+import org.eclipse.jface.viewers.IOpenListener;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.OpenEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+
+/**
+ * A {@link NestedContentOutlinePage} for the {DiffEditorPage}, displaying an
+ * outline for {@link DiffDocument}s.
+ */
+public class DiffEditorOutlinePage extends NestedContentOutlinePage {
+
+ private IDocument input;
+
+ private CopyOnWriteArrayList<IOpenListener> openListeners = new CopyOnWriteArrayList<>();
+
+ private ISelection selection;
+
+ @Override
+ public void createControl(Composite parent) {
+ super.createControl(parent);
+ TreeViewer viewer = getTreeViewer();
+ viewer.setAutoExpandLevel(2);
+ viewer.setContentProvider(new DiffContentProvider());
+ viewer.setLabelProvider(new DiffLabelProvider());
+ viewer.addOpenListener(event -> fireOpenEvent(event));
+ if (input != null) {
+ viewer.setInput(input);
+ }
+ createContextMenu(viewer);
+ if (selection != null) {
+ viewer.setSelection(selection);
+ }
+ }
+
+ /**
+ * Sets the input of the page to the given {@link IDocument}.
+ *
+ * @param input
+ * to set for the page
+ */
+ public void setInput(IDocument input) {
+ this.input = input;
+ TreeViewer viewer = getTreeViewerChecked();
+ if (viewer != null) {
+ viewer.setInput(input);
+ }
+ }
+
+ @Override
+ public void setSelection(ISelection selection) {
+ this.selection = selection;
+ TreeViewer viewer = getTreeViewerChecked();
+ if (viewer != null) {
+ super.setSelection(selection);
+ }
+ }
+
+ private TreeViewer getTreeViewerChecked() {
+ TreeViewer viewer = getTreeViewer();
+ if (viewer == null || viewer.getControl() == null
+ || viewer.getControl().isDisposed()) {
+ return null;
+ }
+ return viewer;
+ }
+
+ /**
+ * Adds a listener for selection-open in this page's viewer. Has no effect
+ * if an identical listener is already registered.
+ *
+ * @param listener
+ * to add to the page'sviewer
+ */
+ public void addOpenListener(IOpenListener listener) {
+ openListeners.addIfAbsent(listener);
+ }
+
+ /**
+ * Removes the given open listener from this page's viewer. Has no effect if
+ * the listener is not registered.
+ *
+ * @param listener
+ * to remove from this page's viewer.
+ */
+ public void removeOpenListener(IOpenListener listener) {
+ openListeners.remove(listener);
+ }
+
+ private void fireOpenEvent(OpenEvent event) {
+ for (IOpenListener listener : openListeners) {
+ SafeRunnable.run(new SafeRunnable() {
+
+ @Override
+ public void run() {
+ listener.open(event);
+ }
+ });
+ }
+ }
+
+ private void createContextMenu(TreeViewer viewer) {
+ MenuManager contextMenu = new MenuManager();
+ contextMenu.setRemoveAllWhenShown(true);
+ contextMenu.addMenuListener(menuManager -> {
+ setFocus();
+ Collection<FileDiffRegion> selected = getSelectedFileDiffs();
+ if (selected.isEmpty()) {
+ return;
+ }
+ Collection<FileDiffRegion> haveNew = selected.stream()
+ .filter(diff -> !diff.getDiff().getChange()
+ .equals(DiffEntry.ChangeType.DELETE))
+ .collect(Collectors.toList());
+ Collection<FileDiffRegion> haveOld = selected.stream()
+ .filter(diff -> !diff.getDiff().getChange()
+ .equals(DiffEntry.ChangeType.ADD))
+ .collect(Collectors.toList());
+ Collection<FileDiffRegion> existing = haveNew.stream()
+ .filter(diff -> new Path(diff.getRepository().getWorkTree()
+ .getAbsolutePath())
+ .append(diff.getDiff().getNewPath())
+ .toFile().exists())
+ .collect(Collectors.toList());
+ if (!existing.isEmpty()) {
+ menuManager.add(new Action(
+ UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
+
+ @Override
+ public void run() {
+ for (FileDiffRegion fileDiff : existing) {
+ File file = new Path(fileDiff.getRepository()
+ .getWorkTree().getAbsolutePath()).append(
+ fileDiff.getDiff().getNewPath())
+ .toFile();
+ DiffViewer.openFileInEditor(file, -1);
+ }
+ }
+ });
+ }
+ if (!haveNew.isEmpty()) {
+ menuManager.add(new Action(
+ UIText.CommitFileDiffViewer_OpenInEditorMenuLabel) {
+
+ @Override
+ public void run() {
+ for (FileDiffRegion fileDiff : haveNew) {
+ DiffViewer.openInEditor(fileDiff.getRepository(),
+ fileDiff.getDiff(), DiffEntry.Side.NEW, -1);
+ }
+ }
+ });
+ }
+ if (!haveOld.isEmpty()) {
+ menuManager.add(new Action(
+ UIText.CommitFileDiffViewer_OpenPreviousInEditorMenuLabel) {
+
+ @Override
+ public void run() {
+ for (FileDiffRegion fileDiff : haveOld) {
+ DiffViewer.openInEditor(fileDiff.getRepository(),
+ fileDiff.getDiff(), DiffEntry.Side.OLD, -1);
+ }
+ }
+ });
+ }
+ if (selected.size() == 1) {
+ menuManager.add(new Separator());
+ menuManager.add(new Action(
+ UIText.CommitFileDiffViewer_CompareMenuLabel) {
+
+ @Override
+ public void run() {
+ FileDiffRegion fileDiff = selected.iterator().next();
+ DiffViewer.showTwoWayFileDiff(fileDiff.getRepository(),
+ fileDiff.getDiff());
+ }
+ });
+ }
+ });
+ Menu menu = contextMenu.createContextMenu(viewer.getTree());
+ viewer.getTree().setMenu(menu);
+ }
+
+ private Collection<FileDiffRegion> getSelectedFileDiffs() {
+ ISelection currentSelection = getSelection();
+ List<FileDiffRegion> result = new ArrayList<>();
+ if (!currentSelection.isEmpty() && currentSelection instanceof StructuredSelection) {
+ for (Object selected : ((StructuredSelection) currentSelection).toList()) {
+ if (selected instanceof FileDiffRegion
+ && !((FileDiffRegion) selected).getDiff()
+ .isSubmodule()) {
+ result.add((FileDiffRegion) selected);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static class DiffContentProvider implements ITreeContentProvider {
+
+ private static final Object[] NOTHING = new Object[0];
+
+ public static class Folder {
+ public String name;
+
+ public List<FileDiffRegion> files;
+ }
+
+ private HashMap<String, Folder> folders = new LinkedHashMap<>();
+
+ private Map<FileDiffRegion, Folder> parents = new HashMap<>();
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ folders.clear();
+ parents.clear();
+ if (inputElement instanceof DiffDocument) {
+ computeFolders(((DiffDocument) inputElement).getFileRegions());
+ return folders.values().toArray();
+ }
+ return NOTHING;
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof Folder) {
+ return ((Folder) parentElement).files.toArray();
+ }
+ return NOTHING;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof FileDiffRegion) {
+ return parents.get(element);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ return (element instanceof Folder);
+ }
+
+ private void computeFolders(FileDiffRegion[] ranges) {
+ for (FileDiffRegion range : ranges) {
+ String path = range.getDiff().getPath();
+ int i = path.lastIndexOf('/');
+ if (i > 0) {
+ path = path.substring(0, i);
+ } else {
+ path = "/"; //$NON-NLS-1$
+ }
+ Folder folder = folders.get(path);
+ if (folder == null) {
+ folder = new Folder();
+ folder.name = path;
+ folder.files = new ArrayList<>();
+ folders.put(path, folder);
+ }
+ folder.files.add(range);
+ parents.put(range, folder);
+ }
+ }
+ }
+
+ private static class DiffLabelProvider extends LabelProvider {
+
+ private final Image FOLDER = PlatformUI.getWorkbench().getSharedImages()
+ .getImage(ISharedImages.IMG_OBJ_FOLDER);
+
+ private final ResourceManager resourceManager = new LocalResourceManager(
+ JFaceResources.getResources());
+
+ public DiffLabelProvider() {
+ super();
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof DiffContentProvider.Folder) {
+ return FOLDER;
+ }
+ if (element instanceof FileDiffRegion) {
+ FileDiff diff = ((FileDiffRegion) element).getDiff();
+ return (Image) resourceManager
+ .get(diff.getImageDescriptor(diff));
+ }
+ return super.getImage(element);
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof DiffContentProvider.Folder) {
+ return ((DiffContentProvider.Folder) element).name;
+ }
+ if (element instanceof FileDiffRegion) {
+ FileDiff diff = ((FileDiffRegion) element).getDiff();
+ String path = diff.getPath();
+ int i = path.lastIndexOf('/');
+ return path.substring(i + 1);
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public void dispose() {
+ resourceManager.dispose();
+ super.dispose();
+ }
+ }
+
+}
+
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java
index f6ac171936..589c9c9616 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/DiffEditorPage.java
@@ -46,6 +46,7 @@ import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -64,10 +65,12 @@ import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.AbstractDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
* A {@link TextEditor} wrapped as an {@link IFormPage} and specialized to
- * showing a unified diff of a whole commit.
+ * showing a unified diff of a whole commit. The editor has an associated
+ * {@link DiffEditorOutlinePage}.
*/
public class DiffEditorPage extends TextEditor
implements IFormPage, IShowInSource, IShowInTargetList {
@@ -85,9 +88,12 @@ public class DiffEditorPage extends TextEditor
private Control textControl;
+ private DiffEditorOutlinePage outlinePage;
private Annotation[] currentFoldingAnnotations;
+ private FileDiffRegion currentFileDiffRange;
+
/**
* Creates a new {@link DiffEditorPage} with the given id and title, which
* is shown on the page's tab.
@@ -116,6 +122,50 @@ public class DiffEditorPage extends TextEditor
this(editor, "diffPage", UIText.DiffEditorPage_Title); //$NON-NLS-1$
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getAdapter(Class adapter) {
+ // TODO Switch to generified signature once EGit's base dependency is
+ // Eclipse 4.6
+ if (IContentOutlinePage.class.equals(adapter)) {
+ if (outlinePage == null) {
+ outlinePage = createOutlinePage();
+ outlinePage.setInput(
+ getDocumentProvider().getDocument(getEditorInput()));
+ if (currentFileDiffRange != null) {
+ outlinePage.setSelection(
+ new StructuredSelection(currentFileDiffRange));
+ }
+ }
+ return outlinePage;
+ }
+ return super.getAdapter(adapter);
+ }
+
+ private DiffEditorOutlinePage createOutlinePage() {
+ DiffEditorOutlinePage page = new DiffEditorOutlinePage();
+ page.addSelectionChangedListener(
+ event -> doSetSelection(event.getSelection()));
+ page.addOpenListener(event -> {
+ FormEditor editor = getEditor();
+ editor.getSite().getPage().activate(editor);
+ editor.setActivePage(getId());
+ doSetSelection(event.getSelection());
+ });
+ return page;
+ }
+
+ @Override
+ public void dispose() {
+ // Nested editors are responsible for disposing their outline pages
+ // themselves.
+ if (outlinePage != null) {
+ outlinePage.dispose();
+ outlinePage = null;
+ }
+ super.dispose();
+ }
+
// TextEditor specifics:
@Override
@@ -127,6 +177,17 @@ public class DiffEditorPage extends TextEditor
ProjectionSupport projector = new ProjectionSupport(viewer,
getAnnotationAccess(), getSharedColors());
projector.install();
+ viewer.getTextWidget().addCaretListener((event) -> {
+ if (outlinePage != null) {
+ FileDiffRegion region = getFileDiffRange(event.caretOffset);
+ if (region != null && !region.equals(currentFileDiffRange)) {
+ currentFileDiffRange = region;
+ outlinePage.setSelection(new StructuredSelection(region));
+ } else {
+ currentFileDiffRange = region;
+ }
+ }
+ });
return viewer;
}
@@ -150,9 +211,36 @@ public class DiffEditorPage extends TextEditor
super.doSetInput(input);
if (input instanceof DiffEditorInput) {
setFolding();
+ FileDiffRegion region = getFileDiffRange(0);
+ currentFileDiffRange = region;
} else if (input instanceof CommitEditorInput) {
formatDiff();
+ currentFileDiffRange = null;
+ }
+ if (outlinePage != null) {
+ outlinePage.setInput(getDocumentProvider().getDocument(input));
+ if (currentFileDiffRange != null) {
+ outlinePage.setSelection(
+ new StructuredSelection(currentFileDiffRange));
+ }
+ }
+ }
+
+ @Override
+ protected void doSetSelection(ISelection selection) {
+ if (!selection.isEmpty() && selection instanceof StructuredSelection) {
+ Object selected = ((StructuredSelection) selection)
+ .getFirstElement();
+ if (selected instanceof FileDiffRegion) {
+ FileDiffRegion newRange = (FileDiffRegion) selected;
+ if (!newRange.equals(currentFileDiffRange)) {
+ currentFileDiffRange = newRange;
+ selectAndReveal(newRange.getOffset(), 0);
+ }
+ return;
+ }
}
+ super.doSetSelection(selection);
}
@Override
@@ -184,7 +272,9 @@ public class DiffEditorPage extends TextEditor
@Override
public void setActive(boolean active) {
- // Nothing to do.
+ if (active) {
+ setFocus();
+ }
}
@Override
@@ -227,6 +317,9 @@ public class DiffEditorPage extends TextEditor
if (object instanceof IMarker) {
IDE.gotoMarker(this, (IMarker) object);
return true;
+ } else if (object instanceof ISelection) {
+ doSetSelection((ISelection) object);
+ return true;
}
return false;
}
@@ -290,6 +383,17 @@ public class DiffEditorPage extends TextEditor
}
}
+ private FileDiffRegion getFileDiffRange(int widgetOffset) {
+ DiffViewer viewer = (DiffViewer) getSourceViewer();
+ int offset = viewer.widgetOffset2ModelOffset(widgetOffset);
+ IDocument document = getDocumentProvider()
+ .getDocument(getEditorInput());
+ if (document instanceof DiffDocument) {
+ return ((DiffDocument) document).findFileRegion(offset);
+ }
+ return null;
+ }
+
/**
* Gets the full unified diff of a {@link RepositoryCommit}.
*
@@ -432,4 +536,5 @@ public class DiffEditorPage extends TextEditor
}
}
+
}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java
new file mode 100644
index 0000000000..732cd09259
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/MultiPageEditorContentOutlinePage.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
+ *
+ * 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
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.commit;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.egit.core.AdapterUtils;
+import org.eclipse.egit.ui.internal.UIText;
+import org.eclipse.jface.dialogs.IPageChangedListener;
+import org.eclipse.jface.util.SafeRunnable;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.SubActionBars;
+import org.eclipse.ui.part.IPage;
+import org.eclipse.ui.part.MessagePage;
+import org.eclipse.ui.part.MultiPageEditorPart;
+import org.eclipse.ui.part.Page;
+import org.eclipse.ui.part.PageBook;
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+
+/**
+ * A {@link ContentOutlinePage} for {@link MultiPageEditorPart}s. The
+ * {@link org.eclipse.ui.views.contentoutline.ContentOutline ContentOutline}
+ * view only automatically handles outline pages of top-level editors, but not
+ * of nested editors. (It reacts only to part events, not to page change events,
+ * and moreover the MultiPageEditorPart framework does not send events when
+ * pages are added or removed.)
+ * <p>
+ * This class manages content outline pages for nested editors in its own
+ * {@link PageBook}. Nested editors can provide their outline pages as usual by
+ * adapting to {@link IContentOutlinePage}.
+ * </p>
+ */
+public class MultiPageEditorContentOutlinePage extends ContentOutlinePage {
+
+ private final MultiPageEditorPart editorPart;
+
+ private final ISelectionChangedListener globalSelectionListener = //
+ event -> fireSelectionChangedEvent(event);
+
+ private final CopyOnWriteArrayList<ISelectionChangedListener> selectionListeners = new CopyOnWriteArrayList<>();
+
+ private PageBook book;
+
+ private MessagePage emptyPage;
+
+ private IPage currentPage;
+
+ private IPageChangedListener pageListener;
+
+ private final Map<IEditorPart, IPage> pages = new HashMap<>();
+
+ private final Map<IPage, SubActionBars> bars = new HashMap<>();
+
+ /**
+ * Creates a new {@link MultiPageEditorContentOutlinePage} for the given
+ * top-level editor part. It will track page changes and create and manage
+ * outline pages for any nested {@link IEditorPart}s of that top-level
+ * editor part.
+ *
+ * @param editorPart
+ * the outline page belongs to
+ */
+ public MultiPageEditorContentOutlinePage(MultiPageEditorPart editorPart) {
+ super();
+ this.editorPart = editorPart;
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ book = new PageBook(parent, SWT.NONE);
+ emptyPage = new MessagePage();
+ emptyPage.createControl(book);
+ emptyPage
+ .setMessage(UIText.MultiPageEditorContentOutlinePage_NoOutline);
+ Object activePage = editorPart.getSelectedPage();
+ if (activePage instanceof IEditorPart) {
+ showPage(createOutlinePage((IEditorPart) activePage));
+ } else {
+ currentPage = emptyPage;
+ book.showPage(emptyPage.getControl());
+ }
+ pageListener = (event) -> {
+ Object newPage = event.getSelectedPage();
+ if (!(newPage instanceof IEditorPart)) {
+ showPage(emptyPage);
+ return;
+ }
+ IPage newOutlinePage = pages.get(newPage);
+ if (newOutlinePage == null) {
+ newOutlinePage = createOutlinePage((IEditorPart) newPage);
+ }
+ showPage(newOutlinePage);
+ };
+ editorPart.addPageChangedListener(pageListener);
+ }
+
+ @Override
+ public void dispose() {
+ if (pageListener != null) {
+ editorPart.removePageChangedListener(pageListener);
+ pageListener = null;
+ }
+ pages.clear();
+ selectionListeners.clear();
+ for (SubActionBars bar : bars.values()) {
+ bar.dispose();
+ }
+ bars.clear();
+ if (currentPage instanceof ISelectionProvider) {
+ ((ISelectionProvider) currentPage)
+ .removeSelectionChangedListener(globalSelectionListener);
+ }
+ currentPage = null;
+ if (book != null) {
+ book.dispose();
+ book = null;
+ }
+ if (emptyPage != null) {
+ emptyPage.dispose();
+ emptyPage = null;
+ }
+ }
+
+ @Override
+ public Control getControl() {
+ return book;
+ }
+
+ @Override
+ public void setFocus() {
+ // See org.eclipse.ui.part.PageBookView
+ book.setFocus();
+ currentPage.setFocus();
+ }
+
+ @Override
+ public void addSelectionChangedListener(
+ ISelectionChangedListener listener) {
+ selectionListeners.addIfAbsent(listener);
+ }
+
+ @Override
+ public ISelection getSelection() {
+ if (currentPage instanceof ISelectionProvider) {
+ return ((ISelectionProvider) currentPage).getSelection();
+ }
+ return StructuredSelection.EMPTY;
+ }
+
+ @Override
+ public void removeSelectionChangedListener(
+ ISelectionChangedListener listener) {
+ selectionListeners.remove(listener);
+ }
+
+ @Override
+ public void setSelection(ISelection selection) {
+ if (currentPage instanceof ISelectionProvider) {
+ ((ISelectionProvider) currentPage).setSelection(selection);
+ }
+ }
+
+ private void showPage(IPage page) {
+ if (page == null) {
+ page = emptyPage;
+ }
+ if (currentPage == page) {
+ return;
+ }
+ if (currentPage instanceof ISelectionProvider) {
+ ((ISelectionProvider) currentPage)
+ .removeSelectionChangedListener(globalSelectionListener);
+ }
+ SubActionBars localBars = bars.get(currentPage);
+ if (localBars != null) {
+ localBars.deactivate();
+ }
+ currentPage = page;
+ if (currentPage instanceof ISelectionProvider) {
+ ((ISelectionProvider) currentPage)
+ .addSelectionChangedListener(globalSelectionListener);
+ }
+ localBars = bars.get(currentPage);
+ Control control = page.getControl();
+ if (control == null || control.isDisposed()) {
+ page.createControl(book);
+ page.setActionBars(localBars);
+ control = page.getControl();
+ }
+ if (localBars != null) {
+ localBars.activate();
+ }
+ getSite().getActionBars().updateActionBars();
+ book.showPage(control);
+ if (currentPage instanceof ISelectionProvider) {
+ ISelection selection = ((ISelectionProvider) currentPage)
+ .getSelection();
+ fireSelectionChangedEvent(new SelectionChangedEvent(
+ (ISelectionProvider) currentPage, selection));
+ } else {
+ fireSelectionChangedEvent(
+ new SelectionChangedEvent(this, StructuredSelection.EMPTY));
+ }
+ }
+
+ private IPage createOutlinePage(IEditorPart editor) {
+ IContentOutlinePage outlinePage = AdapterUtils.adapt(editor,
+ IContentOutlinePage.class);
+ if (outlinePage == null) {
+ pages.put(editor, emptyPage);
+ return emptyPage;
+ }
+ pages.put(editor, outlinePage);
+ if (outlinePage instanceof NestedContentOutlinePage) {
+ ((Page) outlinePage).init(getSite());
+ }
+ SubActionBars pageBars = new SubActionBars(getSite().getActionBars());
+ bars.put(outlinePage, pageBars);
+ return outlinePage;
+ }
+
+ private void fireSelectionChangedEvent(SelectionChangedEvent event) {
+ for (ISelectionChangedListener listener : selectionListeners) {
+ SafeRunnable.run(new SafeRunnable() {
+
+ @Override
+ public void run() {
+ listener.selectionChanged(event);
+ }
+ });
+ }
+ }
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java
new file mode 100644
index 0000000000..87bcc78708
--- /dev/null
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/commit/NestedContentOutlinePage.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
+ *
+ * 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
+ *******************************************************************************/
+package org.eclipse.egit.ui.internal.commit;
+
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
+
+/**
+ * A {@link ContentOutlinePage} that is to be nested in a
+ * {@link MultiPageEditorContentOutlinePage}.
+ */
+public class NestedContentOutlinePage extends ContentOutlinePage {
+
+ @Override
+ public void init(IPageSite pageSite) {
+ // ContentOutlinePage insists on setting itself as selection provider.
+ // For pages nested inside a MultiPageEditorContentOutlinePage this is
+ // wrong. Save and restore the selection provider.
+ ISelectionProvider provider = pageSite.getSelectionProvider();
+ super.init(pageSite);
+ pageSite.setSelectionProvider(provider);
+ }
+}
diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties
index 014a6f4b20..639acbd1d2 100644
--- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties
+++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/uitext.properties
@@ -1494,6 +1494,8 @@ StashEditorPage_LabelParent0=HEAD:
StashEditorPage_LabelParent1=Index:
StashEditorPage_LabelParent2=Untracked:
+MultiPageEditorContentOutlinePage_NoOutline=An outline is not available.
+
Header_contextMenu_copy=&Copy
Header_contextMenu_copy_SHA1=Copy &SHA-1
Header_copy_SHA1_error_title= Problem Copying SHA-1 to Clipboard

Back to the top