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
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
;
87 * Editor class to view a commit in a form editor.
89 public class CommitEditor
extends SharedHeaderFormEditor
implements
90 RefsChangedListener
, IShowInSource
, IShowInTargetList
{
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$
100 * Open commit in editor
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
115 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
116 * @return opened editor part
117 * @throws PartInitException
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
131 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
132 * @return opened editor part or null if opening fails
135 public static final IEditorPart
openQuiet(RepositoryCommit commit
, boolean activateOnOpen
) {
137 return open(commit
, activateOnOpen
);
138 } catch (PartInitException e
) {
139 Activator
.logError(e
.getMessage(), e
);
145 * Open commit in editor
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
;
174 public void partActivated(IWorkbenchPart part
) {
175 if (part
== CommitEditor
.this) {
180 } else if (isActive
) {
187 public void partBroughtToTop(IWorkbenchPart part
) {
192 public void partClosed(IWorkbenchPart part
) {
197 public void partDeactivated(IWorkbenchPart part
) {
202 public void partOpened(IWorkbenchPart part
) {
208 private static class CommitEditorNestedSite
extends MultiPageEditorSite
{
210 public CommitEditorNestedSite(CommitEditor topLevelEditor
,
211 IEditorPart nestedEditor
) {
212 super(topLevelEditor
, nestedEditor
);
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();
229 protected IEditorSite
createSite(IEditorPart editor
) {
230 return new CommitEditorNestedSite(this, editor
);
234 * @see org.eclipse.ui.forms.editor.FormEditor#addPages()
237 protected void addPages() {
239 if (getCommit().isStash()) {
240 commitPage
= new StashEditorPage(this);
242 commitPage
= new CommitEditorPage(this);
245 diffPage
= new DiffEditorPage(this);
246 addPage(diffPage
, getEditorInput());
247 if (getCommit().getNotes().length
> 0) {
248 notePage
= new NotesEditorPage(this);
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
) {
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)
275 protected void createHeaderContents(IManagedForm headerForm
) {
276 headerForm
.addPart(new FocusManagerFormPart(headerFocusTracker
) {
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$
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
);
315 link
.setFont(JFaceResources
.getBannerFont());
316 link
.setForeground(toolkit
.getColors().getColor(
318 link
.setToolTipText(UIText
.CommitEditor_showGitRepo
);
319 link
.addHyperlinkListener(new HyperlinkAdapter() {
321 public void linkActivated(HyperlinkEvent event
) {
322 RepositoriesView view
;
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
,
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()
350 ISharedImages
.IMG_TOOL_DELETE
)));
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() {
371 public void setSelection(ISelection selection
) {
376 public void removeSelectionChangedListener(
377 ISelectionChangedListener listener
) {
382 public ISelection
getSelection() {
383 return new StructuredSelection(getCommit());
387 public void addSelectionChangedListener(
388 ISelectionChangedListener listener
) {
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
404 for (IContributionItem item
: toolbar
.getItems()) {
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
,
421 return MessageFormat
.format(UIText
.CommitEditor_TitleHeaderCommit
,
426 private int getStashIndex(Repository repo
, ObjectId id
) {
429 for (RevCommit commit
: Git
.wrap(repo
).stashList().call())
430 if (commit
.getId().equals(id
))
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
);
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
,
456 private RepositoryCommit
getCommit() {
457 return (RepositoryCommit
) getAdapter(RepositoryCommit
.class);
461 * @see org.eclipse.ui.part.MultiPageEditorPart#getAdapter(java.lang.Class)
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)
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());
489 public void dispose() {
490 CommonUtils
.getService(getSite(), IPartService
.class)
491 .removePartListener(activationListener
);
492 refListenerHandle
.remove();
493 headerFocusTracker
.dispose();
498 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
501 public void doSave(IProgressMonitor monitor
) {
502 // Save not supported
506 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
509 public void doSaveAs() {
510 // Save as not supported
514 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
517 public boolean isSaveAsAllowed() {
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$
528 public IStatus
runInUIThread(IProgressMonitor monitor
) {
529 if (!getContainer().isDisposed())
530 commitPage
.refresh();
531 return Status
.OK_STATUS
;
538 private IContentOutlinePage
getOutlinePage() {
539 if (outlinePage
== null) {
540 outlinePage
= new MultiPageEditorContentOutlinePage(this);
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();
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();