blob: 7e043bbe0dc2e91ef5cafeeecc2b4ef3787aa230 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Eugene Kuleshov <eu@md.pp.ru> - Bug 173959 add mechanism for navigating from team annotation to corresponding task
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.operations;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter;
import org.eclipse.jface.text.revisions.Revision;
import org.eclipse.jface.text.revisions.RevisionInformation;
import org.eclipse.jface.text.source.LineRange;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.listeners.AnnotateListener;
import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.util.KnownRepositories;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.core.TeamPlugin;
import org.eclipse.team.ui.TeamUI;
import org.eclipse.team.ui.history.*;
import org.eclipse.ui.*;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import com.ibm.icu.text.DateFormat;
/**
* An operation to fetch the annotations for a file from the repository and
* display them in the annotations view.
*/
public class ShowAnnotationOperation extends CVSOperation {
private final ICVSResource fCVSResource;
private final String fRevision;
private final boolean fBinary;
public ShowAnnotationOperation(IWorkbenchPart part, ICVSResource cvsResource, String revision, boolean binary) {
super(part);
fCVSResource= cvsResource;
fRevision= revision;
fBinary = binary;
}
@Override
protected void execute(IProgressMonitor monitor) throws CVSException, InterruptedException {
monitor.beginTask(null, 100);
// Get the annotations from the repository.
final AnnotateListener listener= new AnnotateListener();
fetchAnnotation(listener, fCVSResource, fRevision, Policy.subMonitorFor(monitor, 80));
// this is not needed if there is no live annotate
final RevisionInformation information = createRevisionInformation(listener, Policy.subMonitorFor(monitor, 20));
// Open the view and display it from the UI thread.
final Display display= getPart().getSite().getShell().getDisplay();
display.asyncExec(() -> {
try {
// is there an open editor for the given input? If yes, use live annotate
final AbstractDecoratedTextEditor editor = getEditor(listener);
if (editor != null) {
editor.showRevisionInformation(information, "org.eclipse.quickdiff.providers.CVSReferenceProvider"); //$NON-NLS-1$
final IWorkbenchPage page = getPart().getSite().getPage();
showHistoryView(page, editor);
page.activate(editor);
}
} catch (PartInitException e) {
CVSException.wrapException(e);
}
});
monitor.done();
}
/**
* Shows the history view, creating it if necessary, but does not give it focus.
*
* @param page the workbench page to operate in
* @param editor the editor that is showing the file
* @return the history view
* @throws PartInitException
*/
private IHistoryView showHistoryView(IWorkbenchPage page, AbstractDecoratedTextEditor editor) throws PartInitException {
Object object = fCVSResource.getIResource();
if (object == null)
object = editor.getEditorInput();
IHistoryView historyView= TeamUI.showHistoryFor(page, object, null);
IHistoryPage historyPage = historyView.getHistoryPage();
if (historyPage instanceof CVSHistoryPage){
CVSHistoryPage cvsHistoryPage = (CVSHistoryPage) historyPage;
cvsHistoryPage.setMode(CVSHistoryPage.REMOTE_MODE);
// We need to call link to ensure that the history page gets linked
// even if the page input did not change
cvsHistoryPage.linkWithEditor();
}
return historyView;
}
@Override
protected String getTaskName() {
return CVSUIMessages.ShowAnnotationOperation_taskName;
}
protected boolean hasCharset(ICVSResource cvsResource, InputStream contents) {
try {
return TeamPlugin.getCharset(cvsResource.getName(), contents) != null;
} catch (IOException e) {
// Assume that the contents do have a charset
return true;
}
}
private AbstractDecoratedTextEditor getEditor(AnnotateListener listener) throws PartInitException {
IResource resource= fCVSResource.getIResource();
if (resource instanceof IFile){
return RevisionAnnotationController.openEditor(getPart().getSite().getPage(), (IFile)resource);
}
if (fCVSResource instanceof ICVSRemoteResource) {
return RevisionAnnotationController.openEditor(getPart().getSite().getPage(), fCVSResource, new RemoteAnnotationStorage((ICVSRemoteFile)fCVSResource, listener.getContents()));
}
return null;
}
private void fetchAnnotation(AnnotateListener listener, ICVSResource cvsResource, String revision, IProgressMonitor monitor) throws CVSException {
monitor = Policy.monitorFor(monitor);
monitor.beginTask(null, 100);
final ICVSFolder folder = cvsResource.getParent();
final FolderSyncInfo info = folder.getFolderSyncInfo();
final ICVSRepositoryLocation location = KnownRepositories.getInstance().getRepository(info.getRoot());
final Session session = new Session(location, folder, true /*output to console*/);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
final Command.QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
try {
CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
List<Object> localOptions = new ArrayList<>();
if (revision != null) {
localOptions.add(Annotate.makeRevisionOption(revision));
}
if (fBinary) {
localOptions.add(Annotate.FORCE_BINARY_ANNOTATE);
}
final IStatus status = Command.ANNOTATE.execute(session, Command.NO_GLOBAL_OPTIONS, localOptions.toArray(new LocalOption[localOptions.size()]), new ICVSResource[]{cvsResource}, listener, Policy.subMonitorFor(monitor, 90));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
} finally {
session.close();
}
}
private RevisionInformation createRevisionInformation(final AnnotateListener listener, IProgressMonitor monitor) throws CVSException {
Map<String, ILogEntry> logEntriesByRevision = new HashMap<>();
if (fCVSResource instanceof ICVSFile) {
try {
ILogEntry[] logEntries= ((ICVSFile) fCVSResource).getLogEntries(monitor);
for (int i= 0; i < logEntries.length; i++) {
ILogEntry entry= logEntries[i];
logEntriesByRevision.put(entry.getRevision(), entry);
}
} catch (CVSException e) {
throw e;
} catch (TeamException e) {
// XXX why does getLogEntries throw TeamException?
throw CVSException.wrapException(e);
}
}
final CommitterColors colors= CommitterColors.getDefault();
RevisionInformation info= new RevisionInformation();
class AnnotationControlCreator implements IInformationControlCreator {
private final boolean isResizable;
public AnnotationControlCreator(boolean isResizable) {
this.isResizable= isResizable;
}
@Override
public IInformationControl createInformationControl(Shell parent) {
IInformationPresenter presenter = (display, hoverInfo, presentation, maxWidth, maxHeight) -> {
// decorate header
StyleRange styleRange = new StyleRange();
styleRange.start = 0;
styleRange.length = hoverInfo.indexOf('\n');
styleRange.fontStyle = SWT.BOLD;
presentation.addStyleRange(styleRange);
return hoverInfo;
};
if (isResizable)
return new DefaultInformationControl(parent, (ToolBarManager) null, presenter);
else
return new DefaultInformationControl(parent, EditorsUI.getTooltipAffordanceString(), presenter);
}
}
info.setHoverControlCreator(new AnnotationControlCreator(false));
info.setInformationPresenterControlCreator(new AnnotationControlCreator(true));
HashMap<String, Revision> sets = new HashMap<>();
List annotateBlocks= listener.getCvsAnnotateBlocks();
for (Iterator blocks= annotateBlocks.iterator(); blocks.hasNext();) {
final CVSAnnotateBlock block= (CVSAnnotateBlock) blocks.next();
final String revisionString= block.getRevision();
Revision revision= sets.get(revisionString);
if (revision == null) {
final ILogEntry entry= logEntriesByRevision.get(revisionString);
if (entry == null)
continue;
revision= new Revision() {
private String fCommitter= null;
@Override
public Object getHoverInfo() {
return entry.getAuthor()
+ " " + entry.getRevision() + " " + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(entry.getDate()) //$NON-NLS-1$ //$NON-NLS-2$
+ "\n\n" + entry.getComment(); //$NON-NLS-1$
}
private String getCommitterId() {
if (fCommitter == null)
fCommitter= block.toString().substring(0, block.toString().indexOf(' '));
return fCommitter;
}
@Override
public String getId() {
return revisionString;
}
@Override
public Date getDate() {
return entry.getDate();
}
@Override
public RGB getColor() {
return colors.getCommitterRGB(getCommitterId());
}
@Override
public String getAuthor() {
return getCommitterId();
}
};
sets.put(revisionString, revision);
info.addRevision(revision);
}
revision.addRange(new LineRange(block.getStartLine(), block.getEndLine() - block.getStartLine() + 1));
}
return info;
}
}