org.eclipse.egit.ui.internal.commit.CommitEditorPage.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.ui.internal.commit.CommitEditorPage.java

Source

/*******************************************************************************
 *  Copyright (c) 2011, 2012 GitHub Inc. and others.
 *  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
 *
 *  Contributors:
 *    Kevin Sawicki (GitHub Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.egit.ui.internal.commit;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.GitLabelProvider;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.dialogs.SpellcheckableMessageArea;
import org.eclipse.egit.ui.internal.history.CommitFileDiffViewer;
import org.eclipse.egit.ui.internal.history.FileDiff;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.RevWalkUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ShowInContext;

/**
 * Commit editor page class displaying author, committer, parent commits,
 * message, and file information in form sections.
 */
public class CommitEditorPage extends FormPage implements ISchedulingRule {

    private static final String SIGNED_OFF_BY = "Signed-off-by: {0} <{1}>"; //$NON-NLS-1$

    /**
     * Abbreviated length of parent id links displayed
     */
    public static final int PARENT_LENGTH = 20;

    private LocalResourceManager resources = new LocalResourceManager(JFaceResources.getResources());

    private Composite tagLabelArea;

    private Section branchSection;

    private TableViewer branchViewer;

    private Section diffSection;

    private CommitFileDiffViewer diffViewer;

    /**
     * Create commit editor page
     *
     * @param editor
     */
    public CommitEditorPage(FormEditor editor) {
        this(editor, "commitPage", UIText.CommitEditorPage_Title); //$NON-NLS-1$
    }

    /**
     * Create commit editor page
     *
     * @param editor
     * @param id
     * @param title
     */
    public CommitEditorPage(FormEditor editor, String id, String title) {
        super(editor, id, title);
    }

    private void hookExpansionGrabbing(final Section section) {
        section.addExpansionListener(new ExpansionAdapter() {

            public void expansionStateChanged(ExpansionEvent e) {
                ((GridData) section.getLayoutData()).grabExcessVerticalSpace = e.getState();
                getManagedForm().getForm().getBody().layout(true, true);
            }
        });
    }

    private Image getImage(ImageDescriptor descriptor) {
        return (Image) this.resources.get(descriptor);
    }

    private Section createSection(Composite parent, FormToolkit toolkit, int span) {
        Section section = toolkit.createSection(parent,
                ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED);
        GridDataFactory.fillDefaults().span(span, 1).grab(true, true).applyTo(section);
        return section;
    }

