diff --git a/org.eclipse.jdt.debug.tests/testfiles/DebugSelectionTests/src/selectiontests/LambdaSelectionTest.java b/org.eclipse.jdt.debug.tests/testfiles/DebugSelectionTests/src/selectiontests/LambdaSelectionTest.java new file mode 100644 index 0000000000..0fe5d21ade --- /dev/null +++ b/org.eclipse.jdt.debug.tests/testfiles/DebugSelectionTests/src/selectiontests/LambdaSelectionTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev 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: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package selectiontests; + +import java.util.Arrays; +import java.util.List; + +/** + * The test resumes at lambda chain and at each resume expects selecting the next lambda expression. + */ +public class LambdaSelectionTest { + + public static void main(String[] main) { + List list = Arrays.asList("A"); + list.stream() + .map(s -> s.toLowerCase()).filter(s -> s.equals("b")).forEach(System.out::println); // line 27, test breakpoint is set here + } +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugUiTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugUiTests.java index a490af79c0..4ac21e0609 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugUiTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugUiTests.java @@ -263,7 +263,7 @@ protected IEditorPart openEditor(String type) throws RuntimeException { return callInUi(callable); } - private static T callInUi(Callable callable) throws RuntimeException { + protected static T callInUi(Callable callable) throws RuntimeException { if (Display.getCurrent() != null) { try { return callable.call(); diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugSelectionTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugSelectionTests.java new file mode 100644 index 0000000000..b9faaf3a17 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugSelectionTests.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev 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: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.debug.tests.ui; + +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.debug.core.IJavaThread; +import org.eclipse.jdt.debug.testplugin.JavaProjectHelper; +import org.eclipse.jdt.debug.tests.TestUtil; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.test.OrderedTestSuite; +import org.eclipse.ui.texteditor.ITextEditor; + +import junit.framework.Test; + +public class DebugSelectionTests extends AbstractDebugUiTests { + + public static Test suite() { + return new OrderedTestSuite(DebugSelectionTests.class); + } + + private IJavaProject project; + + public DebugSelectionTests(String name) { + super(name); + } + + @Override + protected IJavaProject getProjectContext() { + return project; + } + + @Override + public void setUp() throws Exception { + super.setUp(); + project = createProject("DebugSelectionTests", "testfiles/DebugSelectionTests/", JavaProjectHelper.JAVA_SE_1_8_EE_NAME, false); + waitForBuild(); + } + + @Override + public void tearDown() throws Exception { + closeAllEditors(); + project.getProject().delete(true, null); + super.tearDown(); + } + + /** + * Resume at lambda chain and at each resume expect selecting the next lambda expression. + */ + public void testLambdaEditorSelection() throws Exception { + IJavaThread thread = null; + try { + String typeName = "selectiontests.LambdaSelectionTest"; + createLineBreakpoint(27, typeName); + ILaunchConfiguration config = createLaunchConfiguration(project, typeName); + thread = launchAndSuspend(config); + resume(thread); + waitForSelection("s -> s.toLowerCase()", 10_000L); + resume(thread); + waitForSelection("s -> s.equals(\"b\")", 10_000L); + } finally { + removeAllBreakpoints(); + if (thread != null) { + terminateAndRemove(thread); + } + } + } + + private static void waitForSelection(String expectedSelection, long timeout) throws InterruptedException { + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < timeout) { + if (expectedSelection.equals(getActiveEditorSelectionText())) { + return; + } + TestUtil.runEventLoop(); + Thread.sleep(50L); + } + assertEquals("Timed out while waiting for selection", expectedSelection, getActiveEditorSelectionText()); + } + + private static String getActiveEditorSelectionText() { + return callInUi(() -> { + ITextEditor editor = (ITextEditor) getActivePage().getActiveEditor(); + if (editor != null) { + ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + if (selection != null) { + return selection.getText(); + } + } + return ""; + }); + } +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java index 395d22e402..72ce3215ea 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIModelPresentation.java @@ -42,6 +42,7 @@ import org.eclipse.debug.internal.ui.DefaultLabelProvider; import org.eclipse.debug.internal.ui.views.variables.VariablesView; import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.debug.ui.IDebugEditorPresentation; import org.eclipse.debug.ui.IDebugModelPresentation; import org.eclipse.debug.ui.IDebugModelPresentationExtension; import org.eclipse.debug.ui.IDebugUIConstants; @@ -111,6 +112,7 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; @@ -125,7 +127,7 @@ * @see IDebugModelPresentation */ @SuppressWarnings("deprecation") -public class JDIModelPresentation extends LabelProvider implements IDebugModelPresentationExtension, IColorProvider { +public class JDIModelPresentation extends LabelProvider implements IDebugModelPresentationExtension, IColorProvider, IDebugEditorPresentation { /** * Qualified names presentation property (value "DISPLAY_QUALIFIED_NAMES"). @@ -157,12 +159,15 @@ public class JDIModelPresentation extends LabelProvider implements IDebugModelPr * */ private static final String BREAKPOINT_LABEL_SUFFIX = "JDT_BREAKPOINT_LABEL_SUFFIX"; //$NON-NLS-1$ + private final JavaStackFrameEditorPresenter fJavaStackFrameEditorPresenter; + private JavaElementLabelProvider fJavaLabelProvider; private StackFramePresentationProvider fStackFrameProvider; public JDIModelPresentation() { super(); + fJavaStackFrameEditorPresenter = new JavaStackFrameEditorPresenter(); } /* (non-Javadoc) @@ -2281,4 +2286,14 @@ private void processInLineLambdaLabel(IJavaMethodBreakpoint methodBreakpoint, St } } } + + @Override + public boolean addAnnotations(IEditorPart editorPart, IStackFrame frame) { + return fJavaStackFrameEditorPresenter.addAnnotations(editorPart, frame); + } + + @Override + public void removeAnnotations(IEditorPart editorPart, IThread thread) { + fJavaStackFrameEditorPresenter.removeAnnotations(editorPart, thread); + } } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFrameEditorPresenter.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFrameEditorPresenter.java new file mode 100644 index 0000000000..1c7dc4a6d0 --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFrameEditorPresenter.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation. + * + * 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 + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui; + +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.model.IStackFrame; +import org.eclipse.debug.core.model.IThread; +import org.eclipse.debug.ui.IDebugEditorPresentation; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; +import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; +import org.eclipse.jdt.internal.debug.ui.actions.ToggleBreakpointAdapter; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * Handles specialized selection of Java editors during debugging, such as selecting lambda expressions on a breakpoint hit. + */ +public class JavaStackFrameEditorPresenter implements IDebugEditorPresentation { + + @Override + public boolean addAnnotations(IEditorPart editor, IStackFrame frame) { + try { + if (editor instanceof ITextEditor textEditor && frame instanceof JDIStackFrame jdiFrame + && org.eclipse.jdt.internal.debug.core.model.LambdaUtils.isLambdaFrame(jdiFrame)) { + IEditorInput editorInput = editor.getEditorInput(); + IDocumentProvider provider = textEditor.getDocumentProvider(); + IDocument document = provider.getDocument(editorInput); + if (document != null && JavaUI.getEditorInputJavaElement(editorInput) != null) { + IRegion region = document.getLineInformation(jdiFrame.getLineNumber() - 1); + List inLineLambdas = ToggleBreakpointAdapter.findLambdaExpressions(textEditor, region); + for (LambdaExpression exp : inLineLambdas) { + String key = getMethodBindingKey(exp); + if (key != null && key.contains(jdiFrame.getName())) { + textEditor.selectAndReveal(exp.getStartPosition(), exp.getLength()); + return true; + } + } + } + } + } catch (CoreException | BadLocationException e) { + JDIDebugPlugin.log(e); + } + return false; + } + + @Override + public void removeAnnotations(IEditorPart editorPart, IThread thread) { + // nothing to clean up + } + + private static String getMethodBindingKey(LambdaExpression exp) { + String key = null; + IMethodBinding methodBinding = exp.resolveMethodBinding(); + if (methodBinding != null) { + key = methodBinding.getKey(); + } + return key; + } +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java index 7115e36780..8d8082d67d 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/JavaDebugShowInAdapterFactory.java @@ -14,13 +14,10 @@ package org.eclipse.jdt.internal.debug.ui.sourcelookup; import org.eclipse.core.runtime.IAdapterFactory; -import org.eclipse.debug.core.DebugException; -import org.eclipse.debug.internal.ui.DebugUIPlugin; import org.eclipse.debug.internal.ui.sourcelookup.SourceLookupFacility; import org.eclipse.debug.ui.sourcelookup.ISourceDisplay; import org.eclipse.jdt.debug.core.IJavaStackFrame; import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame; -import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTargetList; @@ -54,14 +51,6 @@ public T getAdapter(Object adaptableObject, Class adapterType) { SourceLookupFacility.getDefault().displaySource(frame, page, forceSourceLookup); }; } - try { - if (adaptableObject instanceof JDIStackFrame jdiFrame - && org.eclipse.jdt.internal.debug.core.model.LambdaUtils.isLambdaFrame(jdiFrame)) { - return (T) new LambdaStackFrameSourceDisplayAdapter(); - } - } catch (DebugException e) { - DebugUIPlugin.log(e); - } } return null; } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/LambdaStackFrameSourceDisplayAdapter.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/LambdaStackFrameSourceDisplayAdapter.java deleted file mode 100644 index d471414e43..0000000000 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/sourcelookup/LambdaStackFrameSourceDisplayAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2025 IBM Corporation. - * - * 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 - *******************************************************************************/ -package org.eclipse.jdt.internal.debug.ui.sourcelookup; - -import java.util.List; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.debug.internal.ui.DebugUIPlugin; -import org.eclipse.debug.internal.ui.sourcelookup.SourceLookupFacility; -import org.eclipse.debug.internal.ui.sourcelookup.SourceLookupResult; -import org.eclipse.debug.ui.sourcelookup.ISourceDisplay; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.LambdaExpression; -import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; -import org.eclipse.jdt.internal.debug.ui.actions.ToggleBreakpointAdapter; -import org.eclipse.jdt.ui.JavaUI; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.texteditor.IDocumentProvider; -import org.eclipse.ui.texteditor.ITextEditor; - -/** - * @since 3.2 - */ -public class LambdaStackFrameSourceDisplayAdapter implements ISourceDisplay { - - @Override - public void displaySource(Object element, IWorkbenchPage page, boolean forceSourceLookup) { - JDIStackFrame jdiFrame = (JDIStackFrame) element; - try { - SourceLookupResult sourceRes = SourceLookupFacility.getDefault().lookup(element, jdiFrame.getLaunch().getSourceLocator(), forceSourceLookup); - IDocumentProvider provider = JavaUI.getDocumentProvider(); - IEditorInput editorInput = sourceRes.getEditorInput(); - provider.connect(editorInput); - IDocument document = provider.getDocument(editorInput); - IRegion region = document.getLineInformation(jdiFrame.getLineNumber() - 1); - IJavaElement je = JavaUI.getEditorInputJavaElement(editorInput); - if (je != null) { - IEditorPart part = JavaUI.openInEditor(je); - if (part instanceof ITextEditor textEditor) { - List inLineLambdas = ToggleBreakpointAdapter.findLambdaExpressions(textEditor, region); - for (LambdaExpression exp : inLineLambdas) { - IMethodBinding methodBinding = exp.resolveMethodBinding(); - String key = methodBinding.getKey(); - if (key.contains(jdiFrame.getName())) { - textEditor.selectAndReveal(exp.getStartPosition(), exp.getLength()); - return; - } - } - } - } - } catch (CoreException | BadLocationException e) { - DebugUIPlugin.log(e); - } - SourceLookupFacility.getDefault().displaySource(jdiFrame, page, forceSourceLookup); - } -}