From 9782f044c75004609e2eef2caf43dcff75a84012 Mon Sep 17 00:00:00 2001 From: John Cortell Date: Thu, 14 Apr 2011 20:31:22 +0000 Subject: Bug 342141 - Executables view content goes stale in various scenarios --- .../executables/ExecutablesContentProvider.java | 46 ++- .../ui/views/executables/ExecutablesView.java | 13 +- .../ui/views/executables/ExecutablesViewer.java | 53 +-- .../internal/ui/views/executables/Messages.java | 1 + .../ui/views/executables/Messages.properties | 1 + .../executables/SourceFilesContentProvider.java | 403 ++++++++++++++++----- .../ui/views/executables/SourceFilesViewer.java | 122 ++----- 7 files changed, 409 insertions(+), 230 deletions(-) (limited to 'debug/org.eclipse.cdt.debug.ui/src/org') diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java index 18e7ced7a4c..132c4a45fba 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java @@ -11,28 +11,44 @@ package org.eclipse.cdt.debug.internal.ui.views.executables; -import com.ibm.icu.text.DateFormat; import java.util.Date; +import java.util.List; import org.eclipse.cdt.debug.core.executables.Executable; import org.eclipse.cdt.debug.core.executables.ExecutablesManager; +import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.ui.progress.WorkbenchJob; + +import com.ibm.icu.text.DateFormat; -class ExecutablesContentProvider extends ColumnLabelProvider implements IStructuredContentProvider, ITreeContentProvider { +class ExecutablesContentProvider extends ColumnLabelProvider implements IStructuredContentProvider, ITreeContentProvider, IExecutablesChangeListener { - public ExecutablesContentProvider(TreeViewer viewer) { + final private TreeViewer viewer; + + public ExecutablesContentProvider(final TreeViewer viewer) { + this.viewer = viewer; + ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(this); } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.BaseLabelProvider#dispose() + */ + @Override public void dispose() { + ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(this); } public Object[] getElements(final Object inputElement) { @@ -100,4 +116,28 @@ class ExecutablesContentProvider extends ColumnLabelProvider implements IStructu return new Object[] {}; } + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesListChanged() + */ + public void executablesListChanged() { + new WorkbenchJob("execs list changed") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + viewer.refresh(null); + if (viewer instanceof BaseViewer) { + ((BaseViewer)viewer).packColumns(); + } + return Status.OK_STATUS; + } + }.schedule(); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesChanged(java.util.List) + */ + public void executablesChanged(List executables) { + // Our concern is only if the list of executables changed. The + // content provider for the source files viewer will care about + // whether the Executables themselves change + } } \ No newline at end of file diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java index 4a35f5c5b0b..83b37d6be72 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java @@ -58,7 +58,7 @@ import org.eclipse.ui.XMLMemento; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.dialogs.ListSelectionDialog; import org.eclipse.ui.part.ViewPart; -import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.progress.WorkbenchJob; /** * ExecutablesView displays a list of executable files either in the workspace @@ -176,6 +176,7 @@ public class ExecutablesView extends ViewPart { class ColumnLabelProvider extends LabelProvider { + @Override public String getText(Object element) { return (String) element; } @@ -193,6 +194,7 @@ public class ExecutablesView extends ViewPart { * * @see org.eclipse.jface.action.Action#run() */ + @Override public void run() { ListSelectionDialog dialog = new ListSelectionDialog(ExecutablesView.this.getExecutablesViewer().getTree().getShell(), this, new ColumnContentProvider(), new ColumnLabelProvider(), Messages.ExecutablesView_SelectColumns); @@ -250,7 +252,6 @@ public class ExecutablesView extends ViewPart { // Create the two sub viewers. executablesViewer = new ExecutablesViewer(this, sashForm, SWT.FULL_SELECTION | SWT.BORDER | SWT.MULTI); focusedViewer = executablesViewer; - ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(executablesViewer); sourceFilesViewer = new SourceFilesViewer(this, sashForm, SWT.BORDER | SWT.MULTI); executablesViewer.getTree().addFocusListener(new FocusListener() { @@ -416,6 +417,7 @@ public class ExecutablesView extends ViewPart { private Action createRemoveAction() { Action action = new Action(Messages.ExecutablesView_Remove) { + @Override public void run() { ISelection selection = getExecutablesViewer().getSelection(); if (selection instanceof IStructuredSelection) @@ -432,8 +434,9 @@ public class ExecutablesView extends ViewPart { if (confirm) { - Job removeJob = new UIJob(Messages.ExecutablesView_RemoveExes) { + Job removeJob = new WorkbenchJob(Messages.ExecutablesView_RemoveExes) { + @Override public IStatus runInUIThread(IProgressMonitor monitor) { IStatus result = ExecutablesManager.getExecutablesManager().removeExecutables(selectedExesArray, monitor); if (result.getSeverity() != IStatus.OK) @@ -490,6 +493,7 @@ public class ExecutablesView extends ViewPart { private Action createImportAction() { Action action = new Action(Messages.ExecutablesView_Import) { + @Override public void run() { FileDialog dialog = new FileDialog(getViewSite().getShell(), SWT.NONE); dialog.setText(Messages.ExecutablesView_SelectExeFile); @@ -515,8 +519,10 @@ public class ExecutablesView extends ViewPart { private Action createRefreshAction() { Action action = new Action(Messages.ExecutablesView_Refresh) { + @Override public void run() { ExecutablesManager.getExecutablesManager().refresh(null); + sourceFilesViewer.restartCanceledExecutableParse(); } }; action.setToolTipText(Messages.ExecutablesView_RefreshList); @@ -574,7 +580,6 @@ public class ExecutablesView extends ViewPart { @Override public void dispose() { - ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(executablesViewer); super.dispose(); } diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java index bc5f2135c42..cd225886b70 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java @@ -10,19 +10,12 @@ *******************************************************************************/ package org.eclipse.cdt.debug.internal.ui.views.executables; -import java.util.List; - import org.eclipse.cdt.debug.core.executables.Executable; import org.eclipse.cdt.debug.core.executables.ExecutablesManager; -import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; -import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerDropAdapter; @@ -35,12 +28,11 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.IWorkbenchActionConstants; -import org.eclipse.ui.progress.UIJob; /** * Displays the list of executables gathered by the ExecutablesManager */ -public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeListener { +public class ExecutablesViewer extends BaseViewer { private static final String P_COLUMN_ORDER_KEY_EXE = "columnOrderKeyEXE"; //$NON-NLS-1$ private static final String P_SORTED_COLUMN_INDEX_KEY_EXE = "sortedColumnIndexKeyEXE"; //$NON-NLS-1$ @@ -155,6 +147,7 @@ public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeL protected ViewerComparator getViewerComparator(int sortType) { if (sortType == ExecutablesView.PROJECT) { return new ExecutablesViewerComparator(sortType, column_sort_order[ExecutablesView.PROJECT]) { + @Override @SuppressWarnings("unchecked") public int compare(Viewer viewer, Object e1, Object e2) { Executable entry1 = (Executable) e1; @@ -192,46 +185,4 @@ public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeL // default visible columns return "1,1,1,0,0,0"; //$NON-NLS-1$ } - - public void executablesChanged(final List executables) { - // some executables have been updated. if one of them is currently - // selected, we need to update the source file list - UIJob refreshJob = new UIJob(Messages.ExecutablesViewer_RefreshExecutablesView) { - - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - // if the user has selected an executable, they expect its - // list of source files to be refreshed automatically - if (getSelection() != null && - getSelection() instanceof IStructuredSelection) { - IStructuredSelection selection = (IStructuredSelection)getSelection(); - - Object firstElement = selection.getFirstElement(); - if (firstElement instanceof Executable) { - Executable executable = (Executable) firstElement; - if (executables.contains(executable)) { - executable.setRefreshSourceFiles(true); - setSelection(selection); - } - } - } - return Status.OK_STATUS; - } - }; - refreshJob.schedule(); - } - - public void executablesListChanged() { - // Executables list has changed so refresh the view. - UIJob refreshJob = new UIJob(Messages.ExecutablesViewer_RefreshExecutablesView) { - - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - refresh(null); - packColumns(); - return Status.OK_STATUS; - } - }; - refreshJob.schedule(); - } } \ No newline at end of file diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java index 1507dfb3d19..be16c63a60c 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java @@ -53,6 +53,7 @@ public class Messages extends NLS { public static String SourceFilesContentProvider_NoFilesFound; public static String SourceFilesContentProvider_ReadingDebugSymbolInformationLabel; public static String SourceFilesContentProvider_Refreshing; + public static String SourceFilesContentProvider_Canceled; public static String SourceFilesViewer_RefreshSourceFiles; public static String SourceFilesViewer_Location; public static String SourceFilesViewer_Modified; diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties index 4262cb72bcd..1cf86d51dd3 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties @@ -48,6 +48,7 @@ ExecutablesViewer_Type=Type SourceFilesContentProvider_NoFilesFound=No source files found in SourceFilesContentProvider_ReadingDebugSymbolInformationLabel=Reading Debug Symbol Information: SourceFilesContentProvider_Refreshing=Refreshing... +SourceFilesContentProvider_Canceled=Parse canceled. Hit refresh to restart. SourceFilesViewer_RefreshSourceFiles=Refresh Source Files SourceFilesViewer_Location=Location SourceFilesViewer_Modified=Modified diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java index 80a2ffe9998..b515756542a 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java @@ -18,7 +18,8 @@ import java.util.Map; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.debug.core.executables.Executable; import org.eclipse.cdt.debug.core.executables.ExecutablesManager; -import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener; +import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2; +import org.eclipse.cdt.debug.internal.core.Trace; import org.eclipse.cdt.debug.internal.ui.views.executables.SourceFilesViewer.TranslationUnitInfo; import org.eclipse.cdt.ui.CElementContentProvider; import org.eclipse.core.runtime.IPath; @@ -30,8 +31,9 @@ import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.progress.WorkbenchJob; -public class SourceFilesContentProvider extends CElementContentProvider implements IExecutablesChangeListener { +public class SourceFilesContentProvider extends CElementContentProvider implements IExecutablesChangeListener2 { static class QuickParseJob extends Job { final Executable executable; @@ -45,18 +47,65 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen @Override protected IStatus run(IProgressMonitor monitor) { - tus = executable.getSourceFiles(monitor); - return Status.OK_STATUS; + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Quick parsing of executable for source files has begun (" + this + ')'); //$NON-NLS-1$ + + // Ask the Executable for its source files. This could take a while... + ITranslationUnit[] mytus = executable.getSourceFiles(monitor); + + IStatus status; + if (!monitor.isCanceled()) { + tus = mytus; + status = Status.OK_STATUS; + } + else { + status = Status.CANCEL_STATUS; + } + + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Quick parsing of executable has finished, status is " + status); //$NON-NLS-1$ + return status; } } - /** contains running jobs */ + /** + * The collection of running file parsing jobs. Each executable file (not + * object) can independently be parsed, and these parses can happen + * simultaneously. Normally, each executable file has at most one ongoing + * parse. An exception is when a search is canceled. We don't wait for the + * search to actually end if a subsequent search comes in shortly after the + * first one is canceled. We cancel the first one, remove it from this list, + * schedule a new one, then add that to the list. It's safe to assume the + * canceled one will complete before the new one. + * + *

This collection must be accessed only from the UI thread + */ private Map pathToJobMap = new HashMap(); /** those executables for which we asked the question and got a result. * NOTE: this contains a duplicate of into in Executable, because we can't * guarantee or check whether Executable still has the info itself. */ - private Map fetchedExecutables = new HashMap(); + private static class TUData{ + /** Constructor used when search completes successfully */ + public TUData(ITranslationUnit[] tus, long timestamp) { + this.tus = tus; + this.timestamp = timestamp; + } + + /** Constructor used when search is canceled */ + public TUData() { + this.canceled = true; + } + + ITranslationUnit[] tus; + /** IResource.getModificationStamp value of when this data was last updated */ + long timestamp; + + boolean canceled; + } + + /** + * The cached file info. Key is the path of the executable. This collection must be accessed only on the UI thread. + */ + private Map fetchedExecutables = new HashMap(); private final SourceFilesViewer viewer; @@ -72,12 +121,14 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen @Override public void dispose() { ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(this); - synchronized (fetchedExecutables) { - fetchedExecutables.clear(); - } - synchronized (pathToJobMap) { - pathToJobMap.clear(); - } + new WorkbenchJob("") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + fetchedExecutables.clear(); + pathToJobMap.clear(); + return Status.OK_STATUS; + } + }.schedule(); super.dispose(); } @@ -92,63 +143,71 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen return super.hasChildren(element); } + /* (non-Javadoc) + * @see org.eclipse.cdt.internal.ui.BaseCElementContentProvider#getElements(java.lang.Object) + */ + @Override public Object[] getElements(Object inputElement) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, inputElement); + if (inputElement instanceof Executable) { final Executable executable = (Executable) inputElement; final IPath exePath = executable.getPath(); // look for a job that is currently fetching this info QuickParseJob job; - synchronized (pathToJobMap) { - job = pathToJobMap.get(exePath); - } + job = pathToJobMap.get(exePath); if (job != null) { // job is still running return new String[] { Messages.SourceFilesContentProvider_Refreshing }; } + // create a background job to look for the sources but don't start it yet + job = new QuickParseJob(executable); + pathToJobMap.put(exePath, job); - // see if we already checked - synchronized (fetchedExecutables) { - if (fetchedExecutables.containsKey(exePath)) { - return fetchedExecutables.get(exePath); - } + // See if we have the result cached for this executable. If so + // return that. It's also possible that the most resent search was + // canceled + Object[] cachedResult = null; + TUData tud = fetchedExecutables.get(exePath); + if (tud != null) { + if (tud.canceled) + cachedResult = new String[]{Messages.SourceFilesContentProvider_Canceled}; + else + cachedResult = tud.tus; } - - // start a background job to look for the sources - job = new QuickParseJob(executable); - synchronized (pathToJobMap) { - pathToJobMap.put(exePath, job); + if (cachedResult != null) { + pathToJobMap.remove(exePath); // removed the unused search job + return cachedResult; } - // once the job finishes, update the viewer + // Schedule the job; once it finishes, update the viewer final QuickParseJob theJob = job; job.addJobChangeListener(new JobChangeAdapter() { - public void done(IJobChangeEvent event) { - synchronized (pathToJobMap) { - pathToJobMap.values().remove(theJob); - } - if (event.getResult().isOK()) { - synchronized (fetchedExecutables) { - fetchedExecutables.put(exePath, theJob.tus); - } - Display.getDefault().asyncExec(new Runnable() { - public void run() { - // update the viewer - if (!viewer.getControl().isDisposed()) { - viewer.getTree().setLayoutDeferred(true); - viewer.refresh(executable); - viewer.packColumns(); - viewer.getTree().setLayoutDeferred(false); - } + @Override + public void done(final IJobChangeEvent event) { + new WorkbenchJob("refreshing source files viewer"){ //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + if (event.getResult().isOK()) { + fetchedExecutables.put(exePath, new TUData(theJob.tus, theJob.executable.getResource().getModificationStamp())); } - }); - } + else { + fetchedExecutables.put(exePath, new TUData()); + } + + pathToJobMap.values().remove(theJob); + + refreshViewer(executable); + return Status.OK_STATUS; + } + }.schedule(); } }); - + job.schedule(); - // while it's running... + // show the user a string that lets him know we're searching return new String[] { Messages.SourceFilesContentProvider_Refreshing }; } return new Object[] {}; @@ -159,46 +218,62 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesListChanged() */ public void executablesListChanged() { - // Don't clear executables -- closing/opening project doesn't imply - // the info is different. But cancel all the jobs in case projects - // were closed. It's non-obvious how to map executables to projects, - // so just bail and cancel all the current parsing. The viewer - // will be refreshed and re-request source lists for any executables - // that are still applicable. - cancelQuickParseJobs(); + // we react via IExecutablesChangeListener2 methods } - /** - * - */ - private void cancelQuickParseJobs() { - synchronized (pathToJobMap) { - for (QuickParseJob job : pathToJobMap.values()) { - job.cancel(); - } - pathToJobMap.clear(); - } - - } - /* (non-Javadoc) * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesChanged(java.util.List) */ - public void executablesChanged(List executables) { - for (Executable executable : executables) { - IPath exePath = executable.getPath(); - synchronized (fetchedExecutables) { - fetchedExecutables.remove(exePath); - } - synchronized (pathToJobMap) { - QuickParseJob job = pathToJobMap.get(exePath); - if (job != null) { - job.cancel(); - pathToJobMap.remove(exePath); + public void executablesChanged(final List executables) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables); + + new WorkbenchJob("Refreshing viewer") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + for (Executable executable : executables) { + IPath exePath = executable.getPath(); + fetchedExecutables.remove(exePath); + QuickParseJob job = pathToJobMap.get(exePath); + if (job != null) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Cancelling QuickParseJob: " + job); //$NON-NLS-1$ + job.cancel(); + pathToJobMap.remove(exePath); + } + } + + if (!viewer.getControl().isDisposed()) { + // See if our current input is one of the executables that has changed. + for (Executable executable : executables) { + if (executable.equals(fInput)) { + // Executable.equals() is not a simple reference + // check. Two Executable objects are equal if they + // represent the same file on disk. I.e., our input + // object might not be one of the instances on the + // changed-list, but for sure the file on disk has + // changed. Now, the manager that called this + // listener has already told the Executable + // instances on the changed list to flush their + // source files list. However, if our input is not + // exactly one of those references, it means the + // manager is no longer managing the Executable + // that's our input. In that case, it's up to us to + // tell that Executable to flush its source file + // cache so that refreshing the viewer will cause a + // fresh fetch of the source file information. + Executable execInput = (Executable)fInput; + if (executable != execInput) { + execInput.setRefreshSourceFiles(true); + } + refreshViewer(execInput); + break; + } + } } + return Status.OK_STATUS; } - } + + }.schedule(); } /* (non-Javadoc) @@ -208,14 +283,178 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen public void inputChanged(Viewer viewer, Object oldInput, final Object newInput) { super.inputChanged(viewer, oldInput, newInput); - Display.getDefault().asyncExec(new Runnable() { - public void run() { + new WorkbenchJob("Refreshing viewer") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { // pack because the quick parse job won't run if (newInput instanceof Executable && fetchedExecutables.containsKey(((Executable) newInput).getPath())) SourceFilesContentProvider.this.viewer.packColumns(); + return Status.OK_STATUS; + } + }.schedule(); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2#executablesAdded(java.util.List) + */ + public void executablesAdded(final List executables) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables); + + // Throw out our cached translation units for the executable *file* but + // only if the file hasn't changed. Executable objects come and go + // independently of the file on disk. + new WorkbenchJob("executables removed") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + for (Executable exec : executables) { + final IPath exePath = exec.getPath(); + final long timestamp = exec.getResource().getModificationStamp(); + TUData tud = fetchedExecutables.get(exePath); + if (tud != null && tud.timestamp != timestamp) { + fetchedExecutables.remove(exePath); + } + } + + if (!viewer.getControl().isDisposed()) { + // See if current viewer input is one of the executables that + // was added. If so, this is likely an exec that was rebuilt + // and CDT missed sending a REMOVED model event. There's + // some crazy race condition going on, but basically CDT + // sends out an event that the binary has changed, then + // sends one that says it was added. Anyway, the best thing + // for us to do is to cause a refresh of the viewer since + // the addition notification probably caused us to cancel + // the parse of the exec that was initiated by the change + // event and the viewer will be stuck with a "canceled" + // message in the viewer table. + for (Executable executable : executables) { + if (executable.equals(fInput)) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "refreshing viewer; added executable is our current input"); //$NON-NLS-1$ + refreshViewer((Executable)fInput); + break; + } + } + } + + return Status.OK_STATUS; + } + }.schedule(); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2#executablesRemoved(java.util.List) + */ + public void executablesRemoved(final List executables) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables); + + // The fact that the Executable was removed from the workspace doesn't + // mean we need to throw out the source file info we've cached. If a + // project is closed then reopened, we are able to reuse the info as + // long as the timestamp of the resource hasn't changed. But, there's no + // point in continuing any ongoing searches in the executables. + new WorkbenchJob("executables removed") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + for (Executable exec : executables) { + final IPath exePath = exec.getPath(); + QuickParseJob job = pathToJobMap.get(exePath); + if (job != null) { + if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Cancelling QuickParseJob: " + job); //$NON-NLS-1$ + job.cancel(); + pathToJobMap.remove(exePath); + } + } + return Status.OK_STATUS; + } + }.schedule(); + } + + /** + * Restarts a parse of the current input (Executable) if and only if its + * last search was canceled. The viewer is refresh accordingly. + * + *

+ * Must be called on the UI thread + * + */ + public void restartCanceledExecutableParse() { + assert Display.getCurrent() != null; + + Object input = viewer.getInput(); + if (input instanceof Executable) { + final Executable executable = (Executable)input; + final IPath exePath = executable.getPath(); + + // Ignore restart if there's an ongoing search. + QuickParseJob job; + job = pathToJobMap.get(exePath); + if (job != null) { + return; + } + + TUData tud = fetchedExecutables.get(exePath); + + // Ignore request if the most recent search wasn't canceled + if (tud != null && !tud.canceled) { + pathToJobMap.remove(exePath); + return; } - }); + + // Create and schedule a parse job. Once the job finishes, update + // the viewer + job = new QuickParseJob(executable); + pathToJobMap.put(exePath, job); + final QuickParseJob theJob = job; + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(final IJobChangeEvent event) { + + new WorkbenchJob("refreshing source files viewer"){ //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + // Update the model with the search results + if (event.getResult().isOK()) { + fetchedExecutables.put(exePath, new TUData(theJob.tus, theJob.executable.getResource().getModificationStamp())); + } + else { + // The search job apparently always completes + // successfully or it was canceled (failure was + // not a considered outcome). If it was canceled, + // well then we're back to where we started + fetchedExecutables.put(exePath, new TUData()); + } + pathToJobMap.values().remove(theJob); + + refreshViewer(executable); + return Status.OK_STATUS; + } + }.schedule(); + } + }); + + job.schedule(); + + // The viewer is currently showing "search canceled". Cause an + // immediate refresh so that it shows "refreshing" while the new + // search is ongoing + refreshViewer(executable); + } + } + + /** + * Utility method to invoke a viewer refresh for the given element + * @param input the Executable to show content for + * + *

Must be called on the UI thread + */ + private void refreshViewer(Executable input) { + if (!viewer.getControl().isDisposed()) { + viewer.getTree().setLayoutDeferred(true); + viewer.refresh(input); + viewer.packColumns(); + viewer.getTree().setLayoutDeferred(false); + } } } diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java index f69c3f7c840..640ba1291f2 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java @@ -11,35 +11,28 @@ package org.eclipse.cdt.debug.internal.ui.views.executables; import java.io.File; +import java.util.List; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ISourceReference; import org.eclipse.cdt.core.model.ITranslationUnit; -import org.eclipse.cdt.debug.core.CDebugCorePlugin; import org.eclipse.cdt.debug.core.executables.Executable; +import org.eclipse.cdt.debug.core.executables.ExecutablesManager; +import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener; import org.eclipse.cdt.debug.internal.ui.sourcelookup.CSourceNotFoundEditorInput; import org.eclipse.cdt.debug.ui.ICDebugUIConstants; import org.eclipse.cdt.internal.core.util.LRUCache; import org.eclipse.cdt.internal.ui.util.EditorUtility; import org.eclipse.cdt.ui.CUIPlugin; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; -import org.eclipse.debug.core.DebugPlugin; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.ILaunchConfigurationListener; -import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; -import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.IEditorPart; @@ -50,7 +43,7 @@ import org.eclipse.ui.PartInitException; * Displays the list of source files for the executable selected in the * ExecutablesViewer. */ -public class SourceFilesViewer extends BaseViewer implements ISourceLookupParticipant, ILaunchConfigurationListener { +public class SourceFilesViewer extends BaseViewer { /** Information from an ITranslationUnit for the various displayed columns */ static class TranslationUnitInfo { @@ -100,25 +93,21 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic openSourceFile(event); } }); - - // We implement ISourceLookupParticipant so we can listen for changes to - // source lookup as this viewer shows both original and remapped - // locations - CDebugCorePlugin.getDefault().getCommonSourceLookupDirector().addParticipants(new ISourceLookupParticipant[] { this }); - // We also look for launch configuration changes, since their source - // locators are involved in source path remapping, too - DebugPlugin.getDefault().getLaunchManager().addLaunchConfigurationListener(this); - - sourceFilesTree.addDisposeListener(new DisposeListener() { - - public void widgetDisposed(DisposeEvent e) { - DebugPlugin.getDefault().getLaunchManager().removeLaunchConfigurationListener(SourceFilesViewer.this); - - CDebugCorePlugin.getDefault().getCommonSourceLookupDirector().removeParticipants( - new ISourceLookupParticipant[] { SourceFilesViewer.this }); + ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(new IExecutablesChangeListener(){ + public void executablesListChanged() { + // this doesn't directly affect us } - }); + + public void executablesChanged(List executables) { + // TODO: be more selective; we don't know what TUs go with which executables yet + flushTranslationUnitCache(); + + // Note that we don't invoke a viewer refresh. Our content + // provider needs to also be listening for this notification. + // It's up to him to invoke a refresh on us if the model has + // been affected by the Executable change + }}); } private void openSourceFile(OpenEvent event) { @@ -198,10 +187,12 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic typeColumn.addSelectionListener(new ColumnSelectionAdapter(ExecutablesView.TYPE)); } + @Override protected ViewerComparator getViewerComparator(int sortType) { if (sortType == ExecutablesView.ORG_LOCATION) { return new ExecutablesViewerComparator(sortType, column_sort_order[ExecutablesView.ORG_LOCATION]) { + @Override @SuppressWarnings("unchecked") public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof ITranslationUnit && e2 instanceof ITranslationUnit) { @@ -219,40 +210,6 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic return new ExecutablesViewerComparator(sortType, column_sort_order[sortType]); } - public void dispose() { - } - - public Object[] findSourceElements(Object object) throws CoreException { - return new Object[0]; - } - - public String getSourceName(Object object) throws CoreException { - return ""; //$NON-NLS-1$ - } - - public void init(ISourceLookupDirector director) { - } - - public void sourceContainersChanged(ISourceLookupDirector director) { - refreshContent(); - } - - private void refreshContent() { - Display.getDefault().asyncExec(new Runnable() { - public void run() { - Object input = getInput(); - if (input != null && input instanceof Executable) { - ((Executable)input).setRemapSourceFiles(true); - - // TODO: be more selective; we don't know what TUs go with which executables yet - flushTranslationUnitCache(); - - refresh(true); - } - } - }); - } - @Override protected String getColumnOrderKey() { return P_COLUMN_ORDER_KEY_SF; @@ -279,34 +236,6 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic return "1,1,0,0,0,0"; //$NON-NLS-1$ } - /* (non-Javadoc) - * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationAdded(org.eclipse.debug.core.ILaunchConfiguration) - */ - public void launchConfigurationAdded(ILaunchConfiguration configuration) { - if (!configuration.isWorkingCopy()) { - refreshContent(); - } - } - - /* (non-Javadoc) - * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationChanged(org.eclipse.debug.core.ILaunchConfiguration) - */ - public void launchConfigurationChanged(ILaunchConfiguration configuration) { - if (!configuration.isWorkingCopy()) { - refreshContent(); - } - } - - /* (non-Javadoc) - * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationRemoved(org.eclipse.debug.core.ILaunchConfiguration) - */ - public void launchConfigurationRemoved(ILaunchConfiguration configuration) { - if (!configuration.isWorkingCopy()) { - refreshContent(); - } - } - - static TranslationUnitInfo fetchTranslationUnitInfo(Executable executable, Object element) { if (!(element instanceof ITranslationUnit)) { return null; @@ -359,4 +288,17 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic } + /** + * The view's refresh action calls this to restart an executable parse for + * the current input if the most recent search (for that element) was + * canceled. If it wasn't canceled, this is a no-op. + */ + public void restartCanceledExecutableParse() { + SourceFilesContentProvider provider = (SourceFilesContentProvider)getContentProvider(); + if (provider != null) { + provider.restartCanceledExecutableParse(); + } + + } + } \ No newline at end of file -- cgit v1.2.3