commit 5f3bcedecc474b20c196a42a315f2caaf8713852 Author: Xavier Coulon Date: Mon Sep 9 15:31:37 2013 +0200 Bug 372588 - [JUnit] Add 'Link with Editor' to JUnit view Adding a toggle action to enable/disable 'Link with Editor' (disabled by default) When 'Linking with editor' is enabled, an inner IPartListener2 is added to the active page When the selection changes (user switching to another Java editor or moving selection in another method of the same Test class), the selection in the TestViewer is updated (if necessary). Signed-off-by: Xavier Coulon diff --git a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java index ccf104e..a852081 100644 --- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java +++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java @@ -96,10 +96,18 @@ import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +import org.eclipse.jface.text.ITextSelection; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorActionBarContributor; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IViewPart; @@ -128,11 +136,15 @@ import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.jdt.core.ElementChangedEvent; +import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.junit.BasicElementLabels; import org.eclipse.jdt.internal.junit.JUnitCorePlugin; @@ -149,6 +161,10 @@ import org.eclipse.jdt.internal.junit.model.TestRunSession; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.ui.JavaUI; + +import org.eclipse.jdt.internal.ui.actions.AbstractToggleLinkingAction; +import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.viewsupport.ViewHistory; /** @@ -210,6 +226,9 @@ public class TestRunnerViewPart extends ViewPart { private Action fPreviousAction; private StopAction fStopAction; + + private LinkWithEditorAction fLinkWithEditorAction; + private JUnitCopyAction fCopyAction; private Action fPasteAction; @@ -342,7 +361,6 @@ public class TestRunnerViewPart extends ViewPart { protected boolean fPartIsVisible= false; - private class RunnerViewHistory extends ViewHistory { @Override @@ -705,6 +723,8 @@ public class TestRunnerViewPart extends ViewPart { startUpdateJobs(); fStopAction.setEnabled(true); + fLinkWithEditorAction.setEnabled(true); + fRerunLastTestAction.setEnabled(true); } @@ -722,6 +742,7 @@ public class TestRunnerViewPart extends ViewPart { if (isDisposed()) return; fStopAction.setEnabled(lastLaunchIsKeptAlive()); + fLinkWithEditorAction.setEnabled(fTestRunSession != null); updateRerunFailedFirstAction(); processChangesInUI(); if (hasErrorsOrFailures()) { @@ -899,6 +920,14 @@ public class TestRunnerViewPart extends ViewPart { } } + private class LinkWithEditorAction extends AbstractToggleLinkingAction { + + @Override + public void run() { + setLinkingEnabled(isChecked()); + } + } + private class RerunLastAction extends Action { public RerunLastAction() { setText(JUnitMessages.TestRunnerViewPart_rerunaction_label); @@ -1200,6 +1229,177 @@ public class TestRunnerViewPart extends ViewPart { } } + /** + * Links the selected test method with the Java Editor + * @param enabled boolean to indicate if the link with editor is enabled (true) or not (false) + */ + public void setLinkingEnabled(boolean enabled) { + final IWorkbenchPage page= getSite().getPage(); + if(page == null) { + return; + } + if (enabled) { + // add an IPartListener for future editor activations/opening/closing/etc. + page.addPartListener(fLinkWithEditorPartListener); + } else { + // removes the IPartListener + page.removePartListener(fLinkWithEditorPartListener); + } + for(IEditorReference editorReference : page.getEditorReferences()) { + final IEditorPart editor = editorReference.getEditor(false); + // set the current editor as active as well + if (editor != null && enabled) { + editorActivated(editor); + } + // unset the current editor as active as well + else if(editor != null && !enabled){ + editorDeactivated(editor); + } + } + } + + + private final IPartListener2 fLinkWithEditorPartListener= new IPartListener2() { + public void partVisible(IWorkbenchPartReference partRef) {} + public void partBroughtToTop(IWorkbenchPartReference partRef) {} + public void partHidden(IWorkbenchPartReference partRef) {} + public void partOpened(IWorkbenchPartReference partRef) {} + public void partInputChanged(IWorkbenchPartReference partRef) {} + public void partClosed(IWorkbenchPartReference partRef) {} + + public void partActivated(IWorkbenchPartReference partRef) { + if (partRef instanceof IEditorReference) { + editorActivated(((IEditorReference) partRef).getEditor(true)); + } + } + + public void partDeactivated(IWorkbenchPartReference partRef) { + if (partRef instanceof IEditorReference) { + editorDeactivated(((IEditorReference) partRef).getEditor(true)); + } + } + + }; + + /** + * Static Inner Java Editor selection listener + * When the user moves to another + */ + private class JavaEditorSelectionListener implements ISelectionChangedListener { + + private final ICompilationUnit compilationUnit; + + JavaEditorSelectionListener(final ICompilationUnit compilationUnit) { + this.compilationUnit = compilationUnit; + } + + public void selectionChanged(final SelectionChangedEvent event) { + selectActiveTestCaseElement(event.getSelection()); + } + + private IJavaElement getActiveJavaElement(final ISelection selection) { + if(selection instanceof ITextSelection) { + final int offset= ((ITextSelection)selection).getOffset(); + try { + return compilationUnit.getElementAt(offset); + } catch (JavaModelException e) { + } + } + return null; + } + + public void selectActiveTestCaseElement(final ISelection selection) { + final IJavaElement activeJavaElement = getActiveJavaElement(selection); + // select the method in the JUnit ViewPart + if(activeJavaElement != null && activeJavaElement.getElementType() == IJavaElement.METHOD) { + final IMethod activeJavaMethod = (IMethod)activeJavaElement; + final IType activeJavaType = (IType)activeJavaMethod.getAncestor(IJavaElement.TYPE); + fTestViewer.selectTestCaseElement(activeJavaType.getFullyQualifiedName(), activeJavaMethod.getElementName()); + } + + } + + /** + * Overriding java.lang.Object#hashCode() using the + * {@link ICompilationUnit#getHandleIdentifier()} value to avoid duplicate listener + * registrations in the {@link JavaEditor}'s {@link IPostSelectionProvider}. + */ + @Override + public int hashCode() { + final int prime= 31; + int result= 1; + result= prime * result + ((compilationUnit == null) ? 0 : compilationUnit.getHandleIdentifier().hashCode()); + return result; + } + + /** + * Overriding java.lang.Object#equals() using the + * {@link ICompilationUnit#getHandleIdentifier()} value to avoid duplicate listener + * registrations in the {@link JavaEditor}'s {@link IPostSelectionProvider}. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + JavaEditorSelectionListener other= (JavaEditorSelectionListener)obj; + if (compilationUnit == null) { + if (other.compilationUnit != null) { + return false; + } + } else if (!compilationUnit.getHandleIdentifier().equals(other.compilationUnit.getHandleIdentifier())) { + return false; + } + return true; + } + + + } + + private JavaEditorSelectionListener fJavaEditorSelectionListener; + + /** + * An editor has been activated. Set the selection in this JUnit ViewPart + * to be the editor's input, if linking is enabled. + * @param editor the activated editor + */ + private void editorActivated(IEditorPart editor) { + if(!(editor instanceof JavaEditor)) { + return; + } + final ISelectionProvider selectionProvider = ((JavaEditor)editor).getSelectionProvider(); + final Object input= JavaUI.getEditorInputJavaElement(editor.getEditorInput()); + if (input instanceof ICompilationUnit && selectionProvider instanceof IPostSelectionProvider) { + final ICompilationUnit unit = (ICompilationUnit) input; + final IPostSelectionProvider postSelectionProvider = (IPostSelectionProvider)selectionProvider; + fJavaEditorSelectionListener = new JavaEditorSelectionListener(unit); + postSelectionProvider.addPostSelectionChangedListener(fJavaEditorSelectionListener); + fJavaEditorSelectionListener.selectActiveTestCaseElement(selectionProvider.getSelection()); + } + } + + /** + * An editor has been deactivated. + * @param editor the activated editor + */ + private void editorDeactivated(IEditorPart editor) { + if(!(editor instanceof JavaEditor)) { + return; + } + final ISelectionProvider selectionProvider = ((JavaEditor)editor).getSelectionProvider(); + if (selectionProvider instanceof IPostSelectionProvider) { + final IPostSelectionProvider postSelectionProvider = (IPostSelectionProvider)selectionProvider; + postSelectionProvider.removePostSelectionChangedListener(fJavaEditorSelectionListener); + } + } + + private void startUpdateJobs() { postSyncProcessChanges(); @@ -1507,11 +1707,13 @@ action enablement startUpdateJobs(); fStopAction.setEnabled(true); + fLinkWithEditorAction.setEnabled(true); } else /* old or fresh session: don't want jobs at this stage */ { stopUpdateJobs(); fStopAction.setEnabled(fTestRunSession.isKeptAlive()); + fLinkWithEditorAction.setEnabled(fTestRunSession != null); fTestViewer.expandFirstLevel(); } } @@ -1887,6 +2089,9 @@ action enablement fStopAction= new StopAction(); fStopAction.setEnabled(false); + fLinkWithEditorAction= new LinkWithEditorAction(); + fLinkWithEditorAction.setEnabled(false); + fRerunLastTestAction= new RerunLastAction(); IHandlerService handlerService= (IHandlerService) getSite().getWorkbenchWindow().getService(IHandlerService.class); IHandler handler = new AbstractHandler() { @@ -1937,7 +2142,9 @@ action enablement toolBar.add(fRerunFailedFirstAction); toolBar.add(fStopAction); toolBar.add(fViewHistory.createHistoryDropDownAction()); - + toolBar.add(new Separator()); + toolBar.add(fLinkWithEditorAction); + viewMenu.add(fShowTestHierarchyAction); viewMenu.add(fShowTimeAction); diff --git a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java index 7a4f575..e885eba 100644 --- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java +++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java @@ -21,7 +21,9 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import org.eclipse.jdt.junit.model.ITestCaseElement; import org.eclipse.jdt.junit.model.ITestElement; +import org.eclipse.jdt.junit.model.ITestSuiteElement; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; @@ -39,6 +41,7 @@ import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ILabelDecorator; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; @@ -571,6 +574,51 @@ public class TestViewer { fTreeViewer.reveal(current); } + /** + * Selects and reveals the given {@link ITestCaseElement} in the viewer's tree. + * @param testClassName the qualified class name of the test to select + * @param testMethodName the method name of the test to select + */ + public void selectTestCaseElement(final String testClassName, final String testMethodName) { + // skip if history was cleared and 'Link with Editor' is still enabled. + if(fTestRunSession == null) { + return; + } + final TestRoot testRoot = fTestRunSession.getTestRoot(); + final ISelection currentSelection= getActiveViewer().getSelection(); + if(currentSelection instanceof IStructuredSelection) { + for(Object selectedItem : ((IStructuredSelection)currentSelection).toList()) { + if(selectedItem instanceof TestCaseElement && ((TestCaseElement)selectedItem).getTestClassName().equals(testClassName) + && ((TestCaseElement)selectedItem).getTestMethodName().equals(testMethodName)) { + // the current selection in the TestViewer includes the given test class/method + return; + } + } + } + final ITestCaseElement activeTestCaseElement = findTestCaseElement(testRoot, testClassName, testMethodName); + if(activeTestCaseElement != null) { + getActiveViewer().setSelection(new StructuredSelection(activeTestCaseElement), true); + } + } + + private ITestCaseElement findTestCaseElement(final ITestSuiteElement parentElement, final String testClassName, final String testMethodName) { + for(ITestElement childElement : parentElement.getChildren()) { + if(childElement instanceof ITestCaseElement) { + ITestCaseElement testCaseElement = (ITestCaseElement)childElement; + if(testCaseElement.getTestClassName().equals(testClassName) && testCaseElement.getTestMethodName().equals(testMethodName)) { + return testCaseElement; + } + } else if(childElement instanceof ITestSuiteElement) { + final ITestCaseElement localResult= findTestCaseElement((ITestSuiteElement)childElement, testClassName, testMethodName); + if(localResult != null) { + return localResult; + } + } + } + return null; + + } + public void selectFirstFailure() { TestCaseElement firstFailure= getNextChildFailure(fTestRunSession.getTestRoot(), true); if (firstFailure != null)