Better align repository hyperlink in commit viewer
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / commit / CommitEditor.java
blob7d4bc981279cc82aa0659c48f18704c268d06949
1 /*******************************************************************************
2 * Copyright (c) 2011, 2016 GitHub Inc. and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Kevin Sawicki (GitHub Inc.) - initial API and implementation
10 * Daniel Megert <daniel_megert@ch.ibm.com> - Added context menu to the Commit Editor's header text
11 * Tomasz Zarna <Tomasz.Zarna@pl.ibm.com> - Add "Revert" action to Commit Editor
12 *******************************************************************************/
13 package org.eclipse.egit.ui.internal.commit;
15 import java.text.MessageFormat;
17 import org.eclipse.core.runtime.IProgressMonitor;
18 import org.eclipse.core.runtime.IStatus;
19 import org.eclipse.core.runtime.Status;
20 import org.eclipse.egit.core.AdapterUtils;
21 import org.eclipse.egit.ui.Activator;
22 import org.eclipse.egit.ui.internal.CommonUtils;
23 import org.eclipse.egit.ui.internal.UIIcons;
24 import org.eclipse.egit.ui.internal.UIText;
25 import org.eclipse.egit.ui.internal.commit.command.CheckoutHandler;
26 import org.eclipse.egit.ui.internal.commit.command.CherryPickHandler;
27 import org.eclipse.egit.ui.internal.commit.command.CreateBranchHandler;
28 import org.eclipse.egit.ui.internal.commit.command.CreateTagHandler;
29 import org.eclipse.egit.ui.internal.commit.command.RevertHandler;
30 import org.eclipse.egit.ui.internal.commit.command.ShowInHistoryHandler;
31 import org.eclipse.egit.ui.internal.commit.command.StashApplyHandler;
32 import org.eclipse.egit.ui.internal.commit.command.StashDropHandler;
33 import org.eclipse.egit.ui.internal.repository.RepositoriesView;
34 import org.eclipse.jface.action.Action;
35 import org.eclipse.jface.action.ActionContributionItem;
36 import org.eclipse.jface.action.ContributionManager;
37 import org.eclipse.jface.action.ControlContribution;
38 import org.eclipse.jface.action.IAction;
39 import org.eclipse.jface.action.IContributionItem;
40 import org.eclipse.jface.action.IToolBarManager;
41 import org.eclipse.jface.action.ToolBarManager;
42 import org.eclipse.jface.resource.ImageDescriptor;
43 import org.eclipse.jface.resource.JFaceResources;
44 import org.eclipse.jface.viewers.ISelection;
45 import org.eclipse.jface.viewers.ISelectionChangedListener;
46 import org.eclipse.jface.viewers.ISelectionProvider;
47 import org.eclipse.jface.viewers.StructuredSelection;
48 import org.eclipse.jgit.api.Git;
49 import org.eclipse.jgit.events.ListenerHandle;
50 import org.eclipse.jgit.events.RefsChangedEvent;
51 import org.eclipse.jgit.events.RefsChangedListener;
52 import org.eclipse.jgit.lib.ObjectId;
53 import org.eclipse.jgit.lib.Repository;
54 import org.eclipse.jgit.revwalk.RevCommit;
55 import org.eclipse.swt.SWT;
56 import org.eclipse.swt.widgets.Composite;
57 import org.eclipse.swt.widgets.Control;
58 import org.eclipse.ui.IEditorActionBarContributor;
59 import org.eclipse.ui.IEditorInput;
60 import org.eclipse.ui.IEditorPart;
61 import org.eclipse.ui.IEditorSite;
62 import org.eclipse.ui.IPartListener;
63 import org.eclipse.ui.IPartService;
64 import org.eclipse.ui.ISharedImages;
65 import org.eclipse.ui.IWorkbenchPart;
66 import org.eclipse.ui.PartInitException;
67 import org.eclipse.ui.PlatformUI;
68 import org.eclipse.ui.forms.IFormColors;
69 import org.eclipse.ui.forms.IManagedForm;
70 import org.eclipse.ui.forms.editor.IFormPage;
71 import org.eclipse.ui.forms.editor.SharedHeaderFormEditor;
72 import org.eclipse.ui.forms.events.HyperlinkAdapter;
73 import org.eclipse.ui.forms.events.HyperlinkEvent;
74 import org.eclipse.ui.forms.widgets.FormToolkit;
75 import org.eclipse.ui.forms.widgets.ImageHyperlink;
76 import org.eclipse.ui.forms.widgets.ScrolledForm;
77 import org.eclipse.ui.ide.IDE;
78 import org.eclipse.ui.menus.IMenuService;
79 import org.eclipse.ui.part.IShowInSource;
80 import org.eclipse.ui.part.IShowInTargetList;
81 import org.eclipse.ui.part.MultiPageEditorSite;
82 import org.eclipse.ui.part.ShowInContext;
83 import org.eclipse.ui.progress.UIJob;
84 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
86 /**
87 * Editor class to view a commit in a form editor.
89 public class CommitEditor extends SharedHeaderFormEditor implements
90 RefsChangedListener, IShowInSource, IShowInTargetList {
92 /**
93 * ID - editor id
95 public static final String ID = "org.eclipse.egit.ui.commitEditor"; //$NON-NLS-1$
97 private static final String TOOLBAR_HEADER_ID = ID + ".header.toolbar"; //$NON-NLS-1$
99 /**
100 * Open commit in editor
102 * @param commit
103 * @return opened editor part
104 * @throws PartInitException
106 public static final IEditorPart open(RepositoryCommit commit)
107 throws PartInitException {
108 return open(commit, true);
112 * Open commit in editor
114 * @param commit
115 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
116 * @return opened editor part
117 * @throws PartInitException
118 * @since 2.1
120 public static final IEditorPart open(RepositoryCommit commit, boolean activateOnOpen)
121 throws PartInitException {
122 CommitEditorInput input = new CommitEditorInput(commit);
123 return IDE.openEditor(PlatformUI.getWorkbench()
124 .getActiveWorkbenchWindow().getActivePage(), input, ID, activateOnOpen);
128 * Open commit in editor
130 * @param commit
131 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
132 * @return opened editor part or null if opening fails
133 * @since 2.1
135 public static final IEditorPart openQuiet(RepositoryCommit commit, boolean activateOnOpen) {
136 try {
137 return open(commit, activateOnOpen);
138 } catch (PartInitException e) {
139 Activator.logError(e.getMessage(), e);
140 return null;
145 * Open commit in editor
147 * @param commit
148 * @return opened editor part or null if opening fails
150 public static final IEditorPart openQuiet(RepositoryCommit commit) {
151 return openQuiet(commit, true);
154 private IContentOutlinePage outlinePage;
156 private CommitEditorPage commitPage;
158 private DiffEditorPage diffPage;
160 private NotesEditorPage notePage;
162 private ListenerHandle refListenerHandle;
164 private FocusTracker headerFocusTracker = new FocusTracker();
166 private IToolBarManager toolbar;
168 /** Ensures that the toolbar buttons in the header are properly updated. */
169 private final IPartListener activationListener = new IPartListener() {
171 private boolean isActive;
173 @Override
174 public void partActivated(IWorkbenchPart part) {
175 if (part == CommitEditor.this) {
176 if (!isActive) {
177 isActive = true;
178 updateToolbar();
180 } else if (isActive) {
181 isActive = false;
182 updateToolbar();
186 @Override
187 public void partBroughtToTop(IWorkbenchPart part) {
188 // Nothing to do
191 @Override
192 public void partClosed(IWorkbenchPart part) {
193 // Nothing to do
196 @Override
197 public void partDeactivated(IWorkbenchPart part) {
198 // Nothing to do
201 @Override
202 public void partOpened(IWorkbenchPart part) {
203 // Nothing to do
208 private static class CommitEditorNestedSite extends MultiPageEditorSite {
210 public CommitEditorNestedSite(CommitEditor topLevelEditor,
211 IEditorPart nestedEditor) {
212 super(topLevelEditor, nestedEditor);
215 @Override
216 public IEditorActionBarContributor getActionBarContributor() {
217 IEditorActionBarContributor globalContributor = getMultiPageEditor()
218 .getEditorSite().getActionBarContributor();
219 if (globalContributor instanceof CommitEditorActionBarContributor) {
220 return ((CommitEditorActionBarContributor) globalContributor)
221 .getTextEditorActionContributor();
223 return super.getActionBarContributor();
228 @Override
229 protected IEditorSite createSite(IEditorPart editor) {
230 return new CommitEditorNestedSite(this, editor);
234 * @see org.eclipse.ui.forms.editor.FormEditor#addPages()
236 @Override
237 protected void addPages() {
238 try {
239 if (getCommit().isStash()) {
240 commitPage = new StashEditorPage(this);
241 } else {
242 commitPage = new CommitEditorPage(this);
244 addPage(commitPage);
245 diffPage = new DiffEditorPage(this);
246 addPage(diffPage, getEditorInput());
247 if (getCommit().getNotes().length > 0) {
248 notePage = new NotesEditorPage(this);
249 addPage(notePage);
251 } catch (PartInitException e) {
252 Activator.error("Error adding page", e); //$NON-NLS-1$
254 refListenerHandle = Repository.getGlobalListenerList()
255 .addRefsChangedListener(this);
258 private IContributionItem createActionContributionItem(String commandId,
259 String title, ImageDescriptor icon) {
260 IAction action = new Action(title, icon) {
262 @Override
263 public void run() {
264 CommonUtils.runCommand(commandId,
265 new StructuredSelection(getCommit()));
268 return new ActionContributionItem(action);
272 * @see org.eclipse.ui.forms.editor.SharedHeaderFormEditor#createHeaderContents(org.eclipse.ui.forms.IManagedForm)
274 @Override
275 protected void createHeaderContents(IManagedForm headerForm) {
276 headerForm.addPart(new FocusManagerFormPart(headerFocusTracker) {
278 @Override
279 public void setDefaultFocus() {
280 headerForm.getForm().getForm().setFocus();
283 RepositoryCommit commit = getCommit();
284 ScrolledForm form = headerForm.getForm();
285 String commitName = commit.getRevCommit().name();
286 String title = getFormattedHeaderTitle(commitName);
287 HeaderText text = new HeaderText(form.getForm(), title, commitName);
288 Control textControl = text.getControl();
289 if (textControl != null) {
290 headerFocusTracker.addToFocusTracking(textControl);
292 form.setToolTipText(commitName);
293 getToolkit().decorateFormHeading(form.getForm());
295 toolbar = form.getToolBarManager();
297 ControlContribution repositoryLabelControl = new ControlContribution(
298 "repositoryLabel") { //$NON-NLS-1$
299 @Override
300 protected Control createControl(Composite parent) {
301 FormToolkit toolkit = getHeaderForm().getToolkit();
302 String label = getCommit().getRepositoryName();
304 ImageHyperlink link = new ImageHyperlink(parent, SWT.NONE);
305 // Focus tracking on this link doesn't really work. It's a
306 // focusable control inside another focusable control (the
307 // toolbar). When focus leaves this control through tabbing
308 // or deactivating the editor, the toolbar gets the focus (and
309 // possibly loses it right away again). Thus the focus tracker
310 // will always see the toolbar as the last focused control.
311 // Unfortunately there is no other way to get some text onto
312 // the first line of a FormHeading.
313 headerFocusTracker.addToFocusTracking(link);
314 link.setText(label);
315 link.setFont(JFaceResources.getBannerFont());
316 link.setForeground(toolkit.getColors().getColor(
317 IFormColors.TITLE));
318 link.setToolTipText(UIText.CommitEditor_showGitRepo);
319 link.addHyperlinkListener(new HyperlinkAdapter() {
320 @Override
321 public void linkActivated(HyperlinkEvent event) {
322 RepositoriesView view;
323 try {
324 view = (RepositoriesView) PlatformUI.getWorkbench()
325 .getActiveWorkbenchWindow().getActivePage()
326 .showView(RepositoriesView.VIEW_ID);
327 view.showRepository(getCommit().getRepository());
328 } catch (PartInitException e) {
329 Activator.handleError(
330 UIText.CommitEditor_couldNotShowRepository,
331 e, false);
336 return link;
339 toolbar.add(repositoryLabelControl);
340 CommonUtils.getService(getSite(), IPartService.class)
341 .addPartListener(activationListener);
342 if (commit.isStash()) {
343 toolbar.add(createActionContributionItem(StashApplyHandler.ID,
344 UIText.CommitEditor_toolbarApplyStash,
345 UIIcons.STASH_APPLY));
346 toolbar.add(createActionContributionItem(StashDropHandler.ID,
347 UIText.CommitEditor_toolbarDeleteStash,
348 PlatformUI.getWorkbench().getSharedImages()
349 .getImageDescriptor(
350 ISharedImages.IMG_TOOL_DELETE)));
351 } else {
352 toolbar.add(createActionContributionItem(CreateTagHandler.ID,
353 UIText.CommitEditor_toolbarCreateTag, UIIcons.TAG));
354 toolbar.add(createActionContributionItem(CreateBranchHandler.ID,
355 UIText.CommitEditor_toolbarCreateBranch, UIIcons.BRANCH));
356 toolbar.add(createActionContributionItem(CheckoutHandler.ID,
357 UIText.CommitEditor_toolbarCheckOut, UIIcons.CHECKOUT));
358 toolbar.add(createActionContributionItem(CherryPickHandler.ID,
359 UIText.CommitEditor_toolbarCherryPick,
360 UIIcons.CHERRY_PICK));
361 toolbar.add(createActionContributionItem(RevertHandler.ID,
362 UIText.CommitEditor_toolbarRevert, UIIcons.REVERT));
363 toolbar.add(createActionContributionItem(ShowInHistoryHandler.ID,
364 UIText.CommitEditor_toolbarShowInHistory, UIIcons.HISTORY));
366 addContributions(toolbar);
367 toolbar.update(true);
368 getSite().setSelectionProvider(new ISelectionProvider() {
370 @Override
371 public void setSelection(ISelection selection) {
372 // Ignored
375 @Override
376 public void removeSelectionChangedListener(
377 ISelectionChangedListener listener) {
378 // Ignored
381 @Override
382 public ISelection getSelection() {
383 return new StructuredSelection(getCommit());
386 @Override
387 public void addSelectionChangedListener(
388 ISelectionChangedListener listener) {
389 // Ignored
392 if (toolbar instanceof ToolBarManager) {
393 Control control = ((ToolBarManager) toolbar).getControl();
394 if (control != null) {
395 headerFocusTracker.addToFocusTracking(control);
400 private void updateToolbar() {
401 if (toolbar != null) {
402 // isEnabled() on a CommandContributionItem actually re-evaluates
403 // the enablement.
404 for (IContributionItem item : toolbar.getItems()) {
405 item.isEnabled();
407 toolbar.update(true);
411 private String getFormattedHeaderTitle(String commitName) {
412 if (getCommit().isStash()) {
413 int stashIndex = getStashIndex(getCommit().getRepository(),
414 getCommit().getRevCommit().getId());
415 String stashName = MessageFormat.format("stash@'{'{0}'}'", //$NON-NLS-1$
416 Integer.valueOf(stashIndex));
417 return MessageFormat.format(
418 UIText.CommitEditor_TitleHeaderStashedCommit,
419 stashName);
420 } else {
421 return MessageFormat.format(UIText.CommitEditor_TitleHeaderCommit,
422 commitName);
426 private int getStashIndex(Repository repo, ObjectId id) {
427 int index = 0;
428 try {
429 for (RevCommit commit : Git.wrap(repo).stashList().call())
430 if (commit.getId().equals(id))
431 return index;
432 else
433 index++;
434 throw new IllegalStateException(
435 UIText.CommitEditor_couldNotFindStashCommit);
436 } catch (Exception e) {
437 String message = MessageFormat.format(
438 UIText.CommitEditor_couldNotGetStashIndex, id.name());
439 Activator.logError(message, e);
440 index = -1;
442 return index;
445 private void addContributions(IToolBarManager toolBarManager) {
446 IMenuService menuService = CommonUtils.getService(getSite(), IMenuService.class);
447 if (menuService != null
448 && toolBarManager instanceof ContributionManager) {
449 ContributionManager contributionManager = (ContributionManager) toolBarManager;
450 String toolbarUri = "toolbar:" + TOOLBAR_HEADER_ID; //$NON-NLS-1$
451 menuService.populateContributionManager(contributionManager,
452 toolbarUri);
456 private RepositoryCommit getCommit() {
457 return (RepositoryCommit) getAdapter(RepositoryCommit.class);
461 * @see org.eclipse.ui.part.MultiPageEditorPart#getAdapter(java.lang.Class)
463 @Override
464 public Object getAdapter(Class adapter) {
465 if (RepositoryCommit.class == adapter) {
466 return AdapterUtils.adapt(getEditorInput(), RepositoryCommit.class);
467 } else if (IContentOutlinePage.class == adapter) {
468 return getOutlinePage();
470 return super.getAdapter(adapter);
474 * @see org.eclipse.ui.forms.editor.FormEditor#init(org.eclipse.ui.IEditorSite,
475 * org.eclipse.ui.IEditorInput)
477 @Override
478 public void init(IEditorSite site, IEditorInput input)
479 throws PartInitException {
480 if (AdapterUtils.adapt(input, RepositoryCommit.class) == null)
481 throw new PartInitException(
482 "Input could not be adapted to commit object"); //$NON-NLS-1$
483 super.init(site, input);
484 setPartName(input.getName());
485 setTitleToolTip(input.getToolTipText());
488 @Override
489 public void dispose() {
490 CommonUtils.getService(getSite(), IPartService.class)
491 .removePartListener(activationListener);
492 refListenerHandle.remove();
493 headerFocusTracker.dispose();
494 super.dispose();
498 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
500 @Override
501 public void doSave(IProgressMonitor monitor) {
502 // Save not supported
506 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
508 @Override
509 public void doSaveAs() {
510 // Save as not supported
514 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
516 @Override
517 public boolean isSaveAsAllowed() {
518 return false;
521 @Override
522 public void onRefsChanged(RefsChangedEvent event) {
523 if (getCommit().getRepository().getDirectory()
524 .equals(event.getRepository().getDirectory())) {
525 UIJob job = new UIJob("Refreshing editor") { //$NON-NLS-1$
527 @Override
528 public IStatus runInUIThread(IProgressMonitor monitor) {
529 if (!getContainer().isDisposed())
530 commitPage.refresh();
531 return Status.OK_STATUS;
534 job.schedule();
538 private IContentOutlinePage getOutlinePage() {
539 if (outlinePage == null) {
540 outlinePage = new MultiPageEditorContentOutlinePage(this);
542 return outlinePage;
545 @Override
546 public ShowInContext getShowInContext() {
547 IFormPage currentPage = getActivePageInstance();
548 IShowInSource showInSource = AdapterUtils.adapt(currentPage,
549 IShowInSource.class);
550 if (showInSource != null) {
551 return showInSource.getShowInContext();
553 return null;
556 @Override
557 public String[] getShowInTargetIds() {
558 IFormPage currentPage = getActivePageInstance();
559 IShowInTargetList targetList = AdapterUtils.adapt(currentPage,
560 IShowInTargetList.class);
561 if (targetList != null) {
562 return targetList.getShowInTargetIds();
564 return null;