Update org.apache.commons:commons-compress to 1.25.0
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / commit / CommitEditor.java
blob1065988d0cacf2bb9769f76539c41936bb7d1a06
1 /*******************************************************************************
2 * Copyright (c) 2011, 2021 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 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
10 * Contributors:
11 * Kevin Sawicki (GitHub Inc.) - initial API and implementation
12 * Daniel Megert <daniel_megert@ch.ibm.com> - Added context menu to the Commit Editor's header text
13 * Tomasz Zarna <Tomasz.Zarna@pl.ibm.com> - Add "Revert" action to Commit Editor
14 *******************************************************************************/
15 package org.eclipse.egit.ui.internal.commit;
17 import java.text.MessageFormat;
18 import java.util.concurrent.atomic.AtomicBoolean;
20 import org.eclipse.core.runtime.Adapters;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.egit.core.RepositoryCache;
25 import org.eclipse.egit.core.internal.IRepositoryCommit;
26 import org.eclipse.egit.ui.Activator;
27 import org.eclipse.egit.ui.internal.CommonUtils;
28 import org.eclipse.egit.ui.internal.UIIcons;
29 import org.eclipse.egit.ui.internal.UIText;
30 import org.eclipse.egit.ui.internal.actions.ActionCommands;
31 import org.eclipse.egit.ui.internal.components.EditorVisibilityTracker;
32 import org.eclipse.egit.ui.internal.properties.CommitPropertySource;
33 import org.eclipse.egit.ui.internal.properties.GitPropertySheetPage;
34 import org.eclipse.egit.ui.internal.repository.RepositoriesView;
35 import org.eclipse.jface.action.Action;
36 import org.eclipse.jface.action.ActionContributionItem;
37 import org.eclipse.jface.action.ContributionManager;
38 import org.eclipse.jface.action.ControlContribution;
39 import org.eclipse.jface.action.IAction;
40 import org.eclipse.jface.action.IContributionItem;
41 import org.eclipse.jface.action.IToolBarManager;
42 import org.eclipse.jface.action.ToolBarManager;
43 import org.eclipse.jface.dialogs.IPageChangedListener;
44 import org.eclipse.jface.resource.ImageDescriptor;
45 import org.eclipse.jface.resource.JFaceResources;
46 import org.eclipse.jface.viewers.ISelection;
47 import org.eclipse.jface.viewers.ISelectionChangedListener;
48 import org.eclipse.jface.viewers.ISelectionProvider;
49 import org.eclipse.jface.viewers.StructuredSelection;
50 import org.eclipse.jgit.api.StashListCommand;
51 import org.eclipse.jgit.events.ListenerHandle;
52 import org.eclipse.jgit.events.RefsChangedEvent;
53 import org.eclipse.jgit.events.RefsChangedListener;
54 import org.eclipse.jgit.lib.ObjectId;
55 import org.eclipse.jgit.lib.Repository;
56 import org.eclipse.jgit.revwalk.RevCommit;
57 import org.eclipse.swt.SWT;
58 import org.eclipse.swt.widgets.Composite;
59 import org.eclipse.swt.widgets.Control;
60 import org.eclipse.ui.IEditorActionBarContributor;
61 import org.eclipse.ui.IEditorInput;
62 import org.eclipse.ui.IEditorPart;
63 import org.eclipse.ui.IEditorSite;
64 import org.eclipse.ui.IPartService;
65 import org.eclipse.ui.ISharedImages;
66 import org.eclipse.ui.ISources;
67 import org.eclipse.ui.IWorkbenchPartReference;
68 import org.eclipse.ui.PartInitException;
69 import org.eclipse.ui.PlatformUI;
70 import org.eclipse.ui.forms.IFormColors;
71 import org.eclipse.ui.forms.IManagedForm;
72 import org.eclipse.ui.forms.editor.IFormPage;
73 import org.eclipse.ui.forms.editor.SharedHeaderFormEditor;
74 import org.eclipse.ui.forms.events.HyperlinkAdapter;
75 import org.eclipse.ui.forms.events.HyperlinkEvent;
76 import org.eclipse.ui.forms.widgets.FormToolkit;
77 import org.eclipse.ui.forms.widgets.ImageHyperlink;
78 import org.eclipse.ui.forms.widgets.ScrolledForm;
79 import org.eclipse.ui.ide.IDE;
80 import org.eclipse.ui.menus.IMenuService;
81 import org.eclipse.ui.part.IShowInSource;
82 import org.eclipse.ui.part.IShowInTargetList;
83 import org.eclipse.ui.part.MultiPageEditorSite;
84 import org.eclipse.ui.part.ShowInContext;
85 import org.eclipse.ui.progress.UIJob;
86 import org.eclipse.ui.services.IEvaluationService;
87 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
88 import org.eclipse.ui.views.properties.IPropertySheetPage;
89 import org.eclipse.ui.views.properties.IPropertySource;
90 import org.eclipse.ui.views.properties.PropertySheetPage;
92 /**
93 * Editor class to view a commit in a form editor.
95 public class CommitEditor extends SharedHeaderFormEditor implements
96 RefsChangedListener, IShowInSource, IShowInTargetList {
98 /**
99 * ID - editor id
101 public static final String ID = "org.eclipse.egit.ui.commitEditor"; //$NON-NLS-1$
103 private static final String TOOLBAR_HEADER_ID = ID + ".header.toolbar"; //$NON-NLS-1$
106 * Open commit in editor
108 * @param commit
109 * @return opened editor part
110 * @throws PartInitException
112 public static final IEditorPart open(RepositoryCommit commit)
113 throws PartInitException {
114 return open(commit, true);
118 * Open commit in editor
120 * @param commit
121 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
122 * @return opened editor part
123 * @throws PartInitException
124 * @since 2.1
126 public static final IEditorPart open(RepositoryCommit commit, boolean activateOnOpen)
127 throws PartInitException {
128 CommitEditorInput input = new CommitEditorInput(commit);
129 return IDE.openEditor(PlatformUI.getWorkbench()
130 .getActiveWorkbenchWindow().getActivePage(), input, ID, activateOnOpen);
134 * Open commit in editor
136 * @param commit
137 * @param activateOnOpen <code>true</code> if the newly opened editor should be activated
138 * @return opened editor part or null if opening fails
139 * @since 2.1
141 public static final IEditorPart openQuiet(RepositoryCommit commit, boolean activateOnOpen) {
142 try {
143 return open(commit, activateOnOpen);
144 } catch (PartInitException e) {
145 Activator.logError(e.getMessage(), e);
146 return null;
151 * Open commit in editor
153 * @param commit
154 * @return opened editor part or null if opening fails
156 public static final IEditorPart openQuiet(RepositoryCommit commit) {
157 return openQuiet(commit, true);
160 private final AtomicBoolean runRefresh = new AtomicBoolean();
162 private IContentOutlinePage outlinePage;
164 private CommitEditorPage commitPage;
166 private DiffEditorPage diffPage;
168 private NotesEditorPage notePage;
170 private ListenerHandle refListenerHandle;
172 private FocusTracker headerFocusTracker = new FocusTracker();
174 private IToolBarManager toolbar;
176 private IPageChangedListener pageListener;
179 * Ensures that the toolbar buttons in the header are properly updated, and
180 * that the editor refreshes on {@link RefsChangedEvent}s only when visible.
181 * Created and installed in {@link #createHeaderContents(IManagedForm)},
182 * which occurs before {@link #addPages()} where we start listening for
183 * {@link RefsChangedEvent}s.
185 private EditorVisibilityTracker visibilityListener;
187 private static class CommitEditorNestedSite extends MultiPageEditorSite {
189 public CommitEditorNestedSite(CommitEditor topLevelEditor,
190 IEditorPart nestedEditor) {
191 super(topLevelEditor, nestedEditor);
194 @Override
195 public IEditorActionBarContributor getActionBarContributor() {
196 IEditorActionBarContributor globalContributor = getMultiPageEditor()
197 .getEditorSite().getActionBarContributor();
198 if (globalContributor instanceof CommitEditorActionBarContributor) {
199 return ((CommitEditorActionBarContributor) globalContributor)
200 .getTextEditorActionContributor();
202 return super.getActionBarContributor();
207 @Override
208 protected IEditorSite createSite(IEditorPart editor) {
209 return new CommitEditorNestedSite(this, editor);
213 * @see org.eclipse.ui.forms.editor.FormEditor#addPages()
215 @Override
216 protected void addPages() {
217 try {
218 if (getCommit().isStash()) {
219 commitPage = new StashEditorPage(this);
220 } else {
221 commitPage = new CommitEditorPage(this);
223 addPage(commitPage);
224 RepositoryCommit commit = getCommit();
225 if (commit != null) {
226 diffPage = new DiffEditorPage(this);
227 addPage(diffPage, new DiffEditorInput(commit));
228 if (commit.getNotes().length > 0) {
229 notePage = new NotesEditorPage(this);
230 addPage(notePage);
233 } catch (PartInitException e) {
234 Activator.error("Error adding page", e); //$NON-NLS-1$
236 refListenerHandle = RepositoryCache.INSTANCE.getGlobalListenerList()
237 .addRefsChangedListener(this);
239 pageListener = event -> {
240 IEvaluationService service = PlatformUI.getWorkbench()
241 .getService(IEvaluationService.class);
242 if (service != null) {
243 // Update enablement of "Save As..."
244 service.requestEvaluation(ISources.ACTIVE_PART_NAME);
246 if (event.getSelectedPage() == commitPage) {
247 refreshCommitPage();
250 addPageChangedListener(pageListener);
253 private IContributionItem createActionContributionItem(String commandId,
254 String title, ImageDescriptor icon) {
255 IAction action = new Action(title, icon) {
257 @Override
258 public void run() {
259 CommonUtils.runCommand(commandId,
260 new StructuredSelection(getCommit()));
263 return new ActionContributionItem(action);
267 * @see org.eclipse.ui.forms.editor.SharedHeaderFormEditor#createHeaderContents(org.eclipse.ui.forms.IManagedForm)
269 @Override
270 protected void createHeaderContents(IManagedForm headerForm) {
271 headerForm.addPart(new FocusManagerFormPart(headerFocusTracker) {
273 @Override
274 public void setDefaultFocus() {
275 headerForm.getForm().getForm().setFocus();
278 RepositoryCommit commit = getCommit();
279 ScrolledForm form = headerForm.getForm();
280 String commitName = commit.getRevCommit().name();
281 String title = getFormattedHeaderTitle(commitName);
282 HeaderText text = new HeaderText(form.getForm(), title, commitName);
283 Control textControl = text.getControl();
284 if (textControl != null) {
285 headerFocusTracker.addToFocusTracking(textControl);
287 form.setToolTipText(commitName);
288 getToolkit().decorateFormHeading(form.getForm());
290 toolbar = form.getToolBarManager();
292 ControlContribution repositoryLabelControl = new ControlContribution(
293 "repositoryLabel") { //$NON-NLS-1$
294 @Override
295 protected Control createControl(Composite parent) {
296 FormToolkit toolkit = getHeaderForm().getToolkit();
297 String label = getCommit().getRepositoryName();
299 ImageHyperlink link = new ImageHyperlink(parent, SWT.NONE);
300 // Focus tracking on this link doesn't really work. It's a
301 // focusable control inside another focusable control (the
302 // toolbar). When focus leaves this control through tabbing
303 // or deactivating the editor, the toolbar gets the focus (and
304 // possibly loses it right away again). Thus the focus tracker
305 // will always see the toolbar as the last focused control.
306 // Unfortunately there is no other way to get some text onto
307 // the first line of a FormHeading.
308 headerFocusTracker.addToFocusTracking(link);
309 link.setText(label);
310 link.setFont(JFaceResources.getBannerFont());
311 link.setForeground(toolkit.getColors().getColor(
312 IFormColors.TITLE));
313 link.setToolTipText(UIText.CommitEditor_showGitRepo);
314 link.addHyperlinkListener(new HyperlinkAdapter() {
315 @Override
316 public void linkActivated(HyperlinkEvent event) {
317 RepositoriesView view;
318 try {
319 view = (RepositoriesView) PlatformUI.getWorkbench()
320 .getActiveWorkbenchWindow().getActivePage()
321 .showView(RepositoriesView.VIEW_ID);
322 view.showRepository(getCommit().getRepository());
323 } catch (PartInitException e) {
324 Activator.handleError(
325 UIText.CommitEditor_couldNotShowRepository,
326 e, false);
331 return link;
334 toolbar.add(repositoryLabelControl);
335 visibilityListener = new EditorVisibilityTracker(this) {
337 private boolean isActive;
339 @Override
340 public void partActivated(IWorkbenchPartReference partRef) {
341 if (isMe(partRef)) {
342 if (!isActive) {
343 isActive = true;
344 updateToolbar();
346 } else if (isActive) {
347 isActive = false;
348 updateToolbar();
353 getSite().getService(IPartService.class)
354 .addPartListener(visibilityListener);
355 if (commit.isStash()) {
356 toolbar.add(createActionContributionItem(ActionCommands.STASH_APPLY,
357 UIText.CommitEditor_toolbarApplyStash,
358 UIIcons.STASH_APPLY));
359 toolbar.add(createActionContributionItem(ActionCommands.STASH_DROP,
360 UIText.CommitEditor_toolbarDeleteStash,
361 PlatformUI.getWorkbench().getSharedImages()
362 .getImageDescriptor(
363 ISharedImages.IMG_TOOL_DELETE)));
364 } else {
365 toolbar.add(createActionContributionItem(ActionCommands.TAG_ACTION,
366 UIText.CommitEditor_toolbarCreateTag, UIIcons.CREATE_TAG));
367 toolbar.add(
368 createActionContributionItem(ActionCommands.BRANCH_CREATE,
369 UIText.CommitEditor_toolbarCreateBranch,
370 UIIcons.CREATE_BRANCH));
371 toolbar.add(createActionContributionItem(ActionCommands.CHECK_OUT,
372 UIText.CommitEditor_toolbarCheckOut, UIIcons.CHECKOUT));
373 toolbar.add(createActionContributionItem(ActionCommands.CHERRY_PICK,
374 UIText.CommitEditor_toolbarCherryPick,
375 UIIcons.CHERRY_PICK));
376 toolbar.add(createActionContributionItem(ActionCommands.REVERT,
377 UIText.CommitEditor_toolbarRevert, UIIcons.REVERT));
378 toolbar.add(createActionContributionItem(
379 ActionCommands.SHOW_IN_HISTORY,
380 UIText.CommitEditor_toolbarShowInHistory, UIIcons.HISTORY));
382 addContributions(toolbar);
383 toolbar.update(true);
384 getSite().setSelectionProvider(new ISelectionProvider() {
386 @Override
387 public void setSelection(ISelection selection) {
388 // Ignored
391 @Override
392 public void removeSelectionChangedListener(
393 ISelectionChangedListener listener) {
394 // Ignored
397 @Override
398 public ISelection getSelection() {
399 return new StructuredSelection(getCommit());
402 @Override
403 public void addSelectionChangedListener(
404 ISelectionChangedListener listener) {
405 // Ignored
408 if (toolbar instanceof ToolBarManager) {
409 Control control = ((ToolBarManager) toolbar).getControl();
410 if (control != null) {
411 headerFocusTracker.addToFocusTracking(control);
416 private void updateToolbar() {
417 if (toolbar != null) {
418 // isEnabled() on a CommandContributionItem actually re-evaluates
419 // the enablement.
420 for (IContributionItem item : toolbar.getItems()) {
421 item.isEnabled();
423 toolbar.update(true);
427 private String getFormattedHeaderTitle(String commitName) {
428 if (getCommit().isStash()) {
429 int stashIndex = getStashIndex(getCommit().getRepository(),
430 getCommit().getRevCommit().getId());
431 String stashName = MessageFormat.format("stash@'{'{0}'}'", //$NON-NLS-1$
432 Integer.valueOf(stashIndex));
433 return MessageFormat.format(
434 UIText.CommitEditor_TitleHeaderStashedCommit,
435 stashName);
436 } else {
437 return MessageFormat.format(UIText.CommitEditor_TitleHeaderCommit,
438 commitName);
442 private int getStashIndex(Repository repo, ObjectId id) {
443 int index = 0;
444 try {
445 for (RevCommit commit : new StashListCommand(repo).call()) {
446 if (commit.getId().equals(id)) {
447 return index;
449 index++;
451 throw new IllegalStateException(
452 UIText.CommitEditor_couldNotFindStashCommit);
453 } catch (Exception e) {
454 String message = MessageFormat.format(
455 UIText.CommitEditor_couldNotGetStashIndex, id.name());
456 Activator.logError(message, e);
457 index = -1;
459 return index;
462 private void addContributions(IToolBarManager toolBarManager) {
463 IMenuService menuService = getSite().getService(IMenuService.class);
464 if (menuService != null
465 && toolBarManager instanceof ContributionManager) {
466 ContributionManager contributionManager = (ContributionManager) toolBarManager;
467 String toolbarUri = "toolbar:" + TOOLBAR_HEADER_ID; //$NON-NLS-1$
468 menuService.populateContributionManager(contributionManager,
469 toolbarUri);
473 private RepositoryCommit getCommit() {
474 return getAdapter(RepositoryCommit.class);
478 * @see org.eclipse.ui.part.MultiPageEditorPart#getAdapter(java.lang.Class)
480 @Override
481 public <T> T getAdapter(Class<T> adapter) {
482 if (RepositoryCommit.class == adapter) {
483 return Adapters.adapt(getEditorInput(), adapter);
484 } else if (IContentOutlinePage.class == adapter) {
485 return adapter.cast(getOutlinePage());
486 } else if (IPropertySheetPage.class == adapter) {
487 PropertySheetPage page = new GitPropertySheetPage();
488 page.setPropertySourceProvider(object -> {
489 if (object instanceof IPropertySource) {
490 return (IPropertySource) object;
492 if (object instanceof IRepositoryCommit) {
493 return new CommitPropertySource(
494 ((IRepositoryCommit) object).getRevCommit(), page);
496 return null;
498 return adapter.cast(page);
500 return super.getAdapter(adapter);
504 * @see org.eclipse.ui.forms.editor.FormEditor#init(org.eclipse.ui.IEditorSite,
505 * org.eclipse.ui.IEditorInput)
507 @Override
508 public void init(IEditorSite site, IEditorInput input)
509 throws PartInitException {
510 if (Adapters.adapt(input, RepositoryCommit.class) == null)
511 throw new PartInitException(
512 "Input could not be adapted to commit object"); //$NON-NLS-1$
513 super.init(site, input);
514 setPartName(input.getName());
515 setTitleToolTip(input.getToolTipText());
518 @Override
519 public void dispose() {
520 refListenerHandle.remove();
521 if (pageListener != null) {
522 removePageChangedListener(pageListener);
523 pageListener = null;
525 if (visibilityListener != null) {
526 getSite().getService(IPartService.class)
527 .removePartListener(visibilityListener);
528 visibilityListener = null;
530 headerFocusTracker.dispose();
531 super.dispose();
535 * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
537 @Override
538 public void doSave(IProgressMonitor monitor) {
539 // Save not supported
543 * @see org.eclipse.ui.part.EditorPart#doSaveAs()
545 @Override
546 public void doSaveAs() {
547 IEditorPart editor = getActiveEditor();
548 if (editor != null && editor.isSaveAsAllowed()) {
549 editor.doSaveAs();
554 * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
556 @Override
557 public boolean isSaveAsAllowed() {
558 IEditorPart editor = getActiveEditor();
559 return editor != null && editor.isSaveAsAllowed();
562 @Override
563 public void onRefsChanged(RefsChangedEvent event) {
564 if (getCommit().getRepository().getDirectory()
565 .equals(event.getRepository().getDirectory())) {
566 visibilityListener.runWhenVisible(() -> {
567 runRefresh.set(true);
568 if (getActivePageInstance() == commitPage) {
569 refreshCommitPage();
571 // Otherwise the pageListener will run the refresh when the
572 // commitPage becomes active
577 private void refreshCommitPage() {
578 if (!runRefresh.getAndSet(false) || getContainer().isDisposed()) {
579 return;
581 UIJob job = new UIJob("Refreshing editor") { //$NON-NLS-1$
583 @Override
584 public IStatus runInUIThread(IProgressMonitor monitor) {
585 try {
586 if (!getContainer().isDisposed()) {
587 commitPage.refresh();
589 } finally {
590 if (monitor != null) {
591 monitor.done();
594 return Status.OK_STATUS;
597 job.schedule();
600 private IContentOutlinePage getOutlinePage() {
601 if (outlinePage == null) {
602 outlinePage = new MultiPageEditorContentOutlinePage(this);
604 return outlinePage;
607 @Override
608 public ShowInContext getShowInContext() {
609 IFormPage currentPage = getActivePageInstance();
610 IShowInSource showInSource = Adapters.adapt(currentPage,
611 IShowInSource.class);
612 if (showInSource != null) {
613 return showInSource.getShowInContext();
615 return null;
618 @Override
619 public String[] getShowInTargetIds() {
620 IFormPage currentPage = getActivePageInstance();
621 IShowInTargetList targetList = Adapters.adapt(currentPage,
622 IShowInTargetList.class);
623 if (targetList != null) {
624 return targetList.getShowInTargetIds();
626 return null;
629 @Override
630 public void setFocus() {
631 // super class sets focus to form header
632 IFormPage currentPage = getActivePageInstance();
633 if (currentPage != null) {
634 currentPage.setFocus();