| /******************************************************************************* |
| * Copyright (c) 2006, 2015 Wind River Systems 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: |
| * IBM Corporation - initial API and implementation |
| * Wind River Systems - initial API and implementation |
| * Tensilica (Abeer Bagul) - fix for bug 391185 |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.debug.ui.sourcelookup; |
| |
| import java.io.File; |
| import java.net.URI; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.debug.internal.core.sourcelookup.CSourceNotFoundElement; |
| import org.eclipse.cdt.debug.internal.ui.sourcelookup.CSourceNotFoundEditorInput; |
| import org.eclipse.cdt.debug.ui.ICDebugUIConstants; |
| import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.DsfExecutor; |
| import org.eclipse.cdt.dsf.concurrent.DsfRunnable; |
| import org.eclipse.cdt.dsf.concurrent.Query; |
| import org.eclipse.cdt.dsf.concurrent.ThreadSafe; |
| import org.eclipse.cdt.dsf.datamodel.DMContexts; |
| import org.eclipse.cdt.dsf.datamodel.IDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; |
| import org.eclipse.cdt.dsf.debug.service.IStack; |
| import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMData; |
| import org.eclipse.cdt.dsf.debug.sourcelookup.DsfSourceLookupParticipant; |
| import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController; |
| import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController.ISteppingControlParticipant; |
| import org.eclipse.cdt.dsf.debug.ui.viewmodel.SteppingController.SteppingTimedOutEvent; |
| import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin; |
| import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; |
| import org.eclipse.cdt.dsf.service.DsfServicesTracker; |
| import org.eclipse.cdt.dsf.service.DsfSession; |
| import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext; |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; |
| import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; |
| import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; |
| import org.eclipse.debug.ui.DebugUITools; |
| import org.eclipse.debug.ui.ISourcePresentation; |
| import org.eclipse.debug.ui.sourcelookup.ISourceDisplay; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextOperationTarget; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IEditorDescriptor; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IReusableEditor; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.editors.text.EditorsUI; |
| import org.eclipse.ui.ide.FileStoreEditorInput; |
| import org.eclipse.ui.ide.IDE; |
| import org.eclipse.ui.part.FileEditorInput; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| /** |
| * Source display adapter that performs the source lookup, opens the editor, and |
| * paints the IP for the given object. |
| * <p> |
| * The implementation relies on three types of jobs to perform the |
| * operations.<br> |
| * - The first kind, "lookup job" performs the source lookup operation. <br> |
| * - The second "display job" positions and annotates the editor. <br> |
| * - The third clears the old IP annotations when a thread or process has |
| * resumed or exited. |
| * </p> |
| * <p> |
| * The the lookup jobs can run in parallel with the display or the clearing job, |
| * but the clearing job and the display job must not run at the same time. Hence |
| * there is some involved logic which ensures that the jobs are run in proper |
| * order. To avoid race conditions, this logic uses the session's dispatch |
| * thread to synchronize access to the state data of the running jobs. |
| * </p> |
| * <p> |
| * Debuggers can override the default source editor used by the source display |
| * adapter by registering their own ISourcePresentation adapter. |
| * </p> |
| * |
| * @see ISourcePresentation |
| * |
| * @since 1.0 |
| */ |
| @ThreadSafe |
| public class DsfSourceDisplayAdapter implements ISourceDisplay, ISteppingControlParticipant { |
| private static final class FrameData { |
| IFrameDMContext fDmc; |
| int fLine; |
| int fLevel; |
| String fFile; |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| FrameData other = (FrameData) obj; |
| if (!fDmc.equals(other.fDmc)) { |
| return false; |
| } |
| if (fFile == null) { |
| if (other.fFile != null) { |
| return false; |
| } |
| } else if (!fFile.equals(other.fFile)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Test whether the given frame data instance refers to the very same |
| * location. |
| * |
| * @param frameData |
| * @return <code>true</code> if the frame data refers to the same |
| * location |
| */ |
| public boolean isIdentical(FrameData frameData) { |
| return equals(frameData) && fLine == frameData.fLine; |
| } |
| } |
| |
| /** |
| * A job to perform source lookup on the given DMC. |
| */ |
| class LookupJob extends Job { |
| |
| private final IWorkbenchPage fPage; |
| private final FrameData fFrameData; |
| private final boolean fEventTriggered; |
| |
| /** |
| * Constructs a new source lookup job. |
| */ |
| public LookupJob(FrameData frameData, IWorkbenchPage page, boolean eventTriggered) { |
| super("DSF Source Lookup"); //$NON-NLS-1$ |
| setPriority(Job.INTERACTIVE); |
| setSystem(true); |
| fFrameData = frameData; |
| fPage = page; |
| fEventTriggered = eventTriggered; |
| } |
| |
| IDMContext getDmc() { |
| return fFrameData.fDmc; |
| } |
| |
| @Override |
| protected IStatus run(final IProgressMonitor monitor) { |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| |
| final SourceLookupResult result = performLookup(); |
| executeFromJob(new DsfRunnable() { |
| @Override |
| public void run() { |
| if (!monitor.isCanceled()) { |
| fPrevResult = result; |
| fPrevFrameData = fFrameData; |
| fRunningLookupJob = null; |
| startDisplayJob(fPrevResult, fFrameData, fPage, fEventTriggered); |
| } |
| } |
| }); |
| return Status.OK_STATUS; |
| } |
| |
| private SourceLookupResult performLookup() { |
| IDMContext dmc = fFrameData.fDmc; |
| SourceLookupResult result = new SourceLookupResult(dmc, null, null, null); |
| String editorId = null; |
| IEditorInput editorInput = null; |
| Object sourceElement = fSourceLookup.getSourceElement(dmc); |
| |
| if (sourceElement == null) { |
| editorInput = new CSourceNotFoundEditorInput( |
| new CSourceNotFoundElement(dmc, fSourceLookup.getLaunchConfiguration(), fFrameData.fFile)); |
| editorId = ICDebugUIConstants.CSOURCENOTFOUND_EDITOR_ID; |
| } else { |
| ISourcePresentation presentation = null; |
| if (fSourceLookup instanceof ISourcePresentation) { |
| presentation = (ISourcePresentation) fSourceLookup; |
| } else { |
| if (dmc != null) { |
| presentation = dmc.getAdapter(ISourcePresentation.class); |
| } |
| } |
| if (presentation != null) { |
| editorInput = presentation.getEditorInput(sourceElement); |
| if (editorInput != null) { |
| editorId = presentation.getEditorId(editorInput, sourceElement); |
| } |
| } else if (sourceElement instanceof IFile) { |
| editorId = getEditorIdForFilename(((IFile) sourceElement).getName()); |
| editorInput = new FileEditorInput((IFile) sourceElement); |
| } else if (sourceElement instanceof ITranslationUnit) { |
| try { |
| URI uriLocation = ((ITranslationUnit) sourceElement).getLocationURI(); |
| IFileStore fileStore = EFS.getStore(uriLocation); |
| editorInput = new FileStoreEditorInput(fileStore); |
| editorId = getEditorIdForFilename(fileStore.getName()); |
| } catch (CoreException e) { |
| editorInput = new CSourceNotFoundEditorInput(new CSourceNotFoundElement(dmc, |
| fSourceLookup.getLaunchConfiguration(), fFrameData.fFile)); |
| editorId = ICDebugUIConstants.CSOURCENOTFOUND_EDITOR_ID; |
| } |
| } else if (sourceElement instanceof LocalFileStorage) { |
| File file = ((LocalFileStorage) sourceElement).getFile(); |
| IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(file); |
| editorInput = new FileStoreEditorInput(fileStore); |
| editorId = getEditorIdForFilename(file.getName()); |
| } |
| } |
| result.setEditorInput(editorInput); |
| result.setEditorId(editorId); |
| result.setSourceElement(sourceElement); |
| |
| return result; |
| } |
| |
| private String getEditorIdForFilename(String filename) { |
| try { |
| IEditorDescriptor descriptor = IDE.getEditorDescriptor(filename); |
| return descriptor.getId(); |
| } catch (PartInitException exc) { |
| DsfUIPlugin.log(exc); |
| } |
| return "org.eclipse.ui.DefaultTextEditor"; //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Job that positions the editor and paints the IP Annotation for given DMC. |
| */ |
| class DisplayJob extends UIJob { |
| private final SourceLookupResult fResult; |
| private final IWorkbenchPage fPage; |
| private final FrameData fFrameData; |
| |
| private final DsfRunnable fDisplayJobFinishedRunnable = new DsfRunnable() { |
| @Override |
| public void run() { |
| /* |
| * If the current display job does not match up with "this", it |
| * means that this job got canceled after it already completed |
| * and after this runnable was queued into the dispatch thread. |
| */ |
| if (fRunningDisplayJob == DisplayJob.this) { |
| fRunningDisplayJob = null; |
| if (fEventTriggered && !fDoneStepping.getAndSet(true)) { |
| doneStepping(fResult.getDmc()); |
| } |
| serviceDisplayAndClearingJobs(); |
| } |
| } |
| }; |
| |
| private final AtomicBoolean fDoneStepping = new AtomicBoolean(false); |
| private IRegion fRegion; |
| private ITextViewer fTextViewer; |
| private final boolean fEventTriggered; |
| |
| IDMContext getDmc() { |
| return fResult.getDmc(); |
| } |
| |
| /** |
| * Constructs a new source display job |
| */ |
| public DisplayJob(SourceLookupResult result, FrameData frameData, IWorkbenchPage page, boolean eventTriggered) { |
| super("Debug Source Display"); //$NON-NLS-1$ |
| setSystem(true); |
| setPriority(Job.INTERACTIVE); |
| fResult = result; |
| fFrameData = frameData; |
| fPage = page; |
| fEventTriggered = eventTriggered; |
| } |
| |
| @Override |
| public IStatus runInUIThread(final IProgressMonitor monitor) { |
| |
| if (monitor.isCanceled()) { |
| executeFromJob(fDisplayJobFinishedRunnable); |
| return Status.CANCEL_STATUS; |
| } |
| |
| if (fRegion != null && fTextViewer != null) { |
| if (fRunningDisplayJob == this) { |
| if (!shouldCancelSelectionChange()) { |
| enableLineBackgroundPainter(); |
| fTextViewer.setSelectedRange(fRegion.getOffset(), 0); |
| } |
| executeFromJob(fDisplayJobFinishedRunnable); |
| } |
| } else { |
| IEditorPart editor = openEditor(fResult, fPage); |
| if (editor == null) { |
| executeFromJob(fDisplayJobFinishedRunnable); |
| return Status.OK_STATUS; |
| } |
| |
| ITextEditor textEditor = null; |
| if (editor instanceof ITextEditor) { |
| textEditor = (ITextEditor) editor; |
| } else { |
| textEditor = editor.getAdapter(ITextEditor.class); |
| } |
| if (textEditor != null) { |
| if (positionEditor(textEditor, fFrameData)) { |
| return Status.OK_STATUS; |
| } |
| } |
| executeFromJob(fDisplayJobFinishedRunnable); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| private boolean shouldCancelSelectionChange() { |
| Query<Boolean> delaySelectionChangeQuery = new Query<Boolean>() { |
| @Override |
| protected void execute(DataRequestMonitor<Boolean> rm) { |
| IExecutionDMContext execCtx = DMContexts.getAncestorOfType(fFrameData.fDmc, |
| IExecutionDMContext.class); |
| |
| IRunControl runControl = fServicesTracker.getService(IRunControl.class); |
| rm.setData(runControl != null && execCtx != null |
| && (fController != null && fController.getPendingStepCount(execCtx) != 0 |
| || runControl.isStepping(execCtx))); |
| rm.done(); |
| } |
| }; |
| |
| try { |
| fExecutor.execute(delaySelectionChangeQuery); |
| } catch (RejectedExecutionException e) { |
| return false; |
| } |
| |
| try { |
| return delaySelectionChangeQuery.get(); |
| } catch (InterruptedException e) { |
| return false; |
| } catch (ExecutionException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Opens the editor used to display the source for an element selected |
| * in this view and returns the editor that was opened or |
| * <code>null</code> if no editor could be opened. |
| */ |
| private IEditorPart openEditor(SourceLookupResult result, IWorkbenchPage page) { |
| IEditorInput input = result.getEditorInput(); |
| String id = result.getEditorId(); |
| if (input == null || id == null) { |
| return null; |
| } |
| |
| return openEditor(page, input, id); |
| } |
| |
| /** |
| * Opens an editor in the workbench and returns the editor that was |
| * opened or <code>null</code> if an error occurred while attempting to |
| * open the editor. |
| */ |
| private IEditorPart openEditor(final IWorkbenchPage page, final IEditorInput input, final String id) { |
| final IEditorPart[] editor = new IEditorPart[] { null }; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| if (!page.getWorkbenchWindow().getWorkbench().isClosing()) { |
| try { |
| if (input instanceof CSourceNotFoundEditorInput) { |
| /* |
| * Don't open additional source not found |
| * editors if there is one to reuse. |
| */ |
| editor[0] = page.openEditor(input, id, false, IWorkbenchPage.MATCH_ID); |
| if (editor[0] instanceof IReusableEditor) { |
| IReusableEditor re = (IReusableEditor) editor[0]; |
| if (!input.equals(re.getEditorInput())) { |
| re.setInput(input); |
| } |
| } |
| } else { |
| editor[0] = page.openEditor(input, id, false); |
| } |
| } catch (PartInitException e) { |
| } |
| } |
| } |
| }; |
| BusyIndicator.showWhile(Display.getDefault(), r); |
| return editor[0]; |
| } |
| |
| /** |
| * Positions the text editor for the given stack frame |
| */ |
| private boolean positionEditor(ITextEditor editor, final FrameData frameData) { |
| // Position and annotate the editor. |
| fRegion = getLineInformation(editor, frameData.fLine); |
| if (fRegion != null) { |
| // add annotation |
| fIPManager.addAnnotation(editor, frameData.fDmc, new Position(fRegion.getOffset(), fRegion.getLength()), |
| frameData.fLevel == 0); |
| |
| // this is a dirty trick to get access to the ITextViewer of the |
| // editor |
| Object tot = editor.getAdapter(ITextOperationTarget.class); |
| if (tot instanceof ITextViewer) { |
| fTextViewer = (ITextViewer) tot; |
| int widgetLine = frameData.fLine; |
| if (tot instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 ext5 = (ITextViewerExtension5) tot; |
| // expand region if collapsed |
| ext5.exposeModelRange(fRegion); |
| widgetLine = ext5.modelLine2WidgetLine(widgetLine); |
| } |
| revealLine(fTextViewer, widgetLine); |
| |
| if (fStepCount > 0 && fSelectionChangeDelay > 0) { |
| disableLineBackgroundPainter(); |
| // reschedule for selection change |
| schedule(fSelectionChangeDelay); |
| if (!fDoneStepping.getAndSet(true)) { |
| doneStepping(getDmc()); |
| } |
| return true; |
| } else { |
| enableLineBackgroundPainter(); |
| fTextViewer.setSelectedRange(fRegion.getOffset(), 0); |
| } |
| } else { |
| editor.selectAndReveal(fRegion.getOffset(), 0); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Scroll the given line into the visible area if it is not yet visible. |
| * |
| * @param focusLine |
| * @see org.eclipse.jface.text.TextViewer#revealRange(int, int) |
| */ |
| private void revealLine(ITextViewer viewer, int focusLine) { |
| StyledText textWidget = viewer.getTextWidget(); |
| int top = textWidget.getTopIndex(); |
| if (top > -1) { |
| |
| // scroll vertically |
| int lines = getEstimatedVisibleLinesInViewport(textWidget); |
| int bottom = top + lines; |
| |
| int bottomBuffer = Math.max(1, lines / 3); |
| |
| if (focusLine >= top && focusLine <= bottom - bottomBuffer) { |
| // do not scroll at all as it is already visible |
| } else { |
| if (focusLine > bottom - bottomBuffer && focusLine <= bottom) { |
| // focusLine is already in bottom bufferZone |
| // scroll to top of bottom bufferzone - for smooth |
| // down-scrolling |
| int scrollDelta = focusLine - (bottom - bottomBuffer); |
| textWidget.setTopIndex(top + scrollDelta); |
| } else { |
| // scroll to top of visible area minus buffer zone |
| int topBuffer = lines / 3; |
| textWidget.setTopIndex(Math.max(0, focusLine - topBuffer)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return the number of visible lines in the view port assuming a |
| * constant line height. |
| */ |
| private int getEstimatedVisibleLinesInViewport(StyledText textWidget) { |
| if (textWidget != null) { |
| Rectangle clArea = textWidget.getClientArea(); |
| if (!clArea.isEmpty()) { |
| return clArea.height / textWidget.getLineHeight(); |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Returns the line information for the given line in the given editor |
| */ |
| private IRegion getLineInformation(ITextEditor editor, int lineNumber) { |
| IDocumentProvider provider = editor.getDocumentProvider(); |
| IEditorInput input = editor.getEditorInput(); |
| try { |
| provider.connect(input); |
| } catch (CoreException e) { |
| return null; |
| } |
| try { |
| IDocument document = provider.getDocument(input); |
| if (document != null) { |
| return document.getLineInformation(lineNumber); |
| } |
| } catch (BadLocationException e) { |
| } finally { |
| provider.disconnect(input); |
| } |
| return null; |
| } |
| |
| } |
| |
| /** |
| * Job that removes the old IP Annotations associated with given execution |
| * context. |
| */ |
| class ClearingJob extends UIJob { |
| Set<IRunControl.IExecutionDMContext> fDmcsToClear; |
| |
| public ClearingJob(Set<IRunControl.IExecutionDMContext> dmcs) { |
| super("Debug Source Display"); //$NON-NLS-1$ |
| setSystem(true); |
| setPriority(Job.INTERACTIVE); |
| fDmcsToClear = dmcs; |
| } |
| |
| @Override |
| public IStatus runInUIThread(IProgressMonitor monitor) { |
| DsfRunnable clearingJobFinishedRunnable = new DsfRunnable() { |
| @Override |
| public void run() { |
| assert fRunningClearingJob == ClearingJob.this; |
| fRunningClearingJob = null; |
| serviceDisplayAndClearingJobs(); |
| } |
| }; |
| |
| enableLineBackgroundPainter(); |
| |
| if (monitor.isCanceled()) { |
| executeFromJob(clearingJobFinishedRunnable); |
| return Status.CANCEL_STATUS; |
| } |
| |
| for (IRunControl.IExecutionDMContext dmc : fDmcsToClear) { |
| fIPManager.removeAnnotations(dmc); |
| } |
| |
| executeFromJob(clearingJobFinishedRunnable); |
| return Status.OK_STATUS; |
| } |
| } |
| |
| private static final boolean DEBUG = false; |
| |
| private DsfSession fSession; |
| private DsfExecutor fExecutor; |
| private DsfServicesTracker fServicesTracker; |
| private FrameData fPrevFrameData; |
| private SourceLookupResult fPrevResult; |
| private ISourceLookupDirector fSourceLookup; |
| private DsfSourceLookupParticipant fSourceLookupParticipant; |
| private InstructionPointerManager fIPManager; |
| |
| private LookupJob fRunningLookupJob; |
| private DisplayJob fRunningDisplayJob; |
| private DisplayJob fPendingDisplayJob; |
| private ClearingJob fRunningClearingJob; |
| private Set<IRunControl.IExecutionDMContext> fPendingExecDmcsToClear = new HashSet<IRunControl.IExecutionDMContext>(); |
| private SteppingController fController; |
| |
| /** |
| * Delay (in milliseconds) before the selection is changed to the IP |
| * location |
| */ |
| private int fSelectionChangeDelay = 150; |
| |
| private long fStepStartTime = 0; |
| private long fLastStepTime = 0; |
| private long fStepCount; |
| |
| private boolean fEnableLineBackgroundPainter; |
| |
| public DsfSourceDisplayAdapter(DsfSession session, ISourceLookupDirector sourceLocator) { |
| this(session, sourceLocator, null); |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| public DsfSourceDisplayAdapter(DsfSession session, ISourceLookupDirector sourceLocator, |
| SteppingController controller) { |
| fSession = session; |
| fExecutor = session.getExecutor(); |
| fServicesTracker = new DsfServicesTracker(DsfUIPlugin.getBundleContext(), session.getId()); |
| fSourceLookup = sourceLocator; |
| fSourceLookupParticipant = new DsfSourceLookupParticipant(session); |
| fSourceLookup.addParticipants(new ISourceLookupParticipant[] { fSourceLookupParticipant }); |
| |
| final IInstructionPointerPresentation ipPresentation = (IInstructionPointerPresentation) session |
| .getModelAdapter(IInstructionPointerPresentation.class); |
| fIPManager = new InstructionPointerManager(ipPresentation); |
| |
| fExecutor.execute(new DsfRunnable() { |
| @Override |
| public void run() { |
| fSession.addServiceEventListener(DsfSourceDisplayAdapter.this, null); |
| } |
| }); |
| |
| fController = controller; |
| if (fController != null) { |
| fController.addSteppingControlParticipant(this); |
| } |
| } |
| |
| /** |
| * Configure the delay (in milliseconds) before the selection in the editor |
| * is changed to the IP location. |
| * |
| * @param delay |
| * the delay in milliseconds, a non-negative integer |
| * |
| * @since 1.1 |
| */ |
| public void setSelectionChangeDelay(int delay) { |
| fSelectionChangeDelay = delay; |
| } |
| |
| public void dispose() { |
| if (fController != null) { |
| fController.removeSteppingControlParticipant(this); |
| fController = null; |
| } |
| |
| try { |
| fExecutor.execute(new DsfRunnable() { |
| @Override |
| public void run() { |
| fSession.removeServiceEventListener(DsfSourceDisplayAdapter.this); |
| } |
| }); |
| } catch (RejectedExecutionException e) { |
| // Session is shut down. |
| } |
| |
| fServicesTracker.dispose(); |
| fSourceLookup.removeParticipants(new ISourceLookupParticipant[] { fSourceLookupParticipant }); |
| |
| // fSourceLookupParticipant is disposed by the source lookup director |
| |
| // Need to remove annotations in UI thread. |
| Display display = PlatformUI.getWorkbench().getDisplay(); |
| if (display != null && !display.isDisposed()) { |
| display.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| enableLineBackgroundPainter(); |
| fIPManager.removeAllAnnotations(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void displaySource(Object context, final IWorkbenchPage page, final boolean force) { |
| fStepCount = 0; |
| |
| IFrameDMContext displayFrame = null; |
| if (context instanceof IDMVMContext) { |
| IDMContext dmc = ((IDMVMContext) context).getDMContext(); |
| if (dmc instanceof IFrameDMContext) { |
| displayFrame = (IFrameDMContext) dmc; |
| } |
| } else if (context instanceof IFrameDMContext) { |
| displayFrame = (IFrameDMContext) context; |
| } |
| |
| // Quick test. DMC is checked again in source lookup participant, but |
| // it's much quicker to test here. |
| if (displayFrame != null) { |
| doDisplaySource(displayFrame, page, force, false); |
| } |
| } |
| |
| private void doDisplaySource(final IFrameDMContext context, final IWorkbenchPage page, final boolean force, |
| final boolean eventTriggered) { |
| if (DEBUG) { |
| System.out.println( |
| "[DsfSourceDisplayAdapter] doDisplaySource ctx=" + context + " eventTriggered=" + eventTriggered); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (context.getLevel() < 0) { |
| return; |
| } |
| // Re-dispatch to executor thread before accessing job lists. |
| fExecutor.execute(new DsfRunnable() { |
| @Override |
| public void run() { |
| // We need to retrieve the frame level and line number from the |
| // service. |
| IStack stackService = fServicesTracker.getService(IStack.class); |
| if (stackService == null) { |
| return; |
| } |
| stackService.getFrameData(context, new DataRequestMonitor<IFrameDMData>(fExecutor, null) { |
| @Override |
| public void handleSuccess() { |
| FrameData frameData = new FrameData(); |
| frameData.fDmc = context; |
| frameData.fLevel = context.getLevel(); |
| // Document line numbers are 0-based. While debugger |
| // line numbers are 1-based. |
| IFrameDMData data = getData(); |
| frameData.fLine = data.getLine() - 1; |
| frameData.fFile = data.getFile(); |
| if (!force && frameData.equals(fPrevFrameData)) { |
| fPrevResult.updateArtifact(context); |
| startDisplayJob(fPrevResult, frameData, page, eventTriggered); |
| } else { |
| startLookupJob(frameData, page, eventTriggered); |
| } |
| } |
| |
| @Override |
| protected void handleFailure() { |
| doneStepping(context); |
| } |
| |
| @Override |
| protected void handleRejectedExecutionException() { |
| doneStepping(context); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private void executeFromJob(Runnable runnable) { |
| try { |
| fExecutor.execute(runnable); |
| } catch (RejectedExecutionException e) { |
| // Session disposed, ignore |
| } |
| } |
| |
| private void startLookupJob(final FrameData frameData, final IWorkbenchPage page, boolean eventTriggered) { |
| // If there is a previous lookup job running, cancel it. |
| if (fRunningLookupJob != null) { |
| if (!eventTriggered && frameData.isIdentical(fRunningLookupJob.fFrameData)) { |
| // identical location - we are done |
| return; |
| } |
| // cancel running lookup job |
| fRunningLookupJob.cancel(); |
| // make sure doneStepping() is called even if the job never ran - |
| // bug 325394 |
| if (fRunningLookupJob.fEventTriggered) { |
| // ... but not if this request is event-triggered for the same |
| // context (duplicate suspended event) |
| if (!eventTriggered || !fRunningLookupJob.getDmc().equals(frameData.fDmc)) { |
| doneStepping(fRunningLookupJob.getDmc()); |
| } |
| } |
| } |
| |
| fRunningLookupJob = new LookupJob(frameData, page, eventTriggered); |
| fRunningLookupJob.schedule(); |
| } |
| |
| // To be called only on dispatch thread. |
| private void startDisplayJob(SourceLookupResult lookupResult, FrameData frameData, IWorkbenchPage page, |
| boolean eventTriggered) { |
| DisplayJob nextDisplayJob = new DisplayJob(lookupResult, frameData, page, eventTriggered); |
| if (fRunningDisplayJob != null) { |
| fPendingDisplayJob = null; |
| IExecutionDMContext[] execCtxs = DMContexts.getAllAncestorsOfType(frameData.fDmc, |
| IExecutionDMContext.class); |
| fPendingExecDmcsToClear.removeAll(Arrays.asList(execCtxs)); |
| |
| if (!eventTriggered && frameData.isIdentical(fRunningDisplayJob.fFrameData)) { |
| // identical location - we are done |
| return; |
| } |
| // cancel running display job |
| fRunningDisplayJob.cancel(); |
| // make sure doneStepping() is called even if the job never ran - |
| // bug 325394 |
| if (fRunningDisplayJob.fEventTriggered && !fRunningDisplayJob.fDoneStepping.getAndSet(true)) { |
| // ... but not if this request is event-triggered for the same |
| // context (duplicate suspended event) |
| if (!eventTriggered || !fRunningDisplayJob.getDmc().equals(lookupResult.getDmc())) { |
| doneStepping(fRunningDisplayJob.getDmc()); |
| } |
| } |
| } |
| if (fRunningClearingJob != null) { |
| // Wait for the clearing job to finish, instead, set the |
| // display job as pending. |
| fPendingDisplayJob = nextDisplayJob; |
| } else { |
| fRunningDisplayJob = nextDisplayJob; |
| fRunningDisplayJob.schedule(); |
| } |
| } |
| |
| private void doneStepping(IDMContext context) { |
| if (fController != null) { |
| // indicate completion of step |
| final IExecutionDMContext dmc = DMContexts.getAncestorOfType(context, IExecutionDMContext.class); |
| if (dmc != null) { |
| try { |
| fController.getExecutor().execute(new DsfRunnable() { |
| @Override |
| public void run() { |
| fController.doneStepping(dmc, DsfSourceDisplayAdapter.this); |
| }; |
| }); |
| } catch (RejectedExecutionException e) { |
| // Session is shutdown |
| } |
| } |
| } |
| } |
| |
| private void serviceDisplayAndClearingJobs() { |
| if (!fPendingExecDmcsToClear.isEmpty()) { |
| // There are annotations to be cleared, run the job first |
| fRunningClearingJob = new ClearingJob(fPendingExecDmcsToClear); |
| fRunningClearingJob.schedule(); |
| fPendingExecDmcsToClear = new HashSet<IRunControl.IExecutionDMContext>(); |
| } else if (fPendingDisplayJob != null) { |
| fRunningDisplayJob = fPendingDisplayJob; |
| fRunningDisplayJob.schedule(); |
| fPendingDisplayJob = null; |
| } |
| } |
| |
| private void startAnnotationClearingJob(IRunControl.IExecutionDMContext execDmc) { |
| // Make sure to add the context to the list. |
| fPendingExecDmcsToClear.add(execDmc); |
| |
| // If lookup job is running, check it against the execution context, |
| // and cancel it if matches. |
| if (fRunningLookupJob != null) { |
| if (DMContexts.isAncestorOf(fRunningLookupJob.getDmc(), execDmc)) { |
| fRunningLookupJob.cancel(); |
| fRunningLookupJob = null; |
| } |
| } |
| // If there is a pending display job, make sure it doesn't get |
| // preempted by this event. If so, just cancel the pending |
| // display job. |
| if (fPendingDisplayJob != null) { |
| if (DMContexts.isAncestorOf(fPendingDisplayJob.getDmc(), execDmc)) { |
| fPendingDisplayJob = null; |
| } |
| } |
| |
| // If no display or clearing jobs are running, schedule the clearing |
| // job. |
| if (fRunningClearingJob == null && fRunningDisplayJob == null) { |
| fRunningClearingJob = new ClearingJob(fPendingExecDmcsToClear); |
| fRunningClearingJob.schedule(); |
| fPendingExecDmcsToClear = new HashSet<IRunControl.IExecutionDMContext>(); |
| } |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(IRunControl.IResumedDMEvent e) { |
| if (e.getReason() != StateChangeReason.STEP) { |
| startAnnotationClearingJob(e.getDMContext()); |
| } |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(IRunControl.IExitedDMEvent e) { |
| startAnnotationClearingJob(e.getDMContext()); |
| } |
| |
| /** |
| * @since 1.1 |
| */ |
| @DsfServiceEventHandler |
| public void eventDispatched(SteppingTimedOutEvent e) { |
| startAnnotationClearingJob(e.getDMContext()); |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(final IRunControl.ISuspendedDMEvent e) { |
| updateStepTiming(); |
| if (e.getReason() == StateChangeReason.STEP || e.getReason() == StateChangeReason.BREAKPOINT) { |
| if (DEBUG) { |
| System.out.println("[DsfSourceDisplayAdapter] eventDispatched e=" + e); //$NON-NLS-1$ |
| } |
| // trigger source display immediately (should be optional?) |
| Display.getDefault().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| Object context = DebugUITools.getDebugContext(); |
| if (context instanceof IDMVMContext) { |
| final IDMContext dmc = ((IDMVMContext) context).getDMContext(); |
| if (dmc instanceof IFrameDMContext && DMContexts.isAncestorOf(dmc, e.getDMContext())) { |
| IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); |
| doDisplaySource((IFrameDMContext) dmc, page, false, true); |
| return; |
| } |
| } |
| doneStepping(e.getDMContext()); |
| } |
| }); |
| } else { |
| doneStepping(e.getDMContext()); |
| } |
| } |
| |
| private void updateStepTiming() { |
| long now = System.currentTimeMillis(); |
| if (now - fLastStepTime > Math.max(fSelectionChangeDelay, 200)) { |
| fStepCount = 0; |
| fStepStartTime = fLastStepTime = now; |
| return; |
| } |
| fLastStepTime = now; |
| ++fStepCount; |
| if (DEBUG) { |
| long delta = now - fStepStartTime; |
| float meanTime = delta / (float) fStepCount / 1000; |
| System.out.println("[DsfSourceDisplayAdapter] step speed = " + 1 / meanTime); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Disable editor line background painter if it is enabled. |
| * <p> |
| * <strong>Must be called on display thread.</strong> |
| * </p> |
| */ |
| private void disableLineBackgroundPainter() { |
| if (!fEnableLineBackgroundPainter) { |
| fEnableLineBackgroundPainter = EditorsUI.getPreferenceStore() |
| .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE); |
| if (fEnableLineBackgroundPainter) { |
| EditorsUI.getPreferenceStore() |
| .setValue(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, false); |
| } |
| } |
| } |
| |
| /** |
| * Enable the editor line background painter if it was enabled before. |
| * <p> |
| * <strong>Must be called on display thread.</strong> |
| * </p> |
| */ |
| private void enableLineBackgroundPainter() { |
| if (fEnableLineBackgroundPainter) { |
| fEnableLineBackgroundPainter = false; |
| EditorsUI.getPreferenceStore().setValue(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, |
| true); |
| } |
| } |
| |
| } |