| /******************************************************************************* |
| * 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; |
| } |
| } |