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 44f55951c8..04c8d85fef 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 @@ -1775,33 +1775,35 @@ protected String getMethodBreakpointText(IJavaMethodBreakpoint methodBreakpoint) appendSuspendPolicy(methodBreakpoint,label); appendThreadFilter(methodBreakpoint, label); - - boolean entry = methodBreakpoint.isEntry(); - boolean exit = methodBreakpoint.isExit(); - if (entry && exit) { - label.append(DebugUIMessages.JDIModelPresentation_entry_and_exit); - } else if (entry) { - label.append(DebugUIMessages.JDIModelPresentation_entry); - } else if (exit) { - label.append(DebugUIMessages.JDIModelPresentation_exit); - } - appendConditional(methodBreakpoint, label); - - if (member != null) { - label.append(" - "); //$NON-NLS-1$ - label.append(getJavaLabelProvider().getText(member)); + if (methodBreakpoint.isLambdaBreakpoint()) { + processInLineLambdaLabel(methodBreakpoint, label, member); } else { - String methodSig= methodBreakpoint.getMethodSignature(); - String methodName= methodBreakpoint.getMethodName(); - if (methodSig != null) { - label.append(" - "); //$NON-NLS-1$ - label.append(Signature.toString(methodSig, methodName, null, false, false)); - } else if (methodName != null) { + boolean entry = methodBreakpoint.isEntry(); + boolean exit = methodBreakpoint.isExit(); + if (entry && exit) { + label.append(DebugUIMessages.JDIModelPresentation_entry_and_exit); + } else if (entry) { + label.append(DebugUIMessages.JDIModelPresentation_entry); + } else if (exit) { + label.append(DebugUIMessages.JDIModelPresentation_exit); + } + appendConditional(methodBreakpoint, label); + + if (member != null) { label.append(" - "); //$NON-NLS-1$ - label.append(methodName); + label.append(getJavaLabelProvider().getText(member)); + } else { + String methodSig = methodBreakpoint.getMethodSignature(); + String methodName = methodBreakpoint.getMethodName(); + if (methodSig != null) { + label.append(" - "); //$NON-NLS-1$ + label.append(Signature.toString(methodSig, methodName, null, false, false)); + } else if (methodName != null) { + label.append(" - "); //$NON-NLS-1$ + label.append(methodName); + } } } - return label.toString(); } @@ -2199,4 +2201,29 @@ public Color getBackground(Object element) { public synchronized boolean requiresUIThread(Object element) { return !isInitialized(); } + + /** + * Process custom label for inline lambda breakpoints + */ + private void processInLineLambdaLabel(IJavaMethodBreakpoint methodBreakpoint, StringBuilder label, IMember member) throws CoreException { + appendConditional(methodBreakpoint, label); + if (methodBreakpoint.getLambdaName() != null) { + label.append(" - [ " + methodBreakpoint.getLambdaName() + " ]"); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + if (member != null) { + label.append(" - "); //$NON-NLS-1$ + label.append(getJavaLabelProvider().getText(member)); + } else { + String methodSig = methodBreakpoint.getMethodSignature(); + String methodName = methodBreakpoint.getMethodName(); + if (methodSig != null) { + label.append(" - "); //$NON-NLS-1$ + label.append(Signature.toString(methodSig, methodName, null, false, false)); + } else if (methodName != null) { + label.append(" - "); //$NON-NLS-1$ + label.append(methodName); + } + } + } + } } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.java index 61fe1daff8..348ad66674 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.java @@ -176,6 +176,8 @@ public class ActionMessages extends NLS { public static String Override_Dependencies_label1; public static String Override_Dependencies_label2; + public static String LambdaSelectionDialog_title; + static { // load message values from bundle file NLS.initializeMessages(BUNDLE_NAME, ActionMessages.class); diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.properties index d1ad471931..a5be1d1d78 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.properties +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ActionMessages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2000, 2022 IBM Corporation and others. +# Copyright (c) 2000, 2025 IBM Corporation and others. # # This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 @@ -154,4 +154,5 @@ Override_Dependencies_title=Override Dependencies Override_Dependencies_button=&Override Override_Dependencies_button1=&Override Dependencies... Override_Dependencies_label1=Dependencies derived from the Java Build Path: -Override_Dependencies_label2=Dependencies for launching: \ No newline at end of file +Override_Dependencies_label2=Dependencies for launching: +LambdaSelectionDialog_title=Select lambda \ No newline at end of file diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ToggleBreakpointAdapter.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ToggleBreakpointAdapter.java index a6ce1cb07d..fa3f0bef8f 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ToggleBreakpointAdapter.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ToggleBreakpointAdapter.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -65,7 +66,9 @@ import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.LambdaExpression; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; @@ -78,11 +81,13 @@ import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.IJavaWatchpoint; import org.eclipse.jdt.debug.core.JDIDebugModel; +import org.eclipse.jdt.internal.core.LambdaMethod; import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContext; import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContextType; import org.eclipse.jdt.internal.corext.template.java.JavaContextType; import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; -import org.eclipse.jdt.internal.debug.core.breakpoints.FirstLambdaLocationLocator; +import org.eclipse.jdt.internal.debug.core.breakpoints.LambdaCollector; +import org.eclipse.jdt.internal.debug.core.breakpoints.LambdaLocationLocatorHelper; import org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator; import org.eclipse.jdt.internal.debug.ui.BreakpointUtils; import org.eclipse.jdt.internal.debug.ui.DebugWorkingCopyManager; @@ -111,18 +116,27 @@ import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.jface.text.templates.TemplateException; +import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.eclipse.ui.dialogs.FilteredList; import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; @@ -136,7 +150,11 @@ public class ToggleBreakpointAdapter implements IToggleBreakpointsTargetExtension2 { private static final String EMPTY_STRING = ""; //$NON-NLS-1$ - + private static final String TRUNCATION_SIGN = "... "; //$NON-NLS-1$ + private static final String LAMBDA_SEPARATOR = " "; //$NON-NLS-1$ + private static final int MAX_LAMBDA_LINE_LENGTH = 25; + private static final int MAX_TOTAL_LAMBDA_LINE_LENGTH = MAX_LAMBDA_LINE_LENGTH * 2; + private static final String ZERO_WIDTH_SPACE = "\u200B"; //$NON-NLS-1$ /** * Constructor @@ -269,7 +287,7 @@ protected IStatus run(IProgressMonitor monitor) { job.schedule(); } - public void toggleLambdaEntryMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection, final String lambdaMethodName, final String lambdaMethodSignature) { + static void toggleLambdaEntryMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection, final String lambdaMethodName, final String lambdaMethodSignature, final LambdaProperties lambdaProperties) { Job job = new Job("Toggle Lambda Entry Method Breakpoints") { //$NON-NLS-1$ @Override protected IStatus run(IProgressMonitor monitor) { @@ -277,7 +295,7 @@ protected IStatus run(IProgressMonitor monitor) { return Status.CANCEL_STATUS; } try { - return doToggleLambdaEntryMethodBreakpoints(part, finalSelection, lambdaMethodName, lambdaMethodSignature, monitor); + return doToggleLambdaEntryMethodBreakpoints(part, finalSelection, lambdaMethodName, lambdaMethodSignature, lambdaProperties, monitor); } catch (CoreException e) { return e.getStatus(); } finally { @@ -290,7 +308,7 @@ protected IStatus run(IProgressMonitor monitor) { job.schedule(); } - static IStatus doToggleLambdaEntryMethodBreakpoints(IWorkbenchPart part, ISelection selection, String lambdaMethodName, String lambdaMethodSignature, IProgressMonitor monitor) throws CoreException { + static IStatus doToggleLambdaEntryMethodBreakpoints(IWorkbenchPart part, ISelection selection, String lambdaMethodName, String lambdaMethodSignature, LambdaProperties lambdaProperties, IProgressMonitor monitor) throws CoreException { ITextEditor textEditor = getTextEditor(part); if (textEditor == null || !(selection instanceof ITextSelection)) { return Status.OK_STATUS; @@ -316,7 +334,7 @@ static IStatus doToggleLambdaEntryMethodBreakpoints(IWorkbenchPart part, ISelect } if (method != null) { - doToggleMethodBreakpoint(method, lambdaMethodName, lambdaMethodSignature, part, selection, monitor); + doToggleMethodBreakpoint(method, lambdaMethodName, lambdaMethodSignature, lambdaProperties, part, selection, monitor); } else { BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); } @@ -354,7 +372,7 @@ static IStatus doToggleLambdaMethodBreakpoints(IWorkbenchPart part, ISelection s } if (method != null) { - doToggleMethodBreakpoint(method, loc.getLambdaMethodName(), loc.getfLambdaMethodSignature(), part, selection, monitor); + doToggleMethodBreakpoint(method, loc.getLambdaMethodName(), loc.getfLambdaMethodSignature(), null, part, selection, monitor); } else { ValidBreakpointLocationLocator locNew = new ValidBreakpointLocationLocator(loc.getCompilationUnit(), textSelection.getStartLine() + 1, true, true); @@ -392,11 +410,16 @@ static IStatus doToggleMethodBreakpoints(IWorkbenchPart part, ISelection finalSe } private static void doToggleMethodBreakpoint(IMethod member, IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException { - doToggleMethodBreakpoint(member, null, null, part, finalSelection, monitor); + doToggleMethodBreakpoint(member, null, null, null, part, finalSelection, monitor); } - private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethodName, String lambdaMethodSignature, IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException { - IJavaBreakpoint breakpoint = getMethodBreakpoint(member); + private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethodName, String lambdaMethodSignature, LambdaProperties lambdaProperties, IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException { + int lambdaPosition = 0; + if (lambdaProperties != null) { + lambdaPosition = lambdaProperties.lambdaPosition(); + } + + IJavaBreakpoint breakpoint = getMethodBreakpoint(member, lambdaPosition); if (breakpoint != null) { if (BreakpointToggleUtils.isToggleTracepoint()) { deleteTracepoint(breakpoint, part, monitor); @@ -448,6 +471,13 @@ private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethod } BreakpointToggleUtils.setUnsetTracepoint(false); } + if (lambdaProperties != null) { + methodBreakpoint.setLambdaBreakpoint(true); + methodBreakpoint.setInlineLambdaPosition(lambdaPosition); + if (lambdaProperties.isInline()) { + methodBreakpoint.setLambdaName(lambdaProperties.getLambdaName()); + } + } if (ValidBreakpointLocationLocator.LOCATION_METHOD_CLOSE) { methodBreakpoint.setEntry(false); methodBreakpoint.setExit(true); @@ -1336,51 +1366,55 @@ protected static IMethod getMethodHandle(IEditorPart editorPart, String typeName } /** - * Returns the IJavaBreakpoint from the specified IMember - * @param element the element to get the breakpoint from - * @return the current breakpoint from the element or null - */ + * Returns the IJavaBreakpoint from the specified IMember + * + * @param element + * the element to get the breakpoint from + * @param lambdaPosition + * the position of the lambda expression in the line (if any) + * @return the current breakpoint from the element or null + */ @SuppressWarnings("restriction") - protected static IJavaBreakpoint getMethodBreakpoint(IMember element) { + protected static IJavaBreakpoint getMethodBreakpoint(IMember element, int lambdaPosition) { + if (!(element instanceof IMethod method)) { + return null; + } IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager(); IBreakpoint[] breakpoints = breakpointManager.getBreakpoints(JDIDebugModel.getPluginIdentifier()); - if (!(element instanceof IMethod)) { - return null; - } - IMethod method = (IMethod) element; for (IBreakpoint breakpoint : breakpoints) { - if (!(breakpoint instanceof IJavaMethodBreakpoint)) { + if (!(breakpoint instanceof IJavaMethodBreakpoint methodBreakpoint)) { continue; } - IJavaMethodBreakpoint methodBreakpoint = (IJavaMethodBreakpoint) breakpoint; - IMember container = null; + IMember container; try { container = BreakpointUtils.getMember(methodBreakpoint); - } catch (CoreException e) { - JDIDebugUIPlugin.log(e); - return null; - } - if (container == null) { - try { + if (container == null) { if (method.getDeclaringType().getFullyQualifiedName().equals(methodBreakpoint.getTypeName()) && method.getElementName().equals(methodBreakpoint.getMethodName()) && methodBreakpoint.getMethodSignature().equals(resolveMethodSignature(method))) { return methodBreakpoint; } - } catch (CoreException e) { - JDIDebugUIPlugin.log(e); + continue; } - } else { - if (container instanceof IMethod) { - if (method instanceof org.eclipse.jdt.internal.core.LambdaMethod) { - return null; + if (!(container instanceof IMethod)) { + continue; + } + if (method instanceof LambdaMethod) { + ISourceRange sourceRange = element.getSourceRange(); + if (sourceRange != null && methodBreakpoint.getInlineLambdaPosition() == lambdaPosition + && methodBreakpoint.getCharStart() == sourceRange.getOffset()) { + return methodBreakpoint; } - if (method.getDeclaringType().getFullyQualifiedName().equals(container.getDeclaringType().getFullyQualifiedName())) { - if (method.isSimilar((IMethod) container)) { - return methodBreakpoint; - } + continue; + } + if (method.getDeclaringType().getFullyQualifiedName().equals(container.getDeclaringType().getFullyQualifiedName())) { + if (method.isSimilar((IMethod) container)) { + return methodBreakpoint; } } + } catch (CoreException e) { + JDIDebugUIPlugin.log(e); + continue; } } return null; @@ -1599,26 +1633,25 @@ private void toggleFieldOrMethodBreakpoints(IWorkbenchPart part, ISelection sele } // remove line breakpoint if present first IJavaLineBreakpoint breakpoint = findExistingBreakpoint(editor, ts); + if (breakpoint != null) { if (BreakpointToggleUtils.isToggleTracepoint()) { deleteTracepoint(breakpoint, part, null); BreakpointToggleUtils.setUnsetTracepoint(false); - } else if (BreakpointToggleUtils.isTriggerpoint()) { - deleteBreakpoint(breakpoint, part, null); BreakpointToggleUtils.setTriggerpoint(false); - } else if (BreakpointToggleUtils.isHitpoint()) { - deleteBreakpoint(breakpoint, part, null); BreakpointToggleUtils.setHitpoint(false); - } else { + } else if ((breakpoint instanceof IJavaMethodBreakpoint javaMBp && !javaMBp.isLambdaBreakpoint())) { + deleteBreakpoint(breakpoint, part, null); + return; + } else if (!BreakpointToggleUtils.isToggleLambdaEntryBreakpoint() + && (breakpoint instanceof IJavaMethodBreakpoint javaMB && javaMB.isLambdaBreakpoint())) { deleteBreakpoint(breakpoint, part, null); - } - if (!BreakpointToggleUtils.isToggleLambdaEntryBreakpoint()) { return; - } else if (breakpoint instanceof IJavaMethodBreakpoint) { + } else if (!BreakpointToggleUtils.isToggleLambdaEntryBreakpoint() && breakpoint instanceof IJavaMethodBreakpoint) { return; } } @@ -1653,33 +1686,68 @@ private void toggleFieldOrMethodBreakpoints(IWorkbenchPart part, ISelection sele toggleWatchpoints(part, ts); } else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) { if (lambdaEntryBP) { - IEditorInput editorInput = editor.getEditorInput(); - IDocumentProvider documentProvider = editor.getDocumentProvider(); - if (documentProvider == null) { - BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); - throw new CoreException(Status.CANCEL_STATUS); - } - IDocument document = documentProvider.getDocument(editorInput); - try { - IRegion region = document.getLineInformation(ts.getStartLine()); - FirstLambdaLocationLocator firstLambda = new FirstLambdaLocationLocator(region.getOffset(), region.getOffset() + region.getLength()); - unit.accept(firstLambda); - if (firstLambda.getNodeLength() == -1 || firstLambda.getNodeOffset() == -1) { - BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); - return; - } - ITextSelection textSelection = new TextSelection(document, firstLambda.getNodeOffset(), firstLambda.getNodeLength()); - toggleLambdaEntryMethodBreakpoints(part, textSelection, firstLambda.getLambdaMethodName(), firstLambda.getfLambdaMethodSignature()); - } catch (BadLocationException e) { - BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); - } - + toggleLambdaBreakpoint(part, ts, editor); } else { toggleLineBreakpoints(part, ts, false, loc); } } } + private static void toggleLambdaBreakpoint(IWorkbenchPart part, ITextSelection ts, ITextEditor editor) throws CoreException { + IDocumentProvider documentProvider = editor.getDocumentProvider(); + if (documentProvider == null) { + BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); + throw new CoreException(Status.CANCEL_STATUS); + } + IEditorInput editorInput = editor.getEditorInput(); + IDocument document = documentProvider.getDocument(editorInput); + if (document == null) { + BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); + throw new CoreException(Status.CANCEL_STATUS); + } + try { + IRegion region = document.getLineInformation(ts.getStartLine()); + List lambdaExpresions = findLambdaExpressions(editor, region); + if (lambdaExpresions.isEmpty()) { + return; + } + int lambdaPosition; + if (lambdaExpresions.size() == 1) { + lambdaPosition = 0; + } else { + lambdaPosition = selectLambda(lambdaExpresions); + if (lambdaPosition == -1) { + return; + } + } + LambdaExpression selectedLambda = lambdaExpresions.get(lambdaPosition); + IMethodBinding methodBinding = selectedLambda.resolveMethodBinding(); + if (methodBinding == null) { + BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); + return; + } + String lambdaMethodName = LambdaLocationLocatorHelper.toMethodName(methodBinding); + String lambdaMethodSignature = LambdaLocationLocatorHelper.toMethodSignature(methodBinding); + ITextSelection textSelection = new TextSelection(document, selectedLambda.getStartPosition(), selectedLambda.getLength()); + boolean inline = lambdaExpresions.size() > 1; + LambdaProperties lambdaProperties = new LambdaProperties(lambdaExpresions.get(lambdaPosition).toString(), lambdaPosition, inline); + toggleLambdaEntryMethodBreakpoints(part, textSelection, lambdaMethodName, lambdaMethodSignature, lambdaProperties); + } catch (BadLocationException e) { + BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part); + } + } + + private static List findLambdaExpressions(ITextEditor editor, IRegion region) { + LambdaCollector lambdas = new LambdaCollector(region.getOffset(), region.getOffset() + region.getLength()); + CompilationUnit unitForLambdas = parseCompilationUnit(editor); + if (unitForLambdas == null) { + JDIDebugUIPlugin.log("Failed to parse CU for: " + editor.getTitle(), new IllegalStateException()); //$NON-NLS-1$ + return List.of(); + } + unitForLambdas.accept(lambdas); + return lambdas.getLambdaExpressions(); + } + /** * Additional diagnosis info for bug 528321 */ @@ -1894,4 +1962,161 @@ private static ICompilationUnit getCompilationUnit(ITextEditor editor) { return JavaCore.createCompilationUnitFrom(file); } + static int selectLambda(List lambdaExps) { + Map seen = new HashMap<>(); + // Generate unique labels used also as keys for lambda expressions. Note that two identical + // lambda expressions in different locations should still have different names + // otherwise the selection dialog cannot distinguish them. + List lambdaNames = lambdaExps.stream().map(expr -> shortenLambdaExpression(expr.toString())).map(name -> { + int count = seen.getOrDefault(name, 0); + seen.put(name, count + 1); + if (count == 0) { + return name; + } + return name + ZERO_WIDTH_SPACE.repeat(count); + }).toList(); + LambdaLabelProvider lambdaLabelProvider = new LambdaLabelProvider(lambdaExps, lambdaNames); + ElementListSelectionDialog dialog = new LambdaSelectionDialog(DebugUIPlugin.getShellForModalDialog(), lambdaLabelProvider, lambdaExps, lambdaNames); + dialog.setMultipleSelection(false); + dialog.setTitle(ActionMessages.LambdaSelectionDialog_title); + dialog.setElements(lambdaExps.toArray()); + dialog.open(); + if (dialog.getReturnCode() == Window.OK) { + Object result = dialog.getFirstResult(); + return lambdaExps.indexOf(result); + } + return -1; + } + + private static final class LambdaSelectionDialog extends ElementListSelectionDialog { + private final List lambdaExps; + private final List lambdaNames; + + private LambdaSelectionDialog(Shell parent, ILabelProvider renderer, List lambdaExps, List lambdaNames) { + super(parent, renderer); + this.lambdaExps = lambdaExps; + this.lambdaNames = lambdaNames; + } + + @Override + protected FilteredList createFilteredList(Composite parent) { + FilteredList filteredList = super.createFilteredList(parent); + // Disable default sorting to keep the original order + filteredList.setComparator(new LambdaPositionComparator(lambdaNames)); + // Add selection listener to highlight selected lambda in the editor + filteredList.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int index = filteredList.getSelectionIndex(); + if (index < 0) { + return; + } + IWorkbenchPage page = JDIDebugUIPlugin.getActivePage(); + if (page == null) { + return; + } + IEditorPart editorPart = page.getActiveEditor(); + if (editorPart instanceof ITextEditor textEditor) { + LambdaExpression lambda = lambdaExps.get(index); + int start = lambda.getStartPosition(); + int length = lambda.getLength(); + textEditor.selectAndReveal(start, length); + } + } + }); + return filteredList; + } + + @Override + protected void setShellStyle(int newShellStyle) { + // overridden to allow interaction with the underlying editor while the dialog is open + super.setShellStyle(newShellStyle & ~SWT.APPLICATION_MODAL); + } + } + + private static final record LambdaProperties(String lambdaName, int lambdaPosition, boolean isInline) { + public String getLambdaName() { + return shortenLambdaExpression(lambdaName); + } + } + + private static final class LambdaPositionComparator implements Comparator { + private List lambdaNames; + + public LambdaPositionComparator(List lambdaNames) { + this.lambdaNames = lambdaNames; + } + + @Override + public int compare(Object o1, Object o2) { + int index1 = lambdaNames.indexOf(o1); + int index2 = lambdaNames.indexOf(o2); + return index1 - index2; + } + } + + private static final class LambdaLabelProvider extends LabelProvider { + private List lambdaExps; + private List lambdaNames; + + public LambdaLabelProvider(List lambdaExps, List lambdaNames) { + this.lambdaExps = lambdaExps; + this.lambdaNames = lambdaNames; + } + + @Override + public String getText(Object element) { + if (element instanceof LambdaExpression) { + int index = lambdaExps.indexOf(element); + if (index != -1) { + return lambdaNames.get(index); + } + } + return super.getText(element); + } + } + + /** + * Shorten and "flatten" the (possible multi-line) lambda expression + * + * @param input + * non null + * @return shortened and flattened lambda expression + */ + static String shortenLambdaExpression(String input) { + input = input.strip(); + StringBuilder result = new StringBuilder(); + if (!input.contains("\n")) { //$NON-NLS-1$ + if (input.length() > MAX_TOTAL_LAMBDA_LINE_LENGTH) { + result.append(input, 0, MAX_TOTAL_LAMBDA_LINE_LENGTH).append(TRUNCATION_SIGN); + } else { + result.append(input); + } + return result.toString(); + } + boolean shortened = false; + for (String line : input.split("\n")) { //$NON-NLS-1$ + if (result.length() > 0) { + result.append(LAMBDA_SEPARATOR); + } + line = line.strip(); + if (line.length() > MAX_LAMBDA_LINE_LENGTH) { + line = line.substring(0, MAX_LAMBDA_LINE_LENGTH) + TRUNCATION_SIGN; + shortened = true; + } + result.append(line); + if (result.length() > MAX_TOTAL_LAMBDA_LINE_LENGTH) { + shortened = true; + break; + } + } + + if (shortened) { + if (result.lastIndexOf(TRUNCATION_SIGN) != result.length() - TRUNCATION_SIGN.length()) { + result.append(TRUNCATION_SIGN); + } + } + return result.toString().trim(); + } + } \ No newline at end of file diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/MethodBreakpointEditor.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/MethodBreakpointEditor.java index 35fc8196b0..506dace43f 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/MethodBreakpointEditor.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/breakpoints/MethodBreakpointEditor.java @@ -56,12 +56,16 @@ public Control createControl(Composite parent) { @Override protected void setBreakpoint(IJavaBreakpoint breakpoint) throws CoreException { super.setBreakpoint(breakpoint); - if (breakpoint instanceof IJavaMethodBreakpoint) { - IJavaMethodBreakpoint watchpoint = (IJavaMethodBreakpoint) breakpoint; + if (breakpoint instanceof IJavaMethodBreakpoint methodBreakpoint) { + if (methodBreakpoint.isLambdaBreakpoint()) { + fEntry.setEnabled(false); + fExit.setEnabled(false); + return; + } fEntry.setEnabled(true); fExit.setEnabled(true); - fEntry.setSelection(watchpoint.isEntry()); - fExit.setSelection(watchpoint.isExit()); + fEntry.setSelection(methodBreakpoint.isEntry()); + fExit.setSelection(methodBreakpoint.isExit()); } else { fEntry.setEnabled(false); fExit.setEnabled(false); diff --git a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF index 39dc0010be..9afbed70f5 100644 --- a/org.eclipse.jdt.debug/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.debug/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jdt.debug; singleton:=true -Bundle-Version: 3.24.100.qualifier +Bundle-Version: 3.25.0.qualifier Bundle-ClassPath: jdimodel.jar Bundle-Activator: org.eclipse.jdt.internal.debug.core.JDIDebugPlugin Bundle-Vendor: %providerName diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaMethodBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaMethodBreakpoint.java index 20d2384f97..7c377e04dd 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaMethodBreakpoint.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/debug/core/IJavaMethodBreakpoint.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -156,4 +156,58 @@ public interface IJavaMethodBreakpoint extends IJavaLineBreakpoint { * exit or if this breakpoint hasn't suspended the given target. */ public boolean isEntrySuspend(IDebugTarget target); + + /** + * Returns whether this breakpoint a lambda entry breakpoint or not. + * + * @return true if this breakpoint is a lambda entry breakpoint, false if not. + * @since 3.25 + */ + public boolean isLambdaBreakpoint(); + + /** + * Sets whether this breakpoint lambda entry breakpoint or not + * + * @param isLambdaBreakpoint + * lambda status of the breakpoint + * @throws CoreException + * @since 3.25 + */ + public void setLambdaBreakpoint(boolean isLambdaBreakpoint) throws CoreException; + + /** + * Returns the position of lambda in a single line chained lambda expression. Starts with 0 + * + * @return the position of lambda in a lambda expression. + * @since 3.25 + */ + public int getInlineLambdaPosition(); + + /** + * Sets the in-line lambda position + * + * @param pos + * lambda position, starts with 0 and cannot be negative + * @throws CoreException + * @since 3.25 + */ + public void setInlineLambdaPosition(int pos) throws CoreException; + + /** + * Sets the local name used by user in the editor for single-lined lambdas + * + * @param lambda + * lambda name + * @throws CoreException + * @since 3.25 + */ + public void setLambdaName(String lambda) throws CoreException; + + /** + * Returns the local lambda name used by the user in editor + * + * @return lambda name + * @since 3.25 + */ + public String getLambdaName(); } diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/FirstLambdaLocationLocator.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/FirstLambdaLocationLocator.java deleted file mode 100644 index 0013374dfc..0000000000 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/FirstLambdaLocationLocator.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.jdt.internal.debug.core.breakpoints; - -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.LambdaExpression; - -public class FirstLambdaLocationLocator extends ASTVisitor { - private int fNodeLength = -1; - private int fNodeOffset = -1; - private int fLineOffset = -1; - private int fLineEndPosition = -1; - private String fLambdaMethodName; - private String fLambdaMethodSignature; - private boolean fLocationFound = false; - - public FirstLambdaLocationLocator(int lineOffset, int lineEndPosition) { - fLineOffset = lineOffset; - fLineEndPosition = lineEndPosition; - } - - /** - * Return of the name of the lambda method where the valid location is. - */ - public String getLambdaMethodName() { - return fLambdaMethodName; - } - - /** - * Return of the signature of the lambda method where the valid location is. - * The signature is computed to be compatible with the final lambda method with - * method arguments and outer local variables. - */ - public String getfLambdaMethodSignature() { - return fLambdaMethodSignature; - } - - public int getNodeLength() { - return fNodeLength; - } - - public int getNodeOffset() { - return fNodeOffset; - } - - @Override - public boolean visit(LambdaExpression node) { - if (fLocationFound) { - return false; - } - if (node.getStartPosition() < fLineOffset || node.getStartPosition() > fLineEndPosition) { - return false; - } - fNodeLength = node.getLength(); - fNodeOffset = node.getStartPosition(); - IMethodBinding methodBinding = node.resolveMethodBinding(); - if (methodBinding != null) { - fLambdaMethodName = LambdaLocationLocatorHelper.toMethodName(methodBinding); - fLambdaMethodSignature = LambdaLocationLocatorHelper.toMethodSignature(methodBinding); - fLocationFound = true; - } - return false; - } -} diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java index b0955c3f24..1e96af76b3 100644 --- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/JavaMethodBreakpoint.java @@ -93,6 +93,24 @@ public class JavaMethodBreakpoint extends JavaLineBreakpoint implements */ protected static final String NATIVE = "org.eclipse.jdt.debug.core.native"; //$NON-NLS-1$ + /** + * Breakpoint attribute storing the local name of lambda seen in the editor (value "org.eclipse.jdt.debug.core.lambdaName"). This + * attribute is a String. + */ + protected static final String LAMBDA_LOCAL_NAME = "org.eclipse.jdt.debug.core.lambdaName"; //$NON-NLS-1$ + + /** + * Breakpoint attribute storing the whether the method breakpoint is of type lambda or not (value + * "org.eclipse.jdt.debug.core.isLambda"). This attribute is a boolean. + */ + protected static final String LAMBDA_BREAKPOINT = "org.eclipse.jdt.debug.core.isLambda"; //$NON-NLS-1$ + + /** + * Breakpoint attribute storing in-line position of lambda for single -line chained lambda expressions (value + * "org.eclipse.jdt.debug.core.lambdaPosition"). This attribute is a integer. + */ + protected static final String LAMBDA_INLINE_POSITION = "org.eclipse.jdt.debug.core.lambdaPosition"; //$NON-NLS-1$ + /** * Cache of method name attribute */ @@ -135,6 +153,21 @@ public class JavaMethodBreakpoint extends JavaLineBreakpoint implements */ private Boolean fUsesTypePattern = null; + /** + * Cache of lambda type or not attribute + */ + private boolean isLambdaBreakpoint; + + /** + * Cache of lambda position attribute + */ + private int inlineLambdaPosition; + + /** + * Cache of lambda name attribute + */ + private String fLambdaName; + /** * Constructs a new method breakpoint */ @@ -634,6 +667,12 @@ public void setMarker(IMarker marker) throws CoreException { JDIDebugBreakpointMessages.JavaMethodBreakpoint_0, e)); } } + isLambdaBreakpoint = marker.getAttribute(LAMBDA_BREAKPOINT, false); + if (isLambdaBreakpoint) { + fLambdaName = marker.getAttribute(LAMBDA_LOCAL_NAME, null); + inlineLambdaPosition = marker.getAttribute(LAMBDA_INLINE_POSITION, 0); + } + } /** @@ -778,4 +817,38 @@ protected void addInstanceFilter(EventRequest request, super.addInstanceFilter(request, object); } } + + @Override + public boolean isLambdaBreakpoint() { + return isLambdaBreakpoint; + } + + @Override + public void setLambdaBreakpoint(boolean isLambdaBreakpoint) throws CoreException { + setAttribute(LAMBDA_BREAKPOINT, isLambdaBreakpoint); + this.isLambdaBreakpoint = isLambdaBreakpoint; + } + + @Override + public int getInlineLambdaPosition() { + return inlineLambdaPosition; + } + + @Override + public void setInlineLambdaPosition(int pos) throws CoreException { + setAttribute(LAMBDA_INLINE_POSITION, pos); + inlineLambdaPosition = pos; + } + + @Override + public void setLambdaName(String lambda) throws CoreException { + setAttribute(LAMBDA_LOCAL_NAME, lambda); + fLambdaName = lambda; + } + + @Override + public String getLambdaName() { + return fLambdaName; + } + } diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/LambdaCollector.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/LambdaCollector.java new file mode 100644 index 0000000000..b71f2acec9 --- /dev/null +++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/breakpoints/LambdaCollector.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.core.breakpoints; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.LambdaExpression; + +/** + * Collects lambda expressions in a given line range + */ +public class LambdaCollector extends ASTVisitor { + private final int fLineOffset; + private final int fLineEndPosition; + private final List lambdaExpressions; + + public LambdaCollector(int lineOffset, int lineEndPosition) { + fLineOffset = lineOffset; + fLineEndPosition = lineEndPosition; + lambdaExpressions = new ArrayList<>(); + } + + @Override + public boolean visit(LambdaExpression node) { + if (node.getStartPosition() < fLineOffset || node.getStartPosition() > fLineEndPosition) { + return false; + } + IMethodBinding methodBinding = node.resolveMethodBinding(); + if (methodBinding != null) { + lambdaExpressions.add(node); + } + return false; + } + + public List getLambdaExpressions() { + return lambdaExpressions; + } +}