    private Composite createSectionClient(Section parent, FormToolkit toolkit) {
        Composite client = toolkit.createComposite(parent);
        GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2).applyTo(client);
        return client;
    }

    private boolean isSignedOffBy(PersonIdent person) {
        RevCommit commit = getCommit().getRevCommit();
        return commit.getFullMessage().indexOf(getSignedOffByLine(person)) != -1;
    }

    private String getSignedOffByLine(PersonIdent person) {
        return MessageFormat.format(SIGNED_OFF_BY, person.getName(), person.getEmailAddress());
    }

    private String replaceSignedOffByLine(String message, PersonIdent person) {
        Pattern pattern = Pattern.compile("^\\s*" + Pattern.quote(getSignedOffByLine(person)) //$NON-NLS-1$
                + "\\s*$", Pattern.MULTILINE); //$NON-NLS-1$
        return pattern.matcher(message).replaceAll(""); //$NON-NLS-1$
    }

    private Composite createUserArea(Composite parent, FormToolkit toolkit, PersonIdent person, boolean author) {
        Composite userArea = toolkit.createComposite(parent);
        GridLayoutFactory.fillDefaults().spacing(2, 2).numColumns(3).applyTo(userArea);

        Label userLabel = toolkit.createLabel(userArea, null);
        userLabel.setImage(getImage(author ? UIIcons.ELCL16_AUTHOR : UIIcons.ELCL16_COMMITTER));
        if (author)
            userLabel.setToolTipText(UIText.CommitEditorPage_TooltipAuthor);
        else
            userLabel.setToolTipText(UIText.CommitEditorPage_TooltipCommitter);

        boolean signedOff = isSignedOffBy(person);

        Text userText = new Text(userArea, SWT.FLAT | SWT.READ_ONLY);
        userText.setText(MessageFormat.format(
                author ? UIText.CommitEditorPage_LabelAuthor : UIText.CommitEditorPage_LabelCommitter,
                person.getName(), person.getEmailAddress(), person.getWhen()));
        toolkit.adapt(userText, false, false);
        userText.setData(FormToolkit.KEY_DRAW_BORDER, Boolean.FALSE);

        GridDataFactory.fillDefaults().span(signedOff ? 1 : 2, 1).applyTo(userText);
        if (signedOff) {
            Label signedOffLabel = toolkit.createLabel(userArea, null);
            signedOffLabel.setImage(getImage(UIIcons.SIGNED_OFF));
            if (author)
                signedOffLabel.setToolTipText(UIText.CommitEditorPage_TooltipSignedOffByAuthor);
            else
                signedOffLabel.setToolTipText(UIText.CommitEditorPage_TooltipSignedOffByCommitter);
        }

        return userArea;
    }

    private void updateSectionClient(Section section, Composite client, FormToolkit toolkit) {
        hookExpansionGrabbing(section);
        toolkit.paintBordersFor(client);
        section.setClient(client);
    }

    private void createHeaderArea(Composite parent, FormToolkit toolkit, int span) {
        RevCommit commit = getCommit().getRevCommit();
        Composite top = toolkit.createComposite(parent);
        GridDataFactory.fillDefaults().grab(true, false).span(span, 1).applyTo(top);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(top);

        Composite userArea = toolkit.createComposite(top);
        GridLayoutFactory.fillDefaults().spacing(2, 2).numColumns(1).applyTo(userArea);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(userArea);

        PersonIdent author = commit.getAuthorIdent();
        if (author != null)
            createUserArea(userArea, toolkit, author, true);

        PersonIdent committer = commit.getCommitterIdent();
        if (committer != null && !committer.equals(author))
            createUserArea(userArea, toolkit, committer, false);

        int count = commit.getParentCount();
        if (count > 0) {
            Composite parents = toolkit.createComposite(top);
            GridLayoutFactory.fillDefaults().spacing(2, 2).numColumns(2).applyTo(parents);
            GridDataFactory.fillDefaults().grab(false, false).applyTo(parents);

            for (int i = 0; i < count; i++) {
                final RevCommit parentCommit = commit.getParent(i);
                toolkit.createLabel(parents, UIText.CommitEditorPage_LabelParent)
                        .setForeground(toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
                final Hyperlink link = toolkit.createHyperlink(parents,
                        parentCommit.abbreviate(PARENT_LENGTH).name(), SWT.NONE);
                link.addHyperlinkListener(new HyperlinkAdapter() {

                    public void linkActivated(HyperlinkEvent e) {
                        try {
                            CommitEditor.open(new RepositoryCommit(getCommit().getRepository(), parentCommit));
                            if ((e.getStateMask() & SWT.MOD1) != 0)
                                getEditor().close(false);
                        } catch (PartInitException e1) {
                            Activator.logError("Error opening commit editor", e1);//$NON-NLS-1$
                        }
                    }
                });
            }
        }

        createTagsArea(userArea, toolkit, 2);
    }

    private List<Ref> getTags() {
        Repository repository = getCommit().getRepository();
        List<Ref> tags = new ArrayList<Ref>(repository.getTags().values());
        Collections.sort(tags, new Comparator<Ref>() {

            public int compare(Ref r1, Ref r2) {
                return Repository.shortenRefName(r1.getName())
                        .compareToIgnoreCase(Repository.shortenRefName(r2.getName()));
            }
        });
        return tags;
    }

    private void createTagsArea(Composite parent, FormToolkit toolkit, int span) {
        Composite tagArea = toolkit.createComposite(parent);
        GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).applyTo(tagArea);
        GridDataFactory.fillDefaults().span(span, 1).grab(true, false).applyTo(tagArea);
        toolkit.createLabel(tagArea, UIText.CommitEditorPage_LabelTags)
                .setForeground(toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
        tagLabelArea = toolkit.createComposite(tagArea);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(tagLabelArea);
        GridLayoutFactory.fillDefaults().spacing(1, 1).applyTo(tagLabelArea);
    }

    private void fillDiffs(FileDiff[] diffs) {
        diffViewer.setInput(diffs);
        diffSection
                .setText(MessageFormat.format(UIText.CommitEditorPage_SectionFiles, Integer.valueOf(diffs.length)));
    }

    private void fillTags(FormToolkit toolkit, List<Ref> tags) {
        for (Control child : tagLabelArea.getChildren())
            child.dispose();

        GridLayoutFactory.fillDefaults().spacing(1, 1).numColumns(tags.size()).applyTo(tagLabelArea);

        for (Ref tag : tags) {
            ObjectId id = tag.getPeeledObjectId();
            boolean annotated = id != null;
            if (id == null)
                id = tag.getObjectId();
            CLabel tagLabel = new CLabel(tagLabelArea, SWT.NONE);
            toolkit.adapt(tagLabel, false, false);
            if (annotated)
                tagLabel.setImage(getImage(UIIcons.TAG_ANNOTATED));
            else
                tagLabel.setImage(getImage(UIIcons.TAG));
            tagLabel.setText(Repository.shortenRefName(tag.getName()));
        }
    }

    private void createMessageArea(Composite parent, FormToolkit toolkit, int span) {
        Section messageSection = createSection(parent, toolkit, span);
        Composite messageArea = createSectionClient(messageSection, toolkit);

        messageSection.setText(UIText.CommitEditorPage_SectionMessage);

        RevCommit commit = getCommit().getRevCommit();
        String message = commit.getFullMessage();

        PersonIdent author = commit.getAuthorIdent();
        if (author != null)
            message = replaceSignedOffByLine(message, author);
        PersonIdent committer = commit.getCommitterIdent();
        if (committer != null)
            message = replaceSignedOffByLine(message, committer);

        SpellcheckableMessageArea textContent = new SpellcheckableMessageArea(messageArea, message, true,
                toolkit.getBorderStyle()) {

            @Override
            protected IAdaptable getDefaultTarget() {
                return new PlatformObject() {
                    public Object getAdapter(Class adapter) {
                        return Platform.getAdapterManager().getAdapter(getEditorInput(), adapter);
                    }
                };
            }

            protected void createMarginPainter() {
                // Disabled intentionally
            }

        };
        if ((toolkit.getBorderStyle() & SWT.BORDER) == 0)
            textContent.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
        GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 80).grab(true, true).applyTo(textContent);

        updateSectionClient(messageSection, messageArea, toolkit);
    }

    private void createBranchesArea(Composite parent, FormToolkit toolkit, int span) {
        branchSection = createSection(parent, toolkit, span);
        branchSection.setText(UIText.CommitEditorPage_SectionBranchesEmpty);
        Composite branchesArea = createSectionClient(branchSection, toolkit);

        branchViewer = new TableViewer(toolkit.createTable(branchesArea, SWT.V_SCROLL | SWT.H_SCROLL));
        GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 50).applyTo(branchViewer.getControl());
        branchViewer.setSorter(new ViewerSorter());
        branchViewer.setLabelProvider(new GitLabelProvider() {

            public String getText(Object element) {
                return Repository.shortenRefName(super.getText(element));
            }

        });
        branchViewer.setContentProvider(ArrayContentProvider.getInstance());
        branchViewer.getTable().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);

        updateSectionClient(branchSection, branchesArea, toolkit);
    }

    private void fillBranches(List<Ref> result) {
        branchViewer.setInput(result);
        branchSection.setText(
                MessageFormat.format(UIText.CommitEditorPage_SectionBranches, Integer.valueOf(result.size())));
    }

    private void createFilesArea(Composite parent, FormToolkit toolkit, int span) {
        diffSection = createSection(parent, toolkit, span);
        diffSection.setText(UIText.CommitEditorPage_SectionFilesEmpty);
        Composite filesArea = createSectionClient(diffSection, toolkit);

        diffViewer = new CommitFileDiffViewer(filesArea, getSite(),
                SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | toolkit.getBorderStyle());
        diffViewer.getTable().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
        GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 80).applyTo(diffViewer.getControl());
        diffViewer.setContentProvider(ArrayContentProvider.getInstance());
        diffViewer.setTreeWalk(getCommit().getRepository(), null);

        updateSectionClient(diffSection, filesArea, toolkit);
    }

    private RepositoryCommit getCommit() {
        return (RepositoryCommit) getEditor().getAdapter(RepositoryCommit.class);
    }

    /**
     * @see org.eclipse.ui.forms.editor.FormPage#createFormContent(org.eclipse.ui.forms.IManagedForm)
     */
    protected void createFormContent(IManagedForm managedForm) {
        Composite body = managedForm.getForm().getBody();
        body.addDisposeListener(new DisposeListener() {

            public void widgetDisposed(DisposeEvent e) {
                resources.dispose();
            }
        });
        FillLayout bodyLayout = new FillLayout();
        bodyLayout.marginHeight = 5;
        bodyLayout.marginWidth = 5;
        body.setLayout(bodyLayout);

        FormToolkit toolkit = managedForm.getToolkit();

        Composite displayArea = toolkit.createComposite(body);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(displayArea);

        createHeaderArea(displayArea, toolkit, 2);
        createMessageArea(displayArea, toolkit, 2);
        createFilesArea(displayArea, toolkit, 1);
        createBranchesArea(displayArea, toolkit, 1);

        loadSections();
    }

    private List<Ref> loadTags() {
        RepositoryCommit repoCommit = getCommit();
        RevCommit commit = repoCommit.getRevCommit();
        Repository repository = repoCommit.getRepository();
        List<Ref> tags = new ArrayList<Ref>();
        for (Ref tag : getTags()) {
            tag = repository.peel(tag);
            ObjectId id = tag.getPeeledObjectId();
            if (id == null)
                id = tag.getObjectId();
            if (!commit.equals(id))
                continue;
            tags.add(tag);
        }
        return tags;
    }

    private List<Ref> loadBranches() {
        Repository repository = getCommit().getRepository();
        RevCommit commit = getCommit().getRevCommit();
        RevWalk revWalk = new RevWalk(repository);
        try {
            Map<String, Ref> refsMap = new HashMap<String, Ref>();
            refsMap.putAll(repository.getRefDatabase().getRefs(Constants.R_HEADS));
            refsMap.putAll(repository.getRefDatabase().getRefs(Constants.R_REMOTES));
            return RevWalkUtils.findBranchesReachableFrom(commit, revWalk, refsMap.values());
        } catch (IOException e) {
            Activator.handleError(e.getMessage(), e, false);
            return Collections.emptyList();
        }
    }

    private void loadSections() {
        RepositoryCommit commit = getCommit();
        Job refreshJob = new Job(
                MessageFormat.format(UIText.CommitEditorPage_JobName, commit.getRevCommit().name())) {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                final List<Ref> tags = loadTags();
                final List<Ref> branches = loadBranches();
                final FileDiff[] diffs = getCommit().getDiffs();

                final ScrolledForm form = getManagedForm().getForm();
                if (UIUtils.isUsable(form))
                    form.getDisplay().syncExec(new Runnable() {

                        public void run() {
                            if (!UIUtils.isUsable(form))
                                return;

                            fillTags(getManagedForm().getToolkit(), tags);
                            fillDiffs(diffs);
                            fillBranches(branches);
                            form.layout(true, true);
                        }
                    });

                return Status.OK_STATUS;
            }
        };
        refreshJob.setRule(this);
        refreshJob.schedule();
    }

    /**
     * Refresh the editor page
     */
    public void refresh() {
        loadSections();
    }

    public boolean contains(ISchedulingRule rule) {
        return rule == this;
    }

    public boolean isConflicting(ISchedulingRule rule) {
        return rule == this;
    }

    ShowInContext getShowInContext() {
        if (diffViewer != null && diffViewer.getControl().isFocusControl())
            return diffViewer.getShowInContext();
        else
            return null;
    }

